diff --git a/.gitignore b/.gitignore index fc58c11b6a1c796c31b5ed56353db1397376dbfd..bf34adef643c1ba6e6e8892cebb799bc3fe6d452 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ __pycache__/ calorimeters/test/ *.d *.pcm + +# org2py output +benchmarks/backgrounds/ecal_backwards.py +benchmarks/ecal_gaps/ecal_gaps.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dae3f2f8c08608471ba57861454329f73acb852c..0e150857e4cfe142bc65ffe96f5908cfa3b7415a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -129,6 +129,7 @@ get_data: include: # - local: 'benchmarks/backgrounds/config.yml' + - local: 'benchmarks/ecal_gaps/config.yml' - local: 'benchmarks/tracking_detectors/config.yml' - local: 'benchmarks/barrel_ecal/config.yml' - local: 'benchmarks/barrel_hcal/config.yml' diff --git a/Snakefile b/Snakefile index 6e4bc637541586d47bdc273cdcd73ee880f55500..d72b69527ac204b11a7382d7056b019c9267dc4e 100644 --- a/Snakefile +++ b/Snakefile @@ -19,6 +19,7 @@ else: include: "benchmarks/backgrounds/Snakefile" include: "benchmarks/barrel_ecal/Snakefile" +include: "benchmarks/ecal_gaps/Snakefile" rule matplotlibrc: output: @@ -28,3 +29,15 @@ rule matplotlibrc: fp.write("backend: Agg\n") # interactive mode prevents plt.show() from blocking fp.write("interactive : True\n") + + +rule org2py: + input: + notebook=workflow.basedir + "/{NOTEBOOK}.org", + converter=workflow.source_path("benchmarks/common/org2py.awk"), + output: + "{NOTEBOOK}.py" + shell: + """ +awk -f {input.converter} {input.notebook} > {output} +""" diff --git a/benchmarks/backgrounds/Snakefile b/benchmarks/backgrounds/Snakefile index 3e3b1c664edca493155d440fa39b41863ddfea62..f89910973fc2e77dafe34cd2092999c446c5f933 100644 --- a/benchmarks/backgrounds/Snakefile +++ b/benchmarks/backgrounds/Snakefile @@ -36,9 +36,9 @@ rule backgrounds_sim: input: "input/backgrounds/{NAME}.hepmc", output: - "sim_output/{DETECTOR_CONFIG}/{NAME}.edm4hep.root", + "sim_output/{DETECTOR_CONFIG}/backgrounds/{NAME}.edm4hep.root", log: - "sim_output/{DETECTOR_CONFIG}/{NAME}.edm4hep.root.log", + "sim_output/{DETECTOR_CONFIG}/backgrounds/{NAME}.edm4hep.root.log", params: N_EVENTS=100 shell: @@ -55,28 +55,17 @@ ddsim \ """ -rule backgrounds_org2py: - input: - notebook=workflow.source_path("ecal_backwards.org"), - converter=workflow.source_path("./org2py.awk"), - output: - "ecal_backwards.py" - shell: - """ -awk -f {input.converter} {input.notebook} > {output} -""" - DETECTOR_CONFIG=os.environ["DETECTOR_CONFIG"] rule backgrounds_ecal_backwards: input: matplotlibrc=".matplotlibrc", - script="ecal_backwards.py", + script="benchmarks/backgrounds/ecal_backwards.py", electron_beam_gas_gen="input/backgrounds/beam_gas_electron.hepmc", - electron_beam_gas_sim="sim_output/" + DETECTOR_CONFIG + "/beam_gas_electron.edm4hep.root", - physics_signal_sim="sim_output/" + DETECTOR_CONFIG + "/pythia8NCDIS_10x100_minQ2=1_beamEffects_xAngle=-0.025_hiDiv_1.edm4hep.root", + electron_beam_gas_sim="sim_output/" + DETECTOR_CONFIG + "/backgrounds/beam_gas_electron.edm4hep.root", + physics_signal_sim="sim_output/" + DETECTOR_CONFIG + "/backgrounds/pythia8NCDIS_10x100_minQ2=1_beamEffects_xAngle=-0.025_hiDiv_1.edm4hep.root", proton_beam_gas_gen="input/backgrounds/beam_gas_proton.hepmc", - proton_beam_gas_sim="sim_output/" + DETECTOR_CONFIG + "/beam_gas_proton.edm4hep.root", + proton_beam_gas_sim="sim_output/" + DETECTOR_CONFIG + "/backgrounds/beam_gas_proton.edm4hep.root", output: directory("results/backgrounds/backwards_ecal") threads: workflow.cores diff --git a/benchmarks/backgrounds/config.yml b/benchmarks/backgrounds/config.yml index 84bbc7e0ad029fcc7bbe6d23ef7d74edf15b4a99..acf45c56975c39fa54343983207a4f563b8de1ae 100644 --- a/benchmarks/backgrounds/config.yml +++ b/benchmarks/backgrounds/config.yml @@ -2,7 +2,7 @@ sim:backgrounds: extends: .det_benchmark stage: simulate script: - - mkdir $LOCAL_DATA_PATH/input + - mkdir -p $LOCAL_DATA_PATH/input - ln -s $LOCAL_DATA_PATH/input input - snakemake --cores 2 sim_output/$DETECTOR_CONFIG/beam_gas_{electron,proton}.edm4hep.root sim_output/$DETECTOR_CONFIG/pythia8NCDIS_10x100_minQ2=1_beamEffects_xAngle=-0.025_hiDiv_vtxfix_1.edm4hep.root diff --git a/benchmarks/backgrounds/org2py.awk b/benchmarks/common/org2py.awk similarity index 100% rename from benchmarks/backgrounds/org2py.awk rename to benchmarks/common/org2py.awk diff --git a/benchmarks/ecal_gaps/Snakefile b/benchmarks/ecal_gaps/Snakefile new file mode 100644 index 0000000000000000000000000000000000000000..2cf76ec8c55d532100eeb31a697d93d617593e3f --- /dev/null +++ b/benchmarks/ecal_gaps/Snakefile @@ -0,0 +1,84 @@ +import os + + +rule ecal_gaps_sim: + input: + steering_file=provider.remote(remote_path("EPIC/EVGEN/SINGLE/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.steer")), + output: + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root", + log: + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root.log", + wildcard_constraints: + PARTICLE="e-", + ENERGY="(500MeV|5GeV|20GeV)", + PHASE_SPACE="(3to50|45to135|130to177)deg", + INDEX="\d{4}", + params: + N_EVENTS=1000 + shell: + """ +ddsim \ + --runType batch \ + --enableGun \ + --steeringFile "{input.steering_file}" \ + --random.seed 1{wildcards.INDEX} \ + --filter.tracker edep0 \ + -v WARNING \ + --numberOfEvents {params.N_EVENTS} \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --outputFile {output} +""" + + +rule ecal_gaps_recon: + input: + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root", + output: + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.eicrecon.tree.edm4eic.root", + log: + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.eicrecon.tree.edm4eic.root.log", + wildcard_constraints: + INDEX="\d{4}", + shell: """ +eicrecon {input} -Ppodio:output_file={output} \ + -Ppodio:output_include_collections=EcalEndcapNRecHits,EcalBarrelScFiRecHits,EcalBarrelImagingRecHits,EcalEndcapPRecHits,MCParticles +""" + + +DETECTOR_CONFIG=os.environ["DETECTOR_CONFIG"] + +rule ecal_gaps: + input: + matplotlibrc=".matplotlibrc", + script="benchmarks/ecal_gaps/ecal_gaps.py", + # TODO pass as a file list? + _=expand( + "sim_output/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX:04d}.eicrecon.tree.edm4eic.root", + DETECTOR_CONFIG=DETECTOR_CONFIG, + PARTICLE=["e-"], + ENERGY=["500MeV", "5GeV", "20GeV"], + PHASE_SPACE=["3to50deg", "45to135deg", "130to177deg"], + INDEX=range(1), + ), + output: + directory("results/ecal_gaps"), + threads: workflow.cores + shell: + """ +cleanup() {{ + echo Cleaning up + kill $WORKER_PID $SCHEDULER_PID +}} +trap cleanup EXIT + +PORT=$RANDOM +dask scheduler --port $PORT & +export DASK_SCHEDULER=localhost:$PORT +SCHEDULER_PID=$! +dask worker tcp://$DASK_SCHEDULER --nworkers {threads} --nthreads 1 & +WORKER_PID=$! +env \ +MATPLOTLIBRC={input.matplotlibrc} \ +OUTPUT_DIR={output} \ +python {input.script} +""" diff --git a/benchmarks/ecal_gaps/config.yml b/benchmarks/ecal_gaps/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..e854964e8f7fa31f30731ece8a53c7fa839114cd --- /dev/null +++ b/benchmarks/ecal_gaps/config.yml @@ -0,0 +1,26 @@ +sim:ecal_gaps: + extends: .det_benchmark + stage: simulate + script: + - mkdir -p $LOCAL_DATA_PATH/input + - ln -s $LOCAL_DATA_PATH/input input + - snakemake --cores 10 ecal_gaps --omit-from ecal_gaps + +bench:ecal_gaps: + extends: .det_benchmark + stage: benchmarks + needs: + - ["sim:ecal_gaps"] + script: + - ln -s $LOCAL_DATA_PATH/input input + - export PYTHONUSERBASE=$LOCAL_DATA_PATH/deps + - pip install -r benchmarks/ecal_gaps/requirements.txt + - snakemake --cores 8 ecal_gaps + +collect_results:ecal_gaps: + extends: .det_benchmark + stage: collect + needs: + - "bench:ecal_gaps" + script: + - ls -lrht diff --git a/benchmarks/ecal_gaps/ecal_gaps.org b/benchmarks/ecal_gaps/ecal_gaps.org new file mode 100644 index 0000000000000000000000000000000000000000..23bd47372e42d30ba9af0f91b3a803cc0efbe9a7 --- /dev/null +++ b/benchmarks/ecal_gaps/ecal_gaps.org @@ -0,0 +1,192 @@ +#+PROPERTY: header-args:jupyter-python :session /jpy:localhost#8888:gap :async yes :results drawer :exports both + +#+TITLE: ePIC ECal gap study +#+AUTHOR: Dmitry Kalinkin +#+OPTIONS: d:t + +#+begin_src jupyter-python :results silent +import os +from pathlib import Path + +import awkward as ak +import boost_histogram as bh +import dask +import dask_awkward as dak +import dask_histogram as dh +import numpy as np +import uproot +#+end_src + +#+begin_src jupyter-python :results slient +from dask.distributed import Client +client = Client(os.environ.get("DASK_SCHEDULER", "localhost:8786")) +#+end_src + +* Plotting setup + +#+begin_src jupyter-python :results silent +import matplotlib as mpl +import matplotlib.pyplot as plt + +def setup_presentation_style(): + mpl.rcParams.update(mpl.rcParamsDefault) + plt.style.use('ggplot') + plt.rcParams.update({ + 'axes.labelsize': 8, + 'axes.titlesize': 9, + 'figure.titlesize': 9, + 'figure.figsize': (4, 3), + 'legend.fontsize': 7, + 'xtick.labelsize': 8, + 'ytick.labelsize': 8, + 'pgf.rcfonts': False, + }) + +setup_presentation_style() +#+end_src + +** Settings + +#+begin_src jupyter-python :results silent +DETECTOR_CONFIG=os.environ.get("DETECTOR_CONFIG") + +output_dir=Path(os.environ.get("OUTPUT_DIR", "./")) +output_dir.mkdir(parents=True, exist_ok=True) +#+end_src + +* Analysis + +#+begin_src jupyter-python :results silent +def filter_name(name): + return ( + "PARAMETERS" not in name + and all(coll not in name for coll in ["_intMap", "_floatMap", "_stringMap", "_doubleMap"]) + ) + +import functools + +@functools.cache +def get_events(particle="e-", energy="20GeV", num_files=1): + events = uproot.dask( + {} + | {f"sim_output/{DETECTOR_CONFIG}/{particle}/{energy}/3to50deg/{particle}_{energy}_3to50deg.{INDEX:04d}.eicrecon.tree.edm4eic.root": "events" for INDEX in range(num_files)} + | {f"sim_output/{DETECTOR_CONFIG}/{particle}/{energy}/45to135deg/{particle}_{energy}_45to135deg.{INDEX:04d}.eicrecon.tree.edm4eic.root": "events" for INDEX in range(num_files)} + | {f"sim_output/{DETECTOR_CONFIG}/{particle}/{energy}/130to177deg/{particle}_{energy}_130to177deg.{INDEX:04d}.eicrecon.tree.edm4eic.root": "events" for INDEX in range(num_files)} + , + filter_name=filter_name, open_files=False, steps_per_file=1, + ) + + pt = np.hypot(events["MCParticles.momentum.x"][:,0], events["MCParticles.momentum.y"][:,0]) + theta = np.arctan2(pt, events["MCParticles.momentum.z"][:,0]) + eta = -np.log(np.tan(theta / 2)) + p = np.hypot(pt, events["MCParticles.momentum.z"][:,0]) + + axis_eta = bh.axis.Regular(200, -4, 4) + + hist_norm = dh.factory( + eta, + axes=( + axis_eta, + ), + ).compute() + weight_lut = 1 / hist_norm.values(flow=True) + + def get_weight(array): + if ak.backend(array) == "typetracer": + ak.typetracer.touch_data(array) + return array + return ak.from_numpy(weight_lut[axis_eta.index(ak.to_numpy(array))]) + + weights = eta.map_partitions(get_weight) + + events["p_thrown"] = p + events["eta_thrown"] = eta + events["weights"] = weights + + return events, axis_eta +#+end_src + +#+begin_src jupyter-python +particle = "e-" + +for energy in ["500MeV", "5GeV", "20GeV"]: + events, axis_eta = get_events(particle=particle, energy=energy) + + for calo_name in ["EcalEndcapN", "EcalBarrelScFi", "EcalBarrelImaging", "EcalEndcapP"]: + cond = ak.sum(events[f"{calo_name}RecHits.energy"], axis=1) > 10e-3 # GeV + + hist = dh.factory( + events["eta_thrown"][cond], + (ak.sum(events[f"{calo_name}RecHits.energy"], axis=1) / events["p_thrown"])[cond], + axes=( + axis_eta, + bh.axis.Regular(100, 0.1, 1.1), + ), + weights=events["weights"][cond], + ).compute() + + cmap = plt.get_cmap(name="rainbow", lut=None).copy() + cmap.set_under("none") + + plt.pcolormesh( + hist.axes[0].edges, + hist.axes[1].edges, + hist.values().T, + cmap=cmap, + norm=mpl.colors.LogNorm( + vmin=np.min(hist.values()[hist.values() > 0]), + ), + ) + plt.colorbar() + plt.xlabel(r"$\eta_{thrown}$", loc="right") + plt.ylabel(r"$\sum E_{\mathrm{dep.}} / p_{\mathrm{thrown}}$", loc="top") + plt.title(f"{energy} {particle} in {calo_name}") + plt.minorticks_on() + plt.savefig(output_dir / f"ecal_gap_{particle}_{energy}_{calo_name}.png", bbox_inches="tight") + plt.show() + plt.clf() +#+end_src + +#+begin_src jupyter-python +particle = "e-" + +for energy in ["500MeV", "5GeV", "20GeV"]: + events, axis_eta = get_events(particle=particle, energy=energy) + + calos = ["EcalEndcapN", "EcalBarrelScFi", "EcalEndcapP"] + total_energy = sum([ + ak.sum(events[f"{calo_name}RecHits.energy"], axis=1) + for calo_name in calos + ]) + + hist = dh.factory( + events["eta_thrown"], + total_energy / events["p_thrown"], + axes=( + axis_eta, + bh.axis.Regular(100, 0.0, 1.1), + ), + weights=events["weights"], + ).compute() + + cmap = plt.get_cmap(name="rainbow", lut=None).copy() + cmap.set_under("none") + + plt.pcolormesh( + hist.axes[0].edges, + hist.axes[1].edges, + hist.values().T, + cmap=cmap, + norm=mpl.colors.LogNorm( + vmin=np.min(hist.values()[hist.values() > 0]), + ), + ) + plt.colorbar() + plt.xlabel(r"$\eta_{thrown}$", loc="right") + plt.ylabel(r"$\sum E_{\mathrm{dep.}} / p_{\mathrm{thrown}}$", loc="top") + plt.title(f"{energy} {particle}\n" + "+".join(calos)) + plt.minorticks_on() + plt.savefig(output_dir / f"ecal_gap_{particle}_{energy}_sum_all.png", bbox_inches="tight") + plt.show() + plt.clf() +#+end_src diff --git a/benchmarks/ecal_gaps/requirements.txt b/benchmarks/ecal_gaps/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..738152ddb925982305bb5b02dc9b3a732b13145a --- /dev/null +++ b/benchmarks/ecal_gaps/requirements.txt @@ -0,0 +1,7 @@ +awkward >= 2.4.0 +dask >= 2023 +dask_awkward +dask_histogram +distributed >= 2023 +pyhepmc +uproot >= 5.2.0