Warning, /detector_benchmarks/benchmarks/backgrounds/ecal_backwards.org is written in an unsupported language. File is not indexed.
0001 #+PROPERTY: header-args:jupyter-python :session /jpy:localhost#8888:eeemcal :async yes :results drawer :exports both
0002
0003 #+TITLE: ePIC EEEMCal background rates study
0004 #+AUTHOR: Dmitry Kalinkin
0005 #+OPTIONS: d:t
0006
0007 #+begin_src jupyter-python :results silent
0008 import os
0009 from pathlib import Path
0010
0011 import awkward as ak
0012 import boost_histogram as bh
0013 import dask
0014 import dask_awkward as dak
0015 import dask_histogram as dh
0016 import numpy as np
0017 import uproot
0018 from pyHepMC3 import HepMC3
0019 #+end_src
0020
0021 #+begin_src jupyter-python :results silent
0022 from dask.distributed import Client
0023
0024 client = Client(os.environ.get("DASK_SCHEDULER", "localhost:8786"))
0025 #+end_src
0026
0027 * Plotting setup
0028
0029 #+begin_src jupyter-python :results silent
0030 import matplotlib as mpl
0031 import matplotlib.pyplot as plt
0032
0033 def setup_presentation_style():
0034 mpl.rcParams.update(mpl.rcParamsDefault)
0035 plt.style.use('ggplot')
0036 plt.rcParams.update({
0037 'axes.labelsize': 8,
0038 'axes.titlesize': 9,
0039 'figure.titlesize': 9,
0040 'figure.figsize': (4, 3),
0041 'legend.fontsize': 7,
0042 'legend.loc': 'upper right',
0043 'xtick.labelsize': 8,
0044 'ytick.labelsize': 8,
0045 'xaxis.labellocation': 'right',
0046 'yaxis.labellocation': 'top',
0047 'pgf.rcfonts': False,
0048 })
0049
0050 setup_presentation_style()
0051 #+end_src
0052
0053 * Analysis
0054
0055 ** Input files
0056
0057 #+begin_src jupyter-python :results silent
0058 ELECTRON_BEAM_GAS_GEN=os.environ.get("ELECTRON_BEAM_GAS_GEN", "../beam_gas_ep_10GeV_foam_emin10keV_10Mevt_vtx.hepmc")
0059 ELECTRON_BEAM_GAS_SIM=os.environ.get("ELECTRON_BEAM_GAS_SIM", "electron_beam_gas.edm4hep.root")
0060 PHYSICS_PROCESS_SIM=os.environ.get("PHYSICS_PROCESS_SIM", "pythia8NCDIS_10x100_minQ2=1_beamEffects_xAngle=-0.025_hiDiv_1.edm4hep.root")
0061 PROTON_BEAM_GAS_GEN=os.environ.get("PROTON_BEAM_GAS_GEN", "100GeV.hepmc")
0062 PROTON_BEAM_GAS_SIM=os.environ.get("PROTON_BEAM_GAS_SIM", "proton+beam_gas_ep.edm4hep.root")
0063
0064 output_dir=Path(os.environ.get("OUTPUT_DIR", "./"))
0065 output_dir.mkdir(parents=True, exist_ok=True)
0066 #+end_src
0067
0068 #+begin_src jupyter-python :results silent
0069 builder = ak.ArrayBuilder()
0070 n = 0
0071 f = HepMC3.ReaderPlugin(PROTON_BEAM_GAS_GEN, "libHepMC3rootIO.so", "newReaderRootTreefile")
0072 event = HepMC3.GenEvent()
0073 while f.read_event(event):
0074 builder.begin_list()
0075 assert event.length_unit().name == "MM"
0076 for vertex in event.vertices():
0077 with builder.record("Vector4D"):
0078 builder.field("x")
0079 builder.real(vertex.position().x())
0080 builder.field("y")
0081 builder.real(vertex.position().y())
0082 builder.field("z")
0083 builder.real(vertex.position().z())
0084 builder.field("t")
0085 builder.real(vertex.position().t())
0086 builder.end_list()
0087 n += 1
0088 if n > 10000: break
0089
0090 vertices_proton_beam_gas = builder.snapshot()
0091
0092 builder = ak.ArrayBuilder()
0093 n = 0
0094 f = HepMC3.ReaderPlugin(ELECTRON_BEAM_GAS_GEN, "libHepMC3rootIO.so", "newReaderRootTreefile")
0095 event = HepMC3.GenEvent()
0096 while f.read_event(event):
0097 builder.begin_list()
0098 assert event.length_unit().name == "MM"
0099 for vertex in event.vertices():
0100 with builder.record("Vector4D"):
0101 builder.field("x")
0102 builder.real(vertex.position().x())
0103 builder.field("y")
0104 builder.real(vertex.position().y())
0105 builder.field("z")
0106 builder.real(vertex.position().z())
0107 builder.field("t")
0108 builder.real(vertex.position().t())
0109 builder.end_list()
0110 n += 1
0111 if n > 10000: break
0112
0113 vertices_electron_beam_gas = builder.snapshot()
0114 #+end_src
0115
0116 #+begin_src jupyter-python :results silent
0117 def filter_name(name):
0118 return "Hits." in name or "MCParticles." in name
0119
0120 datasets = {
0121 # https://wiki.bnl.gov/EPIC/index.php?title=Electron_Beam_Gas
0122 "electron beam gas 10 GeV": {
0123 "vertices": vertices_electron_beam_gas,
0124 "events": uproot.dask({ELECTRON_BEAM_GAS_SIM: "events"}, filter_name=filter_name, open_files=False, steps_per_file=32),
0125 "cmap": "cool",
0126 "rate": 3.2e6, # Hz
0127 },
0128 "DIS 10x100, $Q^2 > 1$ GeV${}^2$": {
0129 "events": uproot.dask({PHYSICS_PROCESS_SIM: "events"}, filter_name=filter_name, open_files=False, steps_per_file=32),
0130 "rate": 184e3, # Hz
0131 },
0132 # https://wiki.bnl.gov/EPIC/index.php?title=Hadron_Beam_Gas
0133 "proton beam gas 100 GeV": {
0134 "vertices": vertices_proton_beam_gas,
0135 "events": uproot.dask({PROTON_BEAM_GAS_SIM: "events"}, filter_name=filter_name, open_files=False, steps_per_file=32),
0136 "cmap": "summer",
0137 "rate": 31.9e3, # Hz
0138 },
0139 }
0140
0141 for ds in datasets.values():
0142 ds["events"].eager_compute_divisions()
0143 #+end_src
0144
0145 ** Vertex distributions
0146
0147 #+begin_src jupyter-python
0148 for label, ds in datasets.items():
0149 if "vertices" not in ds: continue
0150 vs = ds["vertices"]
0151 weight = ds["rate"] / ak.num(ds["vertices"], axis=0)
0152 plt.hist(ak.ravel(vs.t[:,0]), bins=100, histtype="step", label=label)
0153 plt.minorticks_on()
0154 plt.xlabel("vertex[0].t, mm")
0155 plt.legend()
0156 plt.savefig(output_dir / "vertex_time_distribution.png", bbox_inches="tight")
0157 plt.show()
0158 plt.clf()
0159
0160 for label, ds in datasets.items():
0161 if "vertices" not in ds: continue
0162 vs = ds["vertices"]
0163 weight = ds["rate"] / ak.num(ds["vertices"], axis=0)
0164 plt.hist(ak.ravel(vs.z[:,0]), bins=100, histtype="step", label=label)
0165 plt.minorticks_on()
0166 plt.xlabel("vertex[0].z, mm")
0167 plt.legend()
0168 plt.savefig(output_dir / "vertex_z_distribution.png", bbox_inches="tight")
0169 plt.show()
0170 plt.clf()
0171
0172 for label, ds in datasets.items():
0173 if "vertices" not in ds: continue
0174 vs = ds["vertices"]
0175 cmap = ds["cmap"]
0176 weight = ds["rate"] / ak.num(ds["vertices"], axis=0)
0177 plt.hist2d(vs.z[:,0].to_numpy(), vs.x[:,0].to_numpy(), bins=(100, np.linspace(-130, 130, 160)), cmin=1e-30, label=label, cmap=cmap)
0178 plt.plot([], color=mpl.colormaps[cmap](0.5), label=label)
0179 plt.minorticks_on()
0180 plt.xlabel("vertex[0].z, mm")
0181 plt.ylabel("vertex[0].x, mm")
0182 plt.legend()
0183 plt.savefig(output_dir / "vertex_xz_distribution.png", bbox_inches="tight")
0184 plt.show()
0185 plt.clf()
0186
0187 for ix, (label, ds) in enumerate(datasets.items()):
0188 if "vertices" not in ds: continue
0189 vs = ds["vertices"]
0190 cmap = ds["cmap"]
0191 weight = ds["rate"] / ak.num(ds["vertices"], axis=0)
0192 plt.hist2d(vs.z[:,0].to_numpy(), vs.y[:,0].to_numpy(), bins=(100, 100), cmin=1e-30, cmap=cmap)
0193 plt.colorbar()
0194 plt.minorticks_on()
0195 plt.xlabel("vertex[0].z, mm")
0196 plt.ylabel("vertex[0].y, mm")
0197 plt.title(label)
0198 plt.savefig(output_dir / f"vertex_yz_distribution_{ix}.png", bbox_inches="tight")
0199 plt.show()
0200 plt.clf()
0201 #+end_src
0202
0203 ** Simulation results
0204
0205 #+begin_src jupyter-python
0206 for collection_name in ["EcalEndcapNHits", "EcalEndcapPHits"]:
0207 for dataset_ix, (label, ds) in enumerate(datasets.items()):
0208 events = ds["events"]
0209
0210 energy_sums = ak.sum(events[f"{collection_name}.energy"].head(10000), axis=1)
0211 event_id = ak.argmax(energy_sums)
0212 xs = events[f"{collection_name}.position.x"].head(event_id + 1)[event_id].to_numpy()
0213 ys = events[f"{collection_name}.position.y"].head(event_id + 1)[event_id].to_numpy()
0214
0215 bin_widths = [None, None]
0216 for ix, vals in enumerate([xs, ys]):
0217 centers = np.unique(vals)
0218 diffs = centers[1:] - centers[:-1]
0219 EPSILON = 1e-5
0220 bin_widths[ix] = np.min(diffs[diffs > EPSILON]) if np.sum(diffs > EPSILON) > 0 else 1.
0221 print(f"bin_widths[{ix}]", bin_widths[ix])
0222
0223 bins = {
0224 "EcalEndcapNHits": [np.arange(-750., 750., bin_width) for bin_width in bin_widths],
0225 "EcalEndcapPHits": [np.arange(-1800., 1800., bin_width) for bin_width in bin_widths],
0226 }[collection_name]
0227
0228 plt.hist2d(
0229 xs,
0230 ys,
0231 weights=events[f"{collection_name}.energy"].head(event_id + 1)[event_id].to_numpy(),
0232 bins=bins,
0233 cmin=1e-10,
0234 )
0235 plt.colorbar().set_label("energy, GeV", loc="top")
0236 plt.title(f"{label}, event_id={event_id}\n{collection_name}")
0237 plt.xlabel("hit x, mm")
0238 plt.ylabel("hit y, mm")
0239 plt.savefig(output_dir / f"{collection_name}_event_display_{dataset_ix}.png", bbox_inches="tight")
0240 plt.show()
0241 plt.clf()
0242 #+end_src
0243
0244 ** Discovering number of cells
0245
0246 Using HyperLogLog algorithm would be faster here, or actually load
0247 DD4hep geometry and count sensitive volumes.
0248
0249 #+begin_src jupyter-python
0250 def unique(array):
0251 if ak.backend(array) == "typetracer":
0252 ak.typetracer.touch_data(array)
0253 return array
0254 return ak.from_numpy(np.unique(ak.to_numpy(ak.ravel(array))))
0255 unique_delayed = dask.delayed(unique)
0256 len_delayed = dask.delayed(len)
0257
0258 cellID_for_r = dict()
0259
0260 for collection_name in ["EcalEndcapNHits", "EcalEndcapPHits"]:
0261 r_axis = {
0262 "EcalEndcapNHits": bh.axis.Regular(75, 0., 750.),
0263 "EcalEndcapPHits": bh.axis.Regular(90, 0., 1800.),
0264 }[collection_name]
0265 ds = datasets["DIS 10x100, $Q^2 > 1$ GeV${}^2$"]
0266 events = ds["events"]
0267
0268 r = np.hypot(
0269 ak.ravel(events[f"{collection_name}.position.x"]),
0270 ak.ravel(events[f"{collection_name}.position.y"]),
0271 )
0272 cellID = ak.ravel(events[f"{collection_name}.cellID"])
0273
0274 cellID_for_r[collection_name] = np.array(client.gather(client.compute([
0275 len_delayed(unique_delayed(
0276 cellID[(r >= r_min) & (r < r_max)].map_partitions(unique)
0277 ))
0278 for r_min, r_max in zip(r_axis.edges[:-1], r_axis.edges[1:])
0279 ])))
0280
0281 print(cellID_for_r[collection_name])
0282 print(sum(cellID_for_r[collection_name]))
0283
0284 plt.stairs(
0285 cellID_for_r[collection_name],
0286 r_axis.edges,
0287 )
0288
0289 plt.title(f"{collection_name}")
0290 plt.legend()
0291 plt.xlabel("r, mm")
0292 dr = (r_axis.edges[1] - r_axis.edges[0])
0293 plt.ylabel(f"Number of towers per {dr} mm slice in $r$")
0294 plt.savefig(output_dir / f"{collection_name}_num_towers.png", bbox_inches="tight")
0295 plt.show()
0296 plt.clf()
0297 #+end_src
0298
0299 ** Plotting the rates
0300
0301 #+begin_src jupyter-python
0302 for collection_name in ["EcalEndcapNHits", "EcalEndcapPHits"]:
0303 r_axis = {
0304 "EcalEndcapNHits": bh.axis.Regular(75, 0., 750.),
0305 "EcalEndcapPHits": bh.axis.Regular(90, 0., 1800.),
0306 }[collection_name]
0307 for edep_min in [0.005, 0.015, 0.050]: # GeV
0308 for label, ds in datasets.items():
0309 events = ds["events"]
0310 weight = ds["rate"] / len(events)
0311
0312 r = np.hypot(
0313 ak.ravel(events[f"{collection_name}.position.x"]),
0314 ak.ravel(events[f"{collection_name}.position.y"]),
0315 )
0316 edep = ak.ravel(events[f"{collection_name}.energy"])
0317 r = r[edep > edep_min]
0318
0319 hist = dh.factory(
0320 r,
0321 axes=(r_axis,),
0322 ).compute()
0323 plt.stairs(
0324 hist.values() * weight / cellID_for_r[collection_name],
0325 hist.axes[0].edges,
0326 label=f"{label}",
0327 )
0328
0329 plt.title(f"for $E_{{dep.}} >$ {edep_min * 1000} MeV\n{collection_name}")
0330 plt.legend()
0331 plt.xlabel("r, mm")
0332 plt.ylabel("rate per tower, Hz")
0333 plt.yscale("log")
0334 plt.savefig(output_dir / f"{collection_name}_hit_rate_vs_r_edep_min_{edep_min:.3f}.png", bbox_inches="tight")
0335 plt.show()
0336 plt.clf()
0337 #+end_src
0338
0339 #+begin_src jupyter-python
0340 for collection_name in ["EcalEndcapNHits", "EcalEndcapPHits"]:
0341 for totedep_min in [-1, 0, 0.1, 0.5, 1.0, 5.0, 10.]: # GeV
0342 for label, ds in datasets.items():
0343 events = ds["events"]
0344 weight = ds["rate"] / len(events)
0345
0346 z = ds["events"]["MCParticles.vertex.z"][:,1]
0347 totedep = ak.sum(events[f"{collection_name}.energy"], axis=1)
0348 z = z[totedep > totedep_min]
0349
0350 hist = dh.factory(
0351 z,
0352 axes=(bh.axis.Regular(250, -7500., 17500.),),
0353 ).compute()
0354 plt.stairs(
0355 hist.values() * weight,
0356 hist.axes[0].edges,
0357 label=f"{label}",
0358 )
0359
0360 plt.title(rf"for events with $E_{{\mathrm{{dep. tot.}}}}$ $>$ {totedep_min} GeV" + f"\n{collection_name}")
0361 plt.legend()
0362 plt.xlabel("$z$ of the first interaction vertex, mm")
0363 plt.ylabel("rate, Hz")
0364 plt.yscale("log")
0365 plt.savefig(output_dir / f"{collection_name}_hit_rate_vs_z_totedep_min_{totedep_min:.1f}.png", bbox_inches="tight")
0366 plt.show()
0367 plt.clf()
0368 #+end_src
0369
0370 #+begin_src jupyter-python
0371 num_towers_cache = {
0372 "LumiSpecCAL": 200,
0373 "LumiDirectPCAL": 1,
0374 "ZDCHcal": 1470,
0375 "LFHCAL": 578338,
0376 "ZDC_WSi_": 187043,
0377 "EcalBarrelScFi": 124205483,
0378 "EcalEndcapP": 15037,
0379 "ZDCEcal": 400,
0380 "EcalEndcapPInsert": 536,
0381 "HcalEndcapPInsert": 20251,
0382 "B0ECal": 131,
0383 "HcalEndcapN": 13800,
0384 "HcalBarrel": 7680,
0385 "EcalBarrelImaging": 5765469,
0386 "EcalEndcapN": 2988,
0387 "ZDC_PbSi_": 44344,
0388 }
0389
0390 fig_cmb = plt.figure()
0391 ax_cmb = fig_cmb.gca()
0392
0393 for edep_min in [0]: # GeV
0394 for dataset_ix, (x_offset, (ds_label, ds)) in enumerate(zip(np.linspace(-0.3, 0.3, len(datasets)), datasets.items())):
0395 events = ds["events"]
0396 weight = ds["rate"] / len(events)
0397
0398 labels = []
0399 values = []
0400 norms = []
0401
0402 for branch_name in events.fields:
0403 if ".energy" not in branch_name: continue
0404 if "ZDC_SiliconPix_Hits" in branch_name: continue
0405
0406 edep = ak.ravel(events[branch_name])
0407
0408 #cellID = ak.ravel(events[branch_name.replace(".energy", ".cellID")])
0409 #num_towers = len(unique_delayed(
0410 # cellID.map_partitions(unique)
0411 #).compute())
0412
0413 key = branch_name.replace("Hits.energy", "")
0414 if key not in num_towers_cache:
0415 print(f"The \"{key}\" not in num_towers_cache. Skipping.")
0416 continue
0417 num_towers = num_towers_cache[key]
0418
0419 labels.append(branch_name.replace("Hits.energy", ""))
0420 values.append(ak.count(edep[edep > edep_min]))
0421 norms.append(num_towers if num_towers != 0 else np.nan)
0422
0423 fig_cur = plt.figure()
0424 ax_cur = fig_cur.gca()
0425
0426 values, = dask.compute(values)
0427 for ax, per_tower, offset, width in [
0428 (ax_cmb, True, x_offset, 2 * 0.3 / (len(datasets) - 1)),
0429 (ax_cur, False, 0, 2 * 0.3),
0430 ]:
0431 ax.bar(
0432 np.arange(len(labels)) + offset,
0433 weight * np.array(values) / (np.array(norms) if per_tower else np.ones_like(norms)),
0434 width=width,
0435 tick_label=labels,
0436 label=ds_label,
0437 color=f"C{dataset_ix}",
0438 )
0439
0440 plt.sca(ax_cur)
0441 plt.legend()
0442 plt.title(f"for $E_{{dep.}} >$ {edep_min * 1000} MeV")
0443 plt.ylabel("rate, Hz")
0444 plt.yscale("log")
0445 plt.xticks(rotation=90, ha='right')
0446 fig_cur.savefig(f"rates_edep_min_{edep_min}_{dataset_ix}.png", bbox_inches="tight")
0447 plt.show()
0448 plt.close(fig_cur)
0449
0450 plt.sca(ax_cmb)
0451 plt.legend()
0452 plt.title(f"for $E_{{dep.}} >$ {edep_min * 1000} MeV")
0453 plt.ylabel("rate per tower, Hz")
0454 plt.yscale("log")
0455 plt.xticks(rotation=90, ha='right')
0456 fig_cmb.savefig(f"rates_edep_min_{edep_min}.png", bbox_inches="tight")
0457 plt.show()
0458 plt.clf()
0459 #+end_src