diff --git a/.github/workflows/mirror.yaml b/.github/workflows/mirror.yaml index 3af0b171..50003528 100644 --- a/.github/workflows/mirror.yaml +++ b/.github/workflows/mirror.yaml @@ -6,7 +6,7 @@ on: workflow_dispatch: concurrency: - group: mirror + group: mirror-${{ github.event_name }} cancel-in-progress: false jobs: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ad4727c0..f90bb5ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,9 +3,9 @@ image: ${BENCHMARKS_REGISTRY}/${BENCHMARKS_CONTAINER}:${BENCHMARKS_TAG} variables: DETECTOR: epic DETECTOR_CONFIG: epic_craterlake - DETECTOR_REPOSITORYURL: 'https://github.com/eic/epic.git' GITHUB_SHA: '' GITHUB_REPOSITORY: '' + SNAKEMAKE_FLAGS: '--cache' workflow: name: '$PIPELINE_NAME' @@ -89,13 +89,6 @@ common:setup: echo "BENCHMARKS_CONTAINER: ${BENCHMARKS_CONTAINER}" echo "BENCHMARKS_REGISTRY: ${BENCHMARKS_REGISTRY}" - source setup/bin/env.sh && ./setup/bin/install_common.sh - - -common:detector: - stage: initialize - needs: ["common:setup"] - script: - - source .local/bin/env.sh && build_detector.sh - mkdir_local_data_link sim_output - mkdir -p results - mkdir -p config @@ -103,7 +96,7 @@ common:detector: get_data: stage: data_init - needs: ["common:detector"] + needs: ["common:setup"] script: - source .local/bin/env.sh - ln -s "${LOCAL_DATA_PATH}/sim_output" sim_output @@ -112,10 +105,11 @@ get_data: .det_benchmark: needs: - - ["get_data","common:detector"] + - ["get_data","common:setup"] before_script: - mc config host add S3 https://eics3.sdcc.bnl.gov:9000 ${S3_ACCESS_KEY} ${S3_SECRET_KEY} - source .local/bin/env.sh + - source /opt/detector/epic-main/setup.sh - ls -lrtha - ln -s "${LOCAL_DATA_PATH}/sim_output" sim_output - ln -s "${LOCAL_DATA_PATH}/datasets/data" data @@ -139,14 +133,24 @@ include: - local: 'benchmarks/tracking_performances_dis/config.yml' - local: 'benchmarks/barrel_ecal/config.yml' - local: 'benchmarks/barrel_hcal/config.yml' + - local: 'benchmarks/lfhcal/config.yml' - local: 'benchmarks/zdc/config.yml' - local: 'benchmarks/zdc_lyso/config.yml' - local: 'benchmarks/zdc_neutron/config.yml' + - local: 'benchmarks/zdc_photon/config.yml' + - local: 'benchmarks/zdc_pi0/config.yml' - local: 'benchmarks/material_scan/config.yml' - local: 'benchmarks/pid/config.yml' - local: 'benchmarks/timing/config.yml' - local: 'benchmarks/b0_tracker/config.yml' - + - local: 'benchmarks/insert_muon/config.yml' + - local: 'benchmarks/insert_tau/config.yml' + - local: 'benchmarks/zdc_sigma/config.yml' + - local: 'benchmarks/zdc_lambda/config.yml' + - local: 'benchmarks/insert_neutron/config.yml' + - local: 'benchmarks/femc_electron/config.yml' + - local: 'benchmarks/femc_photon/config.yml' + - local: 'benchmarks/femc_pi0/config.yml' deploy_results: allow_failure: true stage: deploy @@ -155,15 +159,26 @@ deploy_results: - "collect_results:backwards_ecal" - "collect_results:barrel_ecal" - "collect_results:barrel_hcal" + - "collect_results:lfhcal" - "collect_results:ecal_gaps" - "collect_results:material_scan" - "collect_results:pid" - "collect_results:tracking_performance" - "collect_results:tracking_performance_campaigns" + - "collect_results:zdc_sigma" + - "collect_results:zdc_lambda" + - "collect_results:insert_neutron" - "collect_results:tracking_performances_dis" - "collect_results:zdc" - "collect_results:zdc_lyso" - "collect_results:zdc_neutron" + - "collect_results:insert_muon" + - "collect_results:insert_tau" + - "collect_results:zdc_photon" + - "collect_results:zdc_pi0" + - "collect_results:femc_electron" + - "collect_results:femc_photon" + - "collect_results:femc_pi0" script: - echo "deploy results!" - find results -print | sort | tee summary.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..15f050fc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=128'] + - id: check-yaml diff --git a/Snakefile b/Snakefile index 6b1c9f34..e214869a 100644 --- a/Snakefile +++ b/Snakefile @@ -7,8 +7,19 @@ include: "benchmarks/ecal_gaps/Snakefile" include: "benchmarks/material_scan/Snakefile" include: "benchmarks/tracking_performances/Snakefile" include: "benchmarks/tracking_performances_dis/Snakefile" +include: "benchmarks/lfhcal/Snakefile" include: "benchmarks/zdc_lyso/Snakefile" include: "benchmarks/zdc_neutron/Snakefile" +include: "benchmarks/insert_muon/Snakefile" +include: "benchmarks/zdc_lambda/Snakefile" +include: "benchmarks/zdc_photon/Snakefile" +include: "benchmarks/zdc_pi0/Snakefile" +include: "benchmarks/zdc_sigma/Snakefile" +include: "benchmarks/insert_neutron/Snakefile" +include: "benchmarks/insert_tau/Snakefile" +include: "benchmarks/femc_electron/Snakefile" +include: "benchmarks/femc_photon/Snakefile" +include: "benchmarks/femc_pi0/Snakefile" use_s3 = config["remote_provider"].lower() == "s3" use_xrootd = config["remote_provider"].lower() == "xrootd" @@ -26,6 +37,9 @@ def get_remote_path(path): rule fetch_epic: output: filepath="EPIC/{PATH}" + params: + # wildcards are not included in hash for caching, we need to add them as params + PATH=lambda wildcards: wildcards.PATH cache: True retries: 3 shell: """ diff --git a/benchmarks/backgrounds/config.yml b/benchmarks/backgrounds/config.yml index 1f9775d8..79b864bb 100644 --- a/benchmarks/backgrounds/config.yml +++ b/benchmarks/backgrounds/config.yml @@ -5,7 +5,7 @@ sim:backgrounds: - mkdir -p $LOCAL_DATA_PATH/input - ln -s $LOCAL_DATA_PATH/input input - | - snakemake -cache --cores 2 \ + snakemake $SNAKEMAKE_FLAGS --cores 2 \ sim_output/$DETECTOR_CONFIG/backgrounds/EPIC/EVGEN/BACKGROUNDS/BEAMGAS/electron/GETaLM1.0.0-1.0/10GeV/GETaLM1.0.0-1.0_ElectronBeamGas_10GeV_foam_emin10keV_run001.edm4hep.root \ sim_output/$DETECTOR_CONFIG/backgrounds/EPIC/EVGEN/DIS/NC/10x100/minQ2=1/pythia8NCDIS_10x100_minQ2=1_beamEffects_xAngle=-0.025_hiDiv_1.edm4hep.root \ sim_output/$DETECTOR_CONFIG/backgrounds/EPIC/EVGEN/BACKGROUNDS/BEAMGAS/proton/pythia8.306-1.0/100GeV/pythia8.306-1.0_ProtonBeamGas_100GeV_run001.edm4hep.root @@ -19,7 +19,7 @@ bench:backgrounds_emcal_backwards: - ln -s $LOCAL_DATA_PATH/input input - export PYTHONUSERBASE=$LOCAL_DATA_PATH/deps - pip install -r benchmarks/backgrounds/requirements.txt - - snakemake --cores 8 backgrounds_ecal_backwards + - snakemake $SNAKEMAKE_FLAGS --cores 8 backgrounds_ecal_backwards collect_results:backgrounds: extends: .det_benchmark @@ -29,5 +29,5 @@ collect_results:backgrounds: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output backgrounds_ecal_backwards + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output backgrounds_ecal_backwards - mv results{_save,}/ diff --git a/benchmarks/backwards_ecal/Snakefile b/benchmarks/backwards_ecal/Snakefile index 003d0ab7..0edae3a7 100644 --- a/benchmarks/backwards_ecal/Snakefile +++ b/benchmarks/backwards_ecal/Snakefile @@ -4,11 +4,29 @@ def get_n_events(wildcards): n_events = int(n_events // (energy ** 0.5)) return n_events +import functools +import json +import ctypes.util +import warnings +from snakemake.logging import logger + +@functools.cache +def get_spack_package_hash(package_name): + try: + ver_info = json.loads(subprocess.check_output(["spack", "find", "--json", package_name])) + return ver_info[0]["package_hash"] + except FileNotFoundError as e: + logger.warning("Spack is not installed") + return "" + except subprocess.CalledProcessError as e: + print(e) + return "" rule backwards_ecal_sim: input: steering_file=ancient("EPIC/EVGEN/SINGLE/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.steer"), warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + geometry_lib=os.environ["DETECTOR_PATH"] + "/../../lib/" + ctypes.util.find_library("epic"), output: "sim_output/backwards_ecal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root", log: @@ -19,7 +37,13 @@ rule backwards_ecal_sim: PHASE_SPACE="(3to50|45to135|130to177)deg", INDEX="\d{4}", params: - N_EVENTS=get_n_events + N_EVENTS=get_n_events, + SEED=lambda wildcards: "1" + wildcards.INDEX, + DETECTOR_PATH=os.environ["DETECTOR_PATH"], + DETECTOR_CONFIG=lambda wildcards: wildcards.DETECTOR_CONFIG, + DD4HEP_HASH=get_spack_package_hash("dd4hep"), + NPSIM_HASH=get_spack_package_hash("npsim"), + cache: True shell: """ set -m # monitor mode to prevent lingering processes @@ -27,11 +51,11 @@ exec ddsim \ --runType batch \ --enableGun \ --steeringFile "{input.steering_file}" \ - --random.seed 1{wildcards.INDEX} \ + --random.seed {params.SEED} \ --filter.tracker edep0 \ -v WARNING \ --numberOfEvents {params.N_EVENTS} \ - --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --compactFile {params.DETECTOR_PATH}/{params.DETECTOR_CONFIG}.xml \ --outputFile {output} """ @@ -45,9 +69,13 @@ rule backwards_ecal_recon: "sim_output/backwards_ecal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.eicrecon.tree.edm4eic.root.log", wildcard_constraints: INDEX="\d{4}", + params: + DETECTOR_CONFIG=lambda wildcards: wildcards.DETECTOR_CONFIG, + EICRECON_HASH=get_spack_package_hash("eicrecon"), + cache: True shell: """ set -m # monitor mode to prevent lingering processes -exec env DETECTOR_CONFIG={wildcards.DETECTOR_CONFIG} \ +exec env DETECTOR_CONFIG={params.DETECTOR_CONFIG} \ eicrecon {input} -Ppodio:output_file={output} \ -Ppodio:output_collections=MCParticles,EcalEndcapNRecHits,EcalEndcapNClusters """ diff --git a/benchmarks/backwards_ecal/backwards_ecal.org b/benchmarks/backwards_ecal/backwards_ecal.org index d3ffca5e..27f1094c 100644 --- a/benchmarks/backwards_ecal/backwards_ecal.org +++ b/benchmarks/backwards_ecal/backwards_ecal.org @@ -113,6 +113,124 @@ for energy in energies: )) #+end_src +** Energy resolution + +#+begin_src jupyter-python +fig, axs = plt.subplots(2, 4, sharex=True, sharey=True, figsize=(15, 6)) + +fig.suptitle(PLOT_TITLE) + +axs = np.ravel(np.array(axs)) + +sigmas_rel_FWHM_cb = {} +fractions_below = {} + +for ix, energy in enumerate(energies): + for use_clusters in [False, True]: + energy_value = float(energy.replace("GeV", "").replace("MeV", "e-3")) + if use_clusters: + clf_label = "leading cluster" + else: + clf_label = "sum all hits" + def clf(events): + if use_clusters: + return ak.drop_none(ak.max(events["EcalEndcapNClusters.energy"], axis=-1)) / energy_value + else: + return ak.sum(events["EcalEndcapNRecHits.energy"], axis=-1) / energy_value + e_pred = clf(e_eval[energy]) + + plt.sca(axs[ix]) + counts, bins, patches = plt.hist(e_pred, weights=np.full_like(e_pred, 1.0 / ak.num(e_pred, axis=0)), bins=np.linspace(0.01, 1.01, 101), label=rf"$e^-$ {clf_label}", hatch=None if use_clusters else r"xxx", alpha=0.8 if use_clusters else 1.) + plt.title(f"{energy}") + + e_over_p = (bins[1:] + bins[:-1]) / 2 + import scipy.stats + def f(x, n, beta, m, loc, scale): + return n * scipy.stats.crystalball.pdf(x, beta, m, loc, scale) + p0 = (np.sum(counts[10:]), 2., 3., 0.95, 0.05) + + try: + import scipy.optimize + par, pcov = scipy.optimize.curve_fit(f, e_over_p[5:], counts[5:], p0=p0, maxfev=10000) + except RuntimeError: + par = None + plt.plot(e_over_p, f(e_over_p, *par), label=rf"Crystal Ball fit", color="tab:green" if use_clusters else "green", lw=0.8) + + def summarize_fit(par): + _, _, _, loc_cb, scale_cb = par + # Calculate FWHM + y_max = np.max(f(np.linspace(0., 1., 100), *par)) + f_prime = lambda x: f(x, *par) - y_max / 2 + x_plus, = scipy.optimize.root(f_prime, loc_cb + scale_cb).x + x_minus, = scipy.optimize.root(f_prime, loc_cb - scale_cb).x + color = "cyan" if use_clusters else "orange" + plt.axvline(x_minus, ls="--", lw=0.75, color=patches[0].get_facecolor(), label=r"$\mu - $FWHM") + plt.axvline(x_plus, ls=":", lw=0.75, color=patches[0].get_facecolor(), label=r"$\mu + $FWHM") + fwhm = (x_plus - x_minus) / loc_cb + sigma_rel_FWHM_cb = fwhm / 2 / np.sqrt(2 * np.log(2)) + + cutoff_x = loc_cb - fwhm + fraction_below = np.sum(counts[e_over_p < cutoff_x]) / ak.num(e_pred, axis=0) + + return sigma_rel_FWHM_cb, fraction_below + + sigma_rel_FWHM_cb, fraction_below = summarize_fit(par) + sigmas_rel_FWHM_cb.setdefault(clf_label, {})[energy] = sigma_rel_FWHM_cb + fractions_below.setdefault(clf_label, {})[energy] = fraction_below + + plt.legend() + plt.xlabel("$E/p$", loc="right") + plt.ylabel("Event yield", loc="top") + +fig.savefig(output_dir / f"resolution_plots.pdf", bbox_inches="tight") +fig.savefig(output_dir / f"resolution_plots.png", bbox_inches="tight") +plt.show() +plt.close(fig) + +plt.figure() +energy_values = np.array([float(energy.replace("GeV", "").replace("MeV", "e-3")) for energy in energies]) + +for clf_label, sigma_rel_FWHM_cb in sigmas_rel_FWHM_cb.items(): + sigma_over_e = np.array([sigma_rel_FWHM_cb[energy] for energy in energies]) * 100 # convert to % + + def f(energy, stochastic, constant): + return np.sqrt((stochastic / np.sqrt(energy)) ** 2 + constant ** 2) + cond = energy_values >= 0.5 + try: + import scipy.optimize + par, pcov = scipy.optimize.curve_fit(f, energy_values[cond], sigma_over_e[cond], maxfev=10000) + except RuntimeError: + par = None + stochastic, constant = par + + plt.plot( + energy_values, + sigma_over_e, + marker=".", + label=f"{clf_label}" + ) + plt.plot( + energy_values[cond], + f(energy_values[cond], *par), + color="black", + ls="--", + lw=0.5, + label=f"{clf_label}, ${np.ceil(stochastic * 10) / 10:.1f}\% / \sqrt{{E}} \oplus {np.ceil(constant * 10) / 10:.1f}\%$", + ) +plt.plot( + energy_values, + np.sqrt((1 / energy_values) ** 2 + (1 / np.sqrt(energy_values)) ** 2 + 1 ** 2), + color="black", label=r"YR requirement $1\% / E \oplus 2.5\% / \sqrt{E} \oplus 1\%$", +) +plt.title(INPUT_PATH_FORMAT) +plt.legend() +plt.xlabel("Energy, GeV", loc="right") +plt.ylabel(r"$\sigma_{E} / E$ derived from FWHM, %", loc="top") +plt.savefig(output_dir / f"resolution.pdf", bbox_inches="tight") +plt.savefig(output_dir / f"resolution.png", bbox_inches="tight") +plt.show() +#+end_src + ** Pion rejection #+begin_src jupyter-python @@ -176,10 +294,13 @@ for ix, energy in enumerate(energies): plt.ylabel("Pion rejection factor") fig.savefig(output_dir / f"pred.pdf", bbox_inches="tight") +fig.savefig(output_dir / f"pred.png", bbox_inches="tight") plt.close(fig) fig_log.savefig(output_dir / f"pred_log.pdf", bbox_inches="tight") +fig_log.savefig(output_dir / f"pred_log.png", bbox_inches="tight") fig_log.show() fig_roc.savefig(output_dir / f"roc.pdf", bbox_inches="tight") +fig_roc.savefig(output_dir / f"roc.png", bbox_inches="tight") fig_roc.show() plt.figure() @@ -196,5 +317,6 @@ plt.legend() plt.xlabel("Energy, GeV") plt.ylabel("Pion rejection at 95%") plt.savefig(output_dir / f"pion_rej.pdf", bbox_inches="tight") +plt.savefig(output_dir / f"pion_rej.png", bbox_inches="tight") plt.show() #+end_src diff --git a/benchmarks/backwards_ecal/config.yml b/benchmarks/backwards_ecal/config.yml index 9eff9b58..7048f8e7 100644 --- a/benchmarks/backwards_ecal/config.yml +++ b/benchmarks/backwards_ecal/config.yml @@ -16,7 +16,7 @@ sim:backwards_ecal: ] script: - | - snakemake --cache --cores 5 sim_output/backwards_ecal/${DETECTOR_CONFIG}/${PARTICLE}/${MOMENTUM}/130to177deg/flag + snakemake $SNAKEMAKE_FLAGS --cores 5 sim_output/backwards_ecal/${DETECTOR_CONFIG}/${PARTICLE}/${MOMENTUM}/130to177deg/flag bench:backwards_ecal: extends: .det_benchmark @@ -26,7 +26,7 @@ bench:backwards_ecal: script: - export PYTHONUSERBASE=$LOCAL_DATA_PATH/deps - pip install -r benchmarks/backwards_ecal/requirements.txt - - snakemake --cores 1 backwards_ecal + - snakemake $SNAKEMAKE_FLAGS --cores 1 backwards_ecal collect_results:backwards_ecal: extends: .det_benchmark @@ -36,5 +36,5 @@ collect_results:backwards_ecal: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output backwards_ecal + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output backwards_ecal - mv results{_save,}/ diff --git a/benchmarks/barrel_ecal/config.yml b/benchmarks/barrel_ecal/config.yml index a236f68b..45fc3e1c 100644 --- a/benchmarks/barrel_ecal/config.yml +++ b/benchmarks/barrel_ecal/config.yml @@ -2,13 +2,13 @@ sim:emcal_barrel_pions: extends: .det_benchmark stage: simulate script: - - snakemake --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_{piplus,piminus}_energies5.0_5.0.edm4hep.root + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_{piplus,piminus}_energies5.0_5.0.edm4hep.root sim:emcal_barrel_pi0: extends: .det_benchmark stage: simulate script: - - snakemake --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_pi0_energies5.0_5.0.edm4hep.root + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_pi0_energies5.0_5.0.edm4hep.root sim:emcal_barrel_electrons: extends: .det_benchmark @@ -16,20 +16,20 @@ sim:emcal_barrel_electrons: script: - if [[ "$RUN_EXTENDED_BENCHMARK" == "true" ]] ; then snakemake --cores $DETECTOR_CONFIG/results/energy_scan/emcal_barrel_electron_fsam_scan.png; fi - export JUGGLER_N_EVENTS=400 - - snakemake --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_electron_energies5.0_5.0.edm4hep.root + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_electron_energies5.0_5.0.edm4hep.root sim:emcal_barrel_photons: extends: .det_benchmark stage: simulate script: - if [[ "$RUN_EXTENDED_BENCHMARK" == "true" ]] ; then snakemake --cores $DETECTOR_CONFIG/results/energy_scan/emcal_barrel_proton_fsam_scan.png; fi - - snakemake --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_photon_energies5.0_5.0.edm4hep.root + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_photon_energies5.0_5.0.edm4hep.root sim:emcal_barrel_pion_rejection: extends: .det_benchmark stage: simulate script: - - snakemake --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_{piminus,electron}_energies1.0_18.0.edm4hep.root + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_{piminus,electron}_energies1.0_18.0.edm4hep.root calib:emcal_barrel_electrons: extends: .det_benchmark @@ -39,7 +39,7 @@ calib:emcal_barrel_electrons: script: - ls -lhtR sim_output/ - rootls -t sim_output/sim_emcal_barrel_electron_energies5.0_5.0.edm4hep.root - - snakemake --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_electron_calibration.json + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_electron_calibration.json - mv sim_output/sim_emcal_barrel_electron.edm4hep.root results/. - echo "JSON file(s) from analysis:" ; cat results/*.json @@ -49,7 +49,7 @@ bench:emcal_barrel_pions: needs: - ["sim:emcal_barrel_pions"] script: - - snakemake --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_pions_Ethr.png + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_pions_Ethr.png bench:emcal_barrel_electrons_scan: extends: .det_benchmark @@ -66,7 +66,7 @@ bench:emcal_barrel_pi0: - ["sim:emcal_barrel_pi0", "calib:emcal_barrel_electrons"] script: - echo "JSON file(s) from analysis:" ; cat results/*.json - - snakemake --cores 1 epic_craterlake/results/Barrel_emcal_pi0.json + - snakemake $SNAKEMAKE_FLAGS --cores 1 epic_craterlake/results/Barrel_emcal_pi0.json bench:emcal_barrel_photons: extends: .det_benchmark @@ -76,7 +76,7 @@ bench:emcal_barrel_photons: script: - ls -lhtR sim_output/ - rootls -t sim_output/sim_emcal_barrel_photon_energies5.0_5.0.edm4hep.root - - snakemake --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_photon_calibration.json + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/results/emcal_barrel_photon_calibration.json - mv sim_output/sim_emcal_barrel_photon.edm4hep.root results/. - if [[ "$RUN_EXTENDED_BENCHMARK" == "true" ]] ; then snakemake --cores $DETECTOR_CONFIG/results/energy_scan/emcal_barrel_proton_fsam_scan.png; fi @@ -89,7 +89,7 @@ bench:emcal_barrel_pion_rejection: - ls -lhtR sim_output/ - rootls -t $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_piminus_energies1.0_18.0.edm4hep.root - rootls -t $DETECTOR_CONFIG/sim_output/sim_emcal_barrel_electron_energies1.0_18.0.edm4hep.root - - snakemake --cores 1 $DETECTOR_CONFIG/results/Barrel_emcal_pion_rej.json + - snakemake $SNAKEMAKE_FLAGS --cores 1 $DETECTOR_CONFIG/results/Barrel_emcal_pion_rej.json collect_results:barrel_ecal: extends: .det_benchmark diff --git a/benchmarks/ecal_gaps/config.yml b/benchmarks/ecal_gaps/config.yml index 9d967c87..9c0a8a09 100644 --- a/benchmarks/ecal_gaps/config.yml +++ b/benchmarks/ecal_gaps/config.yml @@ -4,7 +4,7 @@ sim:ecal_gaps: script: - mkdir -p $LOCAL_DATA_PATH/input - ln -s $LOCAL_DATA_PATH/input input - - snakemake --cache --cores 10 ecal_gaps --omit-from ecal_gaps + - snakemake $SNAKEMAKE_FLAGS --cache --cores 10 ecal_gaps --omit-from ecal_gaps bench:ecal_gaps: extends: .det_benchmark @@ -15,7 +15,7 @@ bench:ecal_gaps: - 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 + - snakemake $SNAKEMAKE_FLAGS --cores 8 ecal_gaps collect_results:ecal_gaps: extends: .det_benchmark @@ -25,5 +25,5 @@ collect_results:ecal_gaps: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output ecal_gaps + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output ecal_gaps - mv results{_save,}/ diff --git a/benchmarks/femc_electron/Snakefile b/benchmarks/femc_electron/Snakefile new file mode 100644 index 00000000..13e8b737 --- /dev/null +++ b/benchmarks/femc_electron/Snakefile @@ -0,0 +1,66 @@ +def get_n_events(wildcards): + energy = float(wildcards.P) + n_events = 1000 + n_events = int(n_events // ((energy / 20) ** 0.5)) + return n_events + + +rule femc_electron_generate: + input: + script="benchmarks/femc_electron/analysis/gen_particles.cxx", + params: + N_EVENTS=get_n_events, + th_max=28, + th_min=2.0 + output: + GEN_FILE="sim_output/femc_electron/e-_{P}GeV.hepmc" + shell: + """ +mkdir -p sim_output/femc_electron +root -l -b -q '{input.script}({params.N_EVENTS},"{output.GEN_FILE}", "e-", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule femc_electron_simulate: + input: + GEN_FILE="sim_output/femc_electron/e-_{P}GeV.hepmc" + params: + N_EVENTS=get_n_events, + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_electron/{DETECTOR_CONFIG}_sim_e-_{P}GeV.edm4hep.root" + shell: + """ +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --numberOfEvents {params.N_EVENTS} \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule femc_electron_recon: + input: + SIM_FILE="sim_output/femc_electron/{DETECTOR_CONFIG}_sim_e-_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/femc_electron/{DETECTOR_CONFIG}_rec_e-_{P}GeV.edm4eic.root" + params: + N_EVENTS=get_n_events, + shell: + """ +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalEndcapPInsertRecHits,HcalEndcapPInsertClusters,HcalEndcapPInsertSubcellHits,EcalEndcapPInsertRecHits,EcalEndcapPInsertClusters,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents={params.N_EVENTS} +""" + +rule femc_electron_analysis: + input: + expand("sim_output/femc_electron/{DETECTOR_CONFIG}_rec_e-_{P}GeV.edm4eic.root", + P=[10, 20, 30, 40, 50, 60, 70, 80], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + script="benchmarks/femc_electron/analysis/femc_electron_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/femc_electron"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/femc_electron/analysis/femc_electron_plots.py b/benchmarks/femc_electron/analysis/femc_electron_plots.py new file mode 100644 index 00000000..8979e0c7 --- /dev/null +++ b/benchmarks/femc_electron/analysis/femc_electron_plots.py @@ -0,0 +1,222 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys, uproot as ur +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) + +config=sys.argv[1].split("/")[1] #results/{config}/{benchmark_name} +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +import uproot as ur +arrays_sim={p:ur.open(f'sim_output/femc_electron/{config}_rec_e-_{p}GeV.edm4eic.root:events').arrays() for p in (10, 20, 30, 40, 50, 60,70,80)} + +for p in arrays_sim: + array=arrays_sim[p] + tilt=-.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['nclust_endcap']=[len(array['EcalEndcapPClusters.energy'][i]) for i in range(len(array))] + +for array in arrays_sim.values(): + tilt=-0.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['phi_truth']=np.arctan2(pyp,pxp) + +#number of clusters +plt.figure() +for eta_min, eta_max, field in (1.5, 2.8, 'nclust_endcap'),: + for p in arrays_sim: + array=arrays_sim[p] + plt.hist(array[field][(array['eta_truth']>eta_min)&(array['eta_truth']eta_min)]['EcalEndcapPClusters.energy']), bins=bins) + bcs=(x[1:]+x[:-1])/2 + + fnc=gauss + slc=abs(bcs-p)<3 + sigma=np.sqrt(y[slc])+0.5*(y[slc]==0) + p0=(100, p, 3) + + try: + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + if abs(coeff[1])>100 or np.sqrt(var_matrix[1][1])>100: + continue + pvals.append(p) + res.append(abs(coeff[2])/coeff[1]) + dres.append(np.sqrt(var_matrix[2][2])/coeff[1]) + scale.append(abs(coeff[1])/p) + dscale.append(np.sqrt(var_matrix[1][1])/p) + except: + pass + plt.sca(axs[0]) + plt.errorbar(pvals, 100*np.array(res), 100*np.array(dres), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + + plt.sca(axs[1]) + plt.errorbar(pvals, 100*np.array(scale), 100*np.array(dscale), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + +plt.sca(axs[0]) +plt.legend() +plt.ylim(0) +plt.ylabel("E resolution [%]") +plt.xlabel("E truth [GeV]") + +plt.sca(axs[1]) +plt.ylabel("energy scale [%]") +plt.xlabel("E truth [GeV]") +plt.axhline(100, color='0.5', alpha=0.5, ls='--') +plt.ylim(0, 110) + +plt.tight_layout() +plt.savefig(outdir+"/energy_res_eta_partitions.pdf") diff --git a/benchmarks/femc_electron/analysis/gen_particles.cxx b/benchmarks/femc_electron/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/femc_electron/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/femc_electron/config.yml b/benchmarks/femc_electron/config.yml new file mode 100644 index 00000000..11c8deea --- /dev/null +++ b/benchmarks/femc_electron/config.yml @@ -0,0 +1,37 @@ +sim:femc_electron: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 sim_output/femc_electron/epic_craterlake_rec_e-_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_electron: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:femc_electron"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 results/epic_craterlake/femc_electron + +collect_results:femc_electron: + extends: .det_benchmark + stage: collect + needs: ["bench:femc_electron"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output results/epic_craterlake/femc_electron + - mv results{_save,}/ diff --git a/benchmarks/femc_photon/Snakefile b/benchmarks/femc_photon/Snakefile new file mode 100644 index 00000000..d92a3cd1 --- /dev/null +++ b/benchmarks/femc_photon/Snakefile @@ -0,0 +1,66 @@ +def get_n_events(wildcards): + energy = float(wildcards.P) + n_events = 1000 + n_events = int(n_events // ((energy / 20) ** 0.5)) + return n_events + + +rule femc_photon_generate: + input: + script="benchmarks/femc_photon/analysis/gen_particles.cxx", + params: + N_EVENTS=get_n_events, + th_max=28, + th_min=2.0 + output: + GEN_FILE="sim_output/femc_photon/photon_{P}GeV.hepmc" + shell: + """ +mkdir -p sim_output/femc_photon +root -l -b -q '{input.script}({params.N_EVENTS},"{output.GEN_FILE}", "gamma", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule femc_photon_simulate: + input: + GEN_FILE="sim_output/femc_photon/photon_{P}GeV.hepmc" + params: + N_EVENTS=get_n_events, + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_photon/{DETECTOR_CONFIG}_sim_photon_{P}GeV.edm4hep.root" + shell: + """ +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --numberOfEvents {params.N_EVENTS} \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule femc_photon_recon: + input: + SIM_FILE="sim_output/femc_photon/{DETECTOR_CONFIG}_sim_photon_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/femc_photon/{DETECTOR_CONFIG}_rec_photon_{P}GeV.edm4eic.root" + params: + N_EVENTS=get_n_events, + shell: + """ +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalEndcapPInsertRecHits,HcalEndcapPInsertClusters,HcalEndcapPInsertSubcellHits,EcalEndcapPInsertRecHits,EcalEndcapPInsertClusters,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents={params.N_EVENTS} +""" + +rule femc_photon_analysis: + input: + expand("sim_output/femc_photon/{DETECTOR_CONFIG}_rec_photon_{P}GeV.edm4eic.root", + P=[10, 20, 30, 40, 50, 60, 70, 80], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + script="benchmarks/femc_photon/analysis/femc_photon_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/femc_photon"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/femc_photon/analysis/femc_photon_plots.py b/benchmarks/femc_photon/analysis/femc_photon_plots.py new file mode 100644 index 00000000..176eb663 --- /dev/null +++ b/benchmarks/femc_photon/analysis/femc_photon_plots.py @@ -0,0 +1,220 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys, uproot as ur +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) + +config=sys.argv[1].split("/")[1] #results/{config}/{benchmark_name} +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +import uproot as ur +arrays_sim={p:ur.open(f'sim_output/femc_photon/{config}_rec_photon_{p}GeV.edm4eic.root:events').arrays() for p in (10, 20, 30, 40, 50, 60,70,80)} + +for p in arrays_sim: + array=arrays_sim[p] + tilt=-.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['nclust_endcap']=[len(array['EcalEndcapPClusters.energy'][i]) for i in range(len(array))] + +for array in arrays_sim.values(): + tilt=-0.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['phi_truth']=np.arctan2(pyp,pxp) + +#number of clusters +plt.figure() +for eta_min, eta_max, field in (1.5, 2.8, 'nclust_endcap'),: + for p in arrays_sim: + array=arrays_sim[p] + plt.hist(array[field][(array['eta_truth']>eta_min)&(array['eta_truth']eta_min)]['EcalEndcapPClusters.energy']), bins=bins) + bcs=(x[1:]+x[:-1])/2 + + fnc=gauss + slc=abs(bcs-p)<3 + sigma=np.sqrt(y[slc])+0.5*(y[slc]==0) + p0=(100, p, 3) + try: + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + if abs(coeff[1])>100 or np.sqrt(var_matrix[1][1])>100: + continue + pvals.append(p) + res.append(abs(coeff[2])/coeff[1]) + dres.append(np.sqrt(var_matrix[2][2])/coeff[1]) + scale.append(abs(coeff[1])/p) + dscale.append(np.sqrt(var_matrix[1][1])/p) + except: + pass + plt.sca(axs[0]) + plt.errorbar(pvals, 100*np.array(res), 100*np.array(dres), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + + plt.sca(axs[1]) + plt.errorbar(pvals, 100*np.array(scale), 100*np.array(dscale), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + +plt.sca(axs[0]) +plt.legend() +plt.ylim(0) +plt.ylabel("E resolution [%]") +plt.xlabel("E truth [GeV]") + +plt.sca(axs[1]) +plt.ylabel("energy scale [%]") +plt.xlabel("E truth [GeV]") +plt.axhline(100, color='0.5', alpha=0.5, ls='--') +plt.ylim(0, 110) + +plt.tight_layout() +plt.savefig(outdir+"/energy_res_eta_partitions.pdf") diff --git a/benchmarks/femc_photon/analysis/gen_particles.cxx b/benchmarks/femc_photon/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/femc_photon/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/femc_photon/config.yml b/benchmarks/femc_photon/config.yml new file mode 100644 index 00000000..dca2694e --- /dev/null +++ b/benchmarks/femc_photon/config.yml @@ -0,0 +1,37 @@ +sim:femc_photon: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 sim_output/femc_photon/epic_craterlake_rec_photon_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_photon: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:femc_photon"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 results/epic_craterlake/femc_photon + +collect_results:femc_photon: + extends: .det_benchmark + stage: collect + needs: ["bench:femc_photon"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output results/epic_craterlake/femc_photon + - mv results{_save,}/ diff --git a/benchmarks/femc_pi0/Snakefile b/benchmarks/femc_pi0/Snakefile new file mode 100644 index 00000000..9b8713ab --- /dev/null +++ b/benchmarks/femc_pi0/Snakefile @@ -0,0 +1,66 @@ +def get_n_events(wildcards): + energy = float(wildcards.P) + n_events = 1000 + n_events = int(n_events // ((energy / 20) ** 0.5)) + return n_events + + +rule femc_pi0_generate: + input: + script="benchmarks/femc_pi0/analysis/gen_particles.cxx", + params: + N_EVENTS=get_n_events, + th_max=28, + th_min=2.0 + output: + GEN_FILE="sim_output/femc_pi0/pi0_{P}GeV.hepmc" + shell: + """ +mkdir -p sim_output/femc_pi0 +root -l -b -q '{input.script}({params.N_EVENTS},"{output.GEN_FILE}", "pi0", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule femc_pi0_simulate: + input: + GEN_FILE="sim_output/femc_pi0/pi0_{P}GeV.hepmc" + params: + N_EVENTS=get_n_events, + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_pi0/{DETECTOR_CONFIG}_sim_pi0_{P}GeV.edm4hep.root" + shell: + """ +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --numberOfEvents {params.N_EVENTS} \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule femc_pi0_recon: + input: + SIM_FILE="sim_output/femc_pi0/{DETECTOR_CONFIG}_sim_pi0_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/femc_pi0/{DETECTOR_CONFIG}_rec_pi0_{P}GeV.edm4eic.root" + params: + N_EVENTS=get_n_events, + shell: + """ +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalEndcapPInsertRecHits,HcalEndcapPInsertClusters,HcalEndcapPInsertSubcellHits,EcalEndcapPInsertRecHits,EcalEndcapPInsertClusters,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents={params.N_EVENTS} +""" + +rule femc_pi0_analysis: + input: + expand("sim_output/femc_pi0/{DETECTOR_CONFIG}_rec_pi0_{P}GeV.edm4eic.root", + P=[10, 20, 30, 40, 50, 60, 70, 80], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + script="benchmarks/femc_pi0/analysis/femc_pi0_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/femc_pi0"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/femc_pi0/analysis/femc_pi0_plots.py b/benchmarks/femc_pi0/analysis/femc_pi0_plots.py new file mode 100644 index 00000000..0bdf26e3 --- /dev/null +++ b/benchmarks/femc_pi0/analysis/femc_pi0_plots.py @@ -0,0 +1,257 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys, uproot as ur +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) + +config=sys.argv[1].split("/")[1] #results/{config}/{benchmark_name} +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +import uproot as ur +arrays_sim={p:ur.open(f'sim_output/femc_pi0/{config}_rec_pi0_{p}GeV.edm4eic.root:events').arrays() for p in (10, 20, 30, 40, 50, 60,70,80)} + +for p in arrays_sim: + array=arrays_sim[p] + tilt=-.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['nclust_endcap']=[len(array['EcalEndcapPClusters.energy'][i]) for i in range(len(array))] + +for array in arrays_sim.values(): + tilt=-0.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['phi_truth']=np.arctan2(pyp,pxp) + +#number of clusters +plt.figure() +for eta_min, eta_max, field in (1.5, 2.8, 'nclust_endcap'),: + for p in arrays_sim: + array=arrays_sim[p] + plt.hist(array[field][(array['eta_truth']>eta_min)&(array['eta_truth']eta_min)&(array['eta_truth']=5))/tot) + plt.errorbar(pvals, f0, label="$n_{cl}$=0") + plt.errorbar(pvals, f1, label="$n_{cl}$=1") + plt.errorbar(pvals, f2, label="$n_{cl}$=2") + plt.errorbar(pvals, f3, label="$n_{cl}$=3") + plt.errorbar(pvals, f4, label="$n_{cl}$=4") + plt.errorbar(pvals, f5p, label="$n_{cl}\\geq$5") + plt.legend() + plt.ylabel("fraction of events") + plt.xlabel("$E_{\\pi^0}$ [GeV]") + plt.ylim(0, 1) + plt.legend(fontsize=20, ncol=2) + plt.savefig(outdir+f"/nclust.pdf") + + + +#number of hits per cluster +fig, axs=plt.subplots(1,2, figsize=(16,8)) +avgs=[] +stds=[] +pvals=[] + +for p in arrays_sim: + + a=arrays_sim[p] + n=[] + nn=-a['EcalEndcapPClusters.hits_begin']+a['EcalEndcapPClusters.hits_end'] + E=a['EcalEndcapPClusters.energy'] + for evt in range(len(array)): + if len(E[evt])==0: + continue + maxE=np.max(E[evt]) + found=False + for i in range(len(E[evt])): + if E[evt][i]==maxE: + n.append(nn[evt][i]) + found=True + break + #if not found: + # n.append(0) + + if p ==50: + plt.sca(axs[0]) + y,x,_=plt.hist(n, range=(0,100), bins=100, histtype='step', label=f"E={p} GeV") + plt.ylabel("events") + plt.xlabel("# hits in cluster") + plt.title(f"$\\pi^0$, E={p} GeV") + pvals.append(p) + avgs.append(np.mean(n)) + stds.append(np.std(n)) + +plt.sca(axs[1]) +plt.errorbar(pvals, avgs, stds, marker='o',ls='') +plt.xlabel("E [GeV]") +plt.ylabel("# hits in cluster [mean$\\pm$std]") +plt.ylim(0) +plt.savefig(outdir+"/nhits_per_cluster.pdf") + + +#energy resolution +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) +from scipy.optimize import curve_fit + +fig, axs=plt.subplots(1,3, figsize=(24,8)) +pvals=[] +res=[] +dres=[] +scale=[] +dscale=[] +for p in arrays_sim: + bins=np.linspace(15*p/20,22*p/20, 50) + if p==50: + plt.sca(axs[0]) + plt.title(f"E={p} GeV") + y,x,_=plt.hist(np.sum(arrays_sim[p]['EcalEndcapPClusters.energy'], axis=-1), bins=bins, histtype='step') + plt.ylabel("events") + plt.xlabel("$E^{rec}_{\\pi^0}$ [GeV]") + else: + y,x=np.histogram(np.sum(arrays_sim[p]['EcalEndcapPClusters.energy'], axis=-1), bins=bins) + bcs=(x[1:]+x[:-1])/2 + + fnc=gauss + slc=abs(bcs-p)<3 + sigma=np.sqrt(y[slc])+0.5*(y[slc]==0) + p0=(100, p, 3) + + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + #res=np.abs(coeff[2]/coeff[1]) + if p==50: + xx=np.linspace(15*p/20,22*p/20, 100) + + plt.plot(xx, fnc(xx,*coeff), label=f"$\\sigma_E/E={abs(coeff[2])/coeff[1]*100:.1f}\%$") + plt.axvline(p, color='g', ls='--', alpha=0.7) + plt.legend() + #plt.xlim(0,60) + #plt.show() + pvals.append(p) + res.append(abs(coeff[2])/coeff[1]) + dres.append(np.sqrt(var_matrix[2][2])/coeff[1]) + scale.append(abs(coeff[1])/p) + dscale.append(np.sqrt(var_matrix[1][1])/p) +plt.sca(axs[1]) +plt.errorbar(pvals, 100*np.array(res), 100*np.array(dres), ls='', marker='o') +fnc = lambda E, a, b: np.hypot(a,b/np.sqrt(E)) +p0=(.05, .12) +coeff, var_matrix = curve_fit(fnc, pvals, res, p0=p0, sigma=dres, maxfev=10000) +xx=np.linspace(15, 85, 100) +plt.plot(xx, 100*fnc(xx,*coeff), label=f'fit:{100*coeff[0]:.1f}%$\\oplus\\frac{{{100*coeff[1]:.0f}\\%}}{{\\sqrt{{E}}}}$') +plt.legend() +plt.ylim(0) +plt.ylabel("E resolution [%]") +plt.xlabel("E truth [GeV]") +plt.sca(axs[2]) + +plt.errorbar(pvals, 100*np.array(scale), 100*np.array(dscale), ls='', marker='o') +plt.ylabel("energy scale [%]") +plt.xlabel("E truth [GeV]") +plt.axhline(100, color='0.5', alpha=0.5, ls='--') +plt.ylim(0, 110) +plt.tight_layout() +plt.savefig(outdir+"/energy_res.pdf") + +#energy res in eta bins +fig, axs=plt.subplots(1,2, figsize=(16,8)) + +partitions=[1.5, 2.0, 2.5, 3.0] +for eta_min, eta_max in zip(partitions[:-1], partitions[1:]): + pvals=[] + res=[] + dres=[] + scale=[] + dscale=[] + for p in arrays_sim: + bins=np.linspace(15*p/20,22*p/20, 50) + eta_truth=arrays_sim[p]['eta_truth'] + y,x=np.histogram(np.sum(arrays_sim[p][(eta_trutheta_min)]['EcalEndcapPClusters.energy'], axis=-1), bins=bins) + bcs=(x[1:]+x[:-1])/2 + + fnc=gauss + slc=abs(bcs-p)<3 + sigma=np.sqrt(y[slc])+0.5*(y[slc]==0) + p0=(100, p, 3) + + try: + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + if abs(coeff[1])>100 or np.sqrt(var_matrix[1][1])>100: + continue + pvals.append(p) + res.append(abs(coeff[2])/coeff[1]) + dres.append(np.sqrt(var_matrix[2][2])/coeff[1]) + scale.append(abs(coeff[1])/p) + dscale.append(np.sqrt(var_matrix[1][1])/p) + except: + pass + plt.sca(axs[0]) + plt.errorbar(pvals, 100*np.array(res), 100*np.array(dres), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + + plt.sca(axs[1]) + plt.errorbar(pvals, 100*np.array(scale), 100*np.array(dscale), ls='', marker='o', label=f'${eta_min:.1f}<\\eta<{eta_max:.1f}$') + +plt.sca(axs[0]) +plt.legend() +plt.ylim(0) +plt.ylabel("E resolution [%]") +plt.xlabel("E truth [GeV]") + +plt.sca(axs[1]) +plt.ylabel("energy scale [%]") +plt.xlabel("E truth [GeV]") +plt.axhline(100, color='0.5', alpha=0.5, ls='--') +plt.ylim(0, 110) + +plt.tight_layout() +plt.savefig(outdir+"/energy_res_eta_partitions.pdf") diff --git a/benchmarks/femc_pi0/analysis/gen_particles.cxx b/benchmarks/femc_pi0/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/femc_pi0/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/femc_pi0/config.yml b/benchmarks/femc_pi0/config.yml new file mode 100644 index 00000000..787cb583 --- /dev/null +++ b/benchmarks/femc_pi0/config.yml @@ -0,0 +1,36 @@ +sim:femc_pi0: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 sim_output/femc_pi0/epic_craterlake_rec_pi0_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_pi0: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:femc_pi0"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 results/epic_craterlake/femc_pi0 + +collect_results:femc_pi0: + extends: .det_benchmark + stage: collect + needs: ["bench:femc_pi0"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output results/epic_craterlake/femc_pi0 + - mv results{_save,}/ diff --git a/benchmarks/insert_muon/Snakefile b/benchmarks/insert_muon/Snakefile new file mode 100644 index 00000000..a9a32168 --- /dev/null +++ b/benchmarks/insert_muon/Snakefile @@ -0,0 +1,61 @@ +rule insert_muon_generate: + input: + script="benchmarks/insert_muon/analysis/gen_particles.cxx", + params: + NEVENTS_GEN=5000, + th_max=7.0, + th_min=1.7 + output: + GEN_FILE="sim_output/insert_muon/mu-_{P}GeV.hepmc" + shell: + """ +root -l -b -q '{input.script}({params.NEVENTS_GEN},"{output.GEN_FILE}", "mu-", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule insert_muon_simulate: + input: + GEN_FILE="sim_output/insert_muon/mu-_{P}GeV.hepmc", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/insert_muon/{DETECTOR_CONFIG}_sim_mu-_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +NEVENTS_SIM=1000 +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --skipNEvents $(( $NEVENTS_SIM * {wildcards.INDEX} )) \ + --numberOfEvents $NEVENTS_SIM \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule insert_muon_recon: + input: + SIM_FILE="sim_output/insert_muon/{DETECTOR_CONFIG}_sim_mu-_{P}GeV_{INDEX}.edm4hep.root" + output: + REC_FILE="sim_output/insert_muon/{DETECTOR_CONFIG}_rec_mu-_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +NEVENTS_REC=1000 +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalEndcapPInsertRecHits,HcalEndcapPInsertClusters,HcalEndcapPInsertSubcellHits,EcalEndcapPInsertRecHits,EcalEndcapPInsertClusters -Pjana:nevents=$NEVENTS_REC +""" + +rule insert_muon_analysis: + input: + expand("sim_output/insert_muon/{DETECTOR_CONFIG}_sim_mu-_{P}GeV_{INDEX}.edm4hep.root", + P=[50], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"], + INDEX=range(5), + ), + script="benchmarks/insert_muon/analysis/muon_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/insert_muon"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/insert_muon/analysis/gen_particles.cxx b/benchmarks/insert_muon/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/insert_muon/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/insert_muon/analysis/muon_plots.py b/benchmarks/insert_muon/analysis/muon_plots.py new file mode 100644 index 00000000..ac2aa100 --- /dev/null +++ b/benchmarks/insert_muon/analysis/muon_plots.py @@ -0,0 +1,83 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) + +config=sys.argv[1].split("/")[1] #results/{config}/{benchmark_name} +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +def Landau(x, normalization,location,stdev): + #print(type(x)) + u=(x-location)*3.591/stdev/2.355 + renormalization = 1.64872*normalization + return renormalization * np.exp(-u/2 - np.exp(-u)/2) + +import uproot as ur +arrays_sim={} +momenta=50, +for p in momenta: + arrays_sim[p] = ur.concatenate({ + f'sim_output/insert_muon/{config}_sim_mu-_{p}GeV_{index}.edm4hep.root': 'events' + for index in range(5) + }) + +for array in arrays_sim.values(): + tilt=-0.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['phi_truth']=np.arctan2(pyp,pxp) + +for p in 50,: + E=arrays_sim[p]["HcalEndcapPInsertHits.energy"] + y, x,_=plt.hist(1e3*ak.flatten(E),bins=100, range=(0, 1.2), histtype='step') + bc=(x[1:]+x[:-1])/2 + from scipy.optimize import curve_fit + slc=abs(bc-.48)<.15 + fnc=Landau + p0=[100, .5, .05] + #print(list(y), list(x)) + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0)), maxfev=10000) + print(coeff) + xx=np.linspace(0,.7, 100) + MIP=coeff[1]/1000 + plt.plot(xx, fnc(xx,*coeff), label=f'Landau fit:\nMIP={coeff[1]*1e3:.0f}$\\pm${1e3*np.sqrt(var_matrix[1][1]):.0f} keV') + plt.xlabel("hit energy [MeV]") + plt.ylabel("hits") + plt.title(f"$E_{{\\mu^-}}=${p} GeV") + plt.legend(fontsize=20) + plt.savefig(outdir+"/MIP.pdf") + + plt.figure(figsize=(10,7)) + array=arrays_sim[p] + bins=30; r=((-np.pi, np.pi),(2.8, 4.2)) + selection=np.sum(array["HcalEndcapPInsertHits.energy"]>0.5*MIP,axis=-1)>0 + h1, xedges, yedges = np.histogram2d(list(array[selection]['phi_truth']),list(array[selection]['eta_truth']), bins=bins, range=r) + h2, xedges, yedges = np.histogram2d(list(array['phi_truth']),list(array['eta_truth']), bins=bins, range=r) + + h = h1 / h2 + pc=plt.pcolor(xedges, yedges, h.T,linewidth=0) + plt.xlabel("$\\phi^*$ [rad]") + plt.ylabel("$\\eta^*$") + cb = plt.colorbar(pc) + cb.set_label("acceptance") + plt.title(f"$E_{{\\mu^-}}=${p} GeV") + plt.savefig(outdir+"/acceptance.pdf") diff --git a/benchmarks/insert_muon/config.yml b/benchmarks/insert_muon/config.yml new file mode 100644 index 00000000..4e3272af --- /dev/null +++ b/benchmarks/insert_muon/config.yml @@ -0,0 +1,29 @@ +sim:insert_muon: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 50 + script: + - snakemake $SNAKEMAKE_FLAGS --cores 5 sim_output/insert_muon/epic_craterlake_sim_mu-_${P}GeV_{0,1,2,3,4}.edm4hep.root + retry: + max: 2 + when: + - runner_system_failure + +bench:insert_muon: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:insert_muon"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 results/epic_craterlake/insert_muon + +collect_results:insert_muon: + extends: .det_benchmark + stage: collect + needs: ["bench:insert_muon"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output results/epic_craterlake/insert_muon + - mv results{_save,}/ diff --git a/benchmarks/insert_neutron/Snakefile b/benchmarks/insert_neutron/Snakefile new file mode 100644 index 00000000..136c56e2 --- /dev/null +++ b/benchmarks/insert_neutron/Snakefile @@ -0,0 +1,62 @@ +rule insert_neutron_generate: + input: + script="benchmarks/insert_neutron/analysis/gen_particles.cxx", + params: + NEVENTS_GEN=1000, + th_max=5.7, + th_min=2.0 + output: + GEN_FILE="sim_output/insert_neutron/neutron_{P}GeV.hepmc" + shell: + """ +mkdir -p sim_output/insert_neutron +root -l -b -q '{input.script}({params.NEVENTS_GEN},"{output.GEN_FILE}", "neutron", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule insert_neutron_simulate: + input: + GEN_FILE="sim_output/insert_neutron/neutron_{P}GeV.hepmc", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_sim_neutron_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +NEVENTS_SIM=200 +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --skipNEvents $(( $NEVENTS_SIM * {wildcards.INDEX} )) \ + --numberOfEvents $NEVENTS_SIM \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule insert_neutron_recon: + input: + SIM_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_sim_neutron_{P}GeV_{INDEX}.edm4hep.root" + output: + REC_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_rec_neutron_{P}GeV_{INDEX}.edm4eic.root" + shell: + """ +NEVENTS_REC=200 +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalEndcapPInsertRecHits,HcalEndcapPInsertClusters,HcalEndcapPInsertSubcellHits,EcalEndcapPInsertRecHits,EcalEndcapPInsertClusters -Pjana:nevents=$NEVENTS_REC +""" + +rule insert_neutron_analysis: + input: + expand("sim_output/insert_neutron/{DETECTOR_CONFIG}_rec_neutron_{P}GeV_{INDEX}.edm4eic.root", + P=[20, 30, 40, 50, 60, 70, 80], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"], + INDEX=range(5), + ), + script="benchmarks/insert_neutron/analysis/neutron_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/insert_neutron"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/insert_neutron/analysis/gen_particles.cxx b/benchmarks/insert_neutron/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/insert_neutron/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/insert_neutron/analysis/neutron_plots.py b/benchmarks/insert_neutron/analysis/neutron_plots.py new file mode 100644 index 00000000..424295cf --- /dev/null +++ b/benchmarks/insert_neutron/analysis/neutron_plots.py @@ -0,0 +1,283 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys, uproot as ur +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) +config=sys.argv[1].split("/")[1] #results/{config}/neutron +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +#read files +arrays_sim={} +for p in 20,30,40,50,60,70,80: + arrays_sim[p] = ur.concatenate({ + f'sim_output/insert_neutron/{config}_rec_neutron_{p}GeV_{index}.edm4eic.root': 'events' + for index in range(5) + }) + +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) + +#get the truth pseudorapidity and energy +for array in arrays_sim.values(): + tilt=-0.025 + px=array['MCParticles.momentum.x'][:,2] + py=array['MCParticles.momentum.y'][:,2] + pz=array['MCParticles.momentum.z'][:,2] + p=np.sqrt(px**2+py**2+pz**2) + + pxp=px*np.cos(tilt)-pz*np.sin(tilt) + pyp=py + pzp=pz*np.cos(tilt)+px*np.sin(tilt) + + array['E_truth']=np.hypot(p, 0.9406) + array['eta_truth']=1/2*np.log((p+pzp)/(p-pzp)) + array['theta_truth']=np.arccos(pzp/p) + +# +# get reconstructed theta as avg of theta of cluster centers, weighted by energy +for array in arrays_sim.values(): + tilt=-0.025 + x=array['HcalEndcapPInsertClusters.position.x'] + y=array['HcalEndcapPInsertClusters.position.y'] + z=array['HcalEndcapPInsertClusters.position.z'] + E=array['HcalEndcapPInsertClusters.energy'] + r=np.sqrt(x**2+y**2+z**2) + + xp=x*np.cos(tilt)-z*np.sin(tilt) + yp=y + zp=z*np.cos(tilt)+x*np.sin(tilt) + + w=E + + array['theta_recon']=np.sum(np.arccos(zp/r)*w, axis=-1)/np.sum(w, axis=-1) + array['eta_recon']=-np.log(np.tan(array['theta_recon']/2)) + + +#plot theta residuals: +print("making theta recon plot") +from scipy.optimize import curve_fit + +fig, axs=plt.subplots(1,2, figsize=(16,8)) +plt.sca(axs[0]) +p=40 +eta_min=3.4; eta_max=3.6 +y,x,_=plt.hist(1000*(arrays_sim[p]['theta_recon']-arrays_sim[p]['theta_truth'])\ + [(arrays_sim[p]['eta_truth']>eta_min)&(arrays_sim[p]['eta_truth']eta_min)&(arrays_sim[p]['eta_truth']0)&(a['eta_truth']>eta_min)&(a['eta_truth']0)&(a['eta_truth']>3.4)&(a['eta_truth']<3.6)] + plt.sca(axs[0]) + y, x, _= plt.hist(r, histtype='step', bins=50) + xx=np.linspace(20, 55, 100) + plt.plot(xx,fnc(xx, *coeff_best), ls='-') + plt.xlabel("$E_{uncorr}=E_{Hcal}+E_{Ecal}/w$ [GeV]") + plt.title(f"p=50 GeV, ${eta_min}<\\eta<{eta_max}$, w={wbest:.2f}") + plt.axvline(np.sqrt(50**2+.9406**2), color='g', ls=':') + plt.text(40, max(y)*0.9, "generated\nenergy", color='g', fontsize=20) + + Euncorr.append(Euncorr_best) + resvals.append(best_res) + reserrs.append(res_err) + pvals.append(p) + svals.append(best_s) + wvals.append(wbest) + +pvals=np.array(pvals) +svals=np.array(svals) +Euncorr=np.array(Euncorr) +plt.sca(axs[1]) +plt.plot(Euncorr,wvals, label="w") +w_avg=np.mean(wvals) +plt.axhline(w_avg, label=f'w avg: {w_avg:.2f}', ls=':') +plt.plot(Euncorr,svals, label="s") +m=(np.sum(svals*Euncorr)*len(Euncorr)-np.sum(Euncorr)*np.sum(svals))/(np.sum(Euncorr**2)*len(Euncorr)-np.sum(Euncorr)**2) +b=np.mean(svals)-np.mean(Euncorr)*m +plt.plot(Euncorr,Euncorr*m+b, label=f"s fit: ${m:.4f}E_{{uncorr}}+{b:.2f}$", ls=':') + +plt.xlabel("$E_{uncorr}=E_{Hcal}+E_{Ecal}/w$ [GeV]") +plt.title("$E_{n,recon}=s\\times(E_{Hcal}+E_{Ecal}/w)$") +plt.ylabel('parameter values') +plt.legend() +plt.ylim(0) +plt.savefig(outdir+"neutron_energy_params.pdf") + +#now make the energy plot +print("making energy recon plot") +fig, axs=plt.subplots(1,3, figsize=(24,8)) +partitions=[3.2,3.4, 3.6, 3.8, 4.0] +for eta_min, eta_max in zip(partitions[:-1],partitions[1:]): + pvals=[] + resvals=[] + reserrs=[] + scalevals=[] + scaleerrs=[] + for p in 20, 30,40,50,60,70, 80: + best_res=1000 + res_err=1000 + + + wrange=np.linspace(30, 70, 30)*0.0257 + + w=w_avg + a=arrays_sim[p] + h=np.sum(a[f'HcalEndcapPInsertClusters.energy'], axis=-1) + e=np.sum(a[f'EcalEndcapPInsertClusters.energy'], axis=-1) + #phi=a['phi_truth'] + uncorr=(e/w+h) + s=-0.0064*uncorr+1.80 + r=uncorr*s #reconstructed energy with correction + r=r[(h>0)&(a['eta_truth']>eta_min)&(a['eta_truth']np.pi/2)] + y,x=np.histogram(r,bins=50) + bcs=(x[1:]+x[:-1])/2 + fnc=gauss + slc=abs(bcs-np.mean(r)*1.25)<2*np.std(r) + sigma=np.sqrt(y[slc])+0.5*(y[slc]==0) + p0=(100, np.mean(r), np.std(r)) + try: + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + res=np.abs(coeff[2]/coeff[1]) + + if res +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/insert_tau/analysis/tau_plots.py b/benchmarks/insert_tau/analysis/tau_plots.py new file mode 100644 index 00000000..77c2dd32 --- /dev/null +++ b/benchmarks/insert_tau/analysis/tau_plots.py @@ -0,0 +1,154 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + +plt.rcParams["figure.figsize"] = (7, 7) + +config=sys.argv[1].split("/")[1] #results/{config}/{benchmark_name} +outdir=sys.argv[1]+"/" +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +import uproot as ur +arrays_sim={} +momenta=20, 30, 40, 50, 60,80,100 +for p in momenta: + arrays_sim[p] = ur.concatenate({ + f'sim_output/insert_tau/{config}_rec_tau-_{p}GeV_{index}.edm4eic.root': 'events' + for index in range(5) + }) + + +for a in arrays_sim.values(): + #recon + Etot=0 + px=0 + py=0 + pz=0 + for det in "HcalEndcapPInsert", "EcalEndcapPInsert", "EcalEndcapP", "LFHCAL": + + E=a[f'{det}Clusters.energy'] + + #todo apply corrections depending on whether this is an electromagnetic or hadronic shower. + + x=a[f'{det}Clusters.position.x'] + y=a[f'{det}Clusters.position.y'] + z=a[f'{det}Clusters.position.z'] + r=np.sqrt(x**2+y**2+z**2) + Etot=Etot+np.sum(E, axis=-1) + px=px+np.sum(E*x/r,axis=-1) + py=py+np.sum(E*y/r,axis=-1) + pz=pz+np.sum(E*z/r,axis=-1) + + a['jet_p_recon']=np.sqrt(px**2+py**2+pz**2) + a['jet_E_recon']=Etot + + a['jet_theta_recon']=np.arctan2(np.hypot(px*np.cos(-.025)-pz*np.sin(-.025),py), + pz*np.cos(-.025)+px*np.sin(-.025)) + + #truth + m=a['MCParticles.mass'][::,2:] + px=a['MCParticles.momentum.x'][::,2:] + py=a['MCParticles.momentum.y'][::,2:] + pz=a['MCParticles.momentum.z'][::,2:] + E=np.sqrt(m**2+px**2+py**2+pz**2) + status=a['MCParticles.simulatorStatus'][::,2:] + PDG=a['MCParticles.PDG'][::,2:] + + #find the hadronic part: initial-state tau - all leptons + selection=1*(PDG==15)-1*(np.abs(PDG)==16) + is_hadronic=1*(np.sum((PDG==-14)+(PDG==-12), axis=-1)==0) + + px_hfs, py_hfs, pz_hfs= np.sum(px*selection,axis=-1)*is_hadronic,np.sum(py*selection,axis=-1)*is_hadronic, np.sum(pz*selection,axis=-1)*is_hadronic + + a['hfs_p_truth']=np.sqrt(px_hfs**2+py_hfs**2+pz_hfs**2) + a['hfs_E_truth']=np.sum(E*selection,axis=-1)*is_hadronic + + + a['hfs_theta_truth']=np.arctan2(np.hypot(px_hfs*np.cos(-.025)-pz_hfs*np.sin(-.025),py_hfs), + pz_hfs*np.cos(-.025)+px_hfs*np.sin(-.025)) + a['hfs_eta_truth']=-np.log(np.tan(a['hfs_theta_truth']/2)) + a['n_mu']=np.sum(np.abs(PDG)==13, axis=-1) + a['n_e']=np.sum(np.abs(PDG)==13, axis=-1) + a['hfs_mass_truth']=np.sqrt(a['hfs_E_truth']**2-a['hfs_p_truth']**2) + +for a in arrays_sim.values(): + selection=(a['hfs_eta_truth']>3.1) & (a['hfs_eta_truth']<3.8)\ + &(a['n_mu']==0)&(a['n_e']==0)&(a['hfs_mass_truth']>.140)&(a['jet_E_recon']>0) + +Etruth=[] +Erecon=[] + +theta_truth=[] +theta_recon=[] + +eta_max=3.7 +eta_min=3.3 +for a in arrays_sim.values(): + selection=(a['hfs_eta_truth']>eta_min) & (a['hfs_eta_truth'].140)&(a['jet_E_recon']>1) + theta_truth=np.concatenate((theta_truth,a['hfs_theta_truth'][selection])) + theta_recon=np.concatenate((theta_recon,a['jet_theta_recon'][selection])) + Etruth=np.concatenate((Etruth,a['hfs_E_truth'][selection])) + Erecon=np.concatenate((Erecon,a['jet_E_recon'][selection])) + +plt.figure() +plt.scatter(theta_truth, theta_recon, 1) +plt.xlabel("$\\theta^{hfs}_{\\rm truth}$ [rad]") +plt.ylabel("$\\theta^{hfs}_{\\rm rec}$ [rad]") +plt.title(f"$E_{{\\tau}}$=20-100 GeV, ${eta_min}<\\eta_{{hfs}}<{eta_max}$") +plt.plot([0.04,0.1], [0.04, 0.1], color='tab:orange') +plt.ylim(0, 0.15) +plt.savefig(outdir+"/theta_scatter.pdf") + +plt.figure() +plt.scatter(Etruth, Erecon, 1) +plt.xlabel("$E^{hfs}_{\\rm truth}$ [GeV]") +plt.ylabel("$E^{hfs}_{\\rm rec}$ [GeV]") +plt.title(f"$E_{{\\tau}}$=20-100 GeV, ${eta_min}<\\eta_{{hfs}}<{eta_max}$") +plt.plot((0,100), (0, 100), color='tab:orange') +plt.savefig(outdir+"/energy_scatter.pdf") + +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) +from scipy.optimize import curve_fit + +res=[] +dres=[] +emid=[] +ew=[] +partitions=(20,30, 40, 60,80,100) +for emin, emax in zip(partitions[:-1], partitions[1:]): + + y,x = np.histogram((theta_recon-theta_truth)[(Etruth>emin)&(EtruthSetPalette(1); + gStyle->SetOptTitle(1); + gStyle->SetTitleOffset(.85,"X");gStyle->SetTitleOffset(.85,"Y"); + gStyle->SetTitleSize(.05,"X");gStyle->SetTitleSize(.05,"Y"); + gStyle->SetLabelSize(.04,"X");gStyle->SetLabelSize(.04,"Y"); + gStyle->SetHistLineWidth(2); + gStyle->SetOptFit(1); + gStyle->SetOptStat(1); + + TString dir = ""; + TString dist_dir_mom = "mom_resol"; + + bool debug=true; + // Tree with reconstructed tracks + const int nbins_eta = 5; + int nfiles = 100; + double eta[nbins_eta+1]={1.2,1.5,2,2.5,3,3.5}; + double pt[nbins_eta+1]={0.5,1.0,2.0,5.0,10.0,20.1}; + TH1D *histp[nbins_eta]; + + + for (int i=0; iSetTitle(Form("%1.1f < #eta < %1.1f && p = %1.1f ",eta[i],eta[i+1],mom)); + histp[i]->SetName(Form("hist_mom_%1.1f_%1.1f_pmax_%1.1f",mom,eta[i],eta[i+1])); + } + + TFile* file = TFile::Open(filename.Data()); + if (!file) {printf("file not found !!!"); return;} + TTreeReader myReader("events", file); // name of tree and file + if (debug) cout<<"Filename: "<GetName()<<"\t NEvents: "< charge(myReader, "MCParticles.charge"); + TTreeReaderArray vx_mc(myReader, "MCParticles.vertex.x"); + TTreeReaderArray vy_mc(myReader, "MCParticles.vertex.y"); + TTreeReaderArray vz_mc(myReader, "MCParticles.vertex.z"); + TTreeReaderArray px_mc(myReader, "MCParticles.momentum.x"); + TTreeReaderArray py_mc(myReader, "MCParticles.momentum.y"); + TTreeReaderArray pz_mc(myReader, "MCParticles.momentum.z"); + TTreeReaderArray status(myReader, "MCParticles.generatorStatus"); + TTreeReaderArray pdg(myReader, "MCParticles.PDG"); + + TTreeReaderArray pe_lc(myReader, "LFHCALClusters.energy"); + TTreeReaderArray px_lc(myReader, "LFHCALClusters.position.x"); + TTreeReaderArray py_lc(myReader, "LFHCALClusters.position.y"); + TTreeReaderArray pz_lc(myReader, "LFHCALClusters.position.z"); + TTreeReaderArray pe_ec(myReader, "EcalEndcapPClusters.energy"); + TTreeReaderArray px_ec(myReader, "EcalEndcapPClusters.position.x"); + TTreeReaderArray py_ec(myReader, "EcalEndcapPClusters.position.y"); + TTreeReaderArray pz_ec(myReader, "EcalEndcapPClusters.position.z"); + + int count =0; + int matchId = 1; // Always matched track assigned the index 0 + while (myReader.Next()) + { + std::cout << "events = " << count++ << std::endl; + for (int j = 0; j < pdg.GetSize(); ++j) + { + if (status[j] !=1 && pdg.GetSize()!=1) continue; + Double_t pzmc = pz_mc[j]; + Double_t ptmc = sqrt(px_mc[j]*px_mc[j]+py_mc[j]*py_mc[j]); + Double_t pmc = sqrt(px_mc[j]*px_mc[j]+py_mc[j]*py_mc[j]+pz_mc[j]*pz_mc[j]); // 1./(q/p); similar to prec + Double_t etamc = -1.0*TMath::Log(TMath::Tan((TMath::ACos(pzmc/fabs(pmc)))/2)); + Double_t phimc = TMath::ATan2(py_mc[j],px_mc[j]); + std::cout << "neutron p=" << pmc << " pt=" << ptmc << std::endl; + + if (fabs(ptmc) < pTcut) continue; + + float l_px_tot=0; + float l_py_tot=0; + float l_pz_tot=0; + float l_e_tot=0; + + std::cout << "LFHCAL nclus=" << px_lc.GetSize() << " ECAL nclus=" << pe_ec.GetSize() << std::endl; + for (int jl = 0;jlcd(); + for (int ibin=0; ibinWrite(); + fout_mom->Close(); + +} + + + + diff --git a/benchmarks/lfhcal/README.md b/benchmarks/lfhcal/README.md new file mode 100644 index 00000000..5f56298b --- /dev/null +++ b/benchmarks/lfhcal/README.md @@ -0,0 +1,11 @@ +Detector Benchmark for LFHCAL +=============================== + +## Overview +This benchmark generates events with a range of particle species at fixed energies, all aimed at, or forward of, the LFHCAL, and produces basic plots of clustering performance, both in isolation and combined with the FEMCAL. + + +## Contacts +[Peter Steinberg](steinberg@bnl.gov) + + diff --git a/benchmarks/lfhcal/Snakefile b/benchmarks/lfhcal/Snakefile new file mode 100644 index 00000000..459392c0 --- /dev/null +++ b/benchmarks/lfhcal/Snakefile @@ -0,0 +1,145 @@ +rule lfhcal_sim: + input: + steering_file="EPIC/EVGEN/SINGLE/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.steer", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + output: + "sim_output/lfhcal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root", + log: + "sim_output/lfhcal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root.log", + wildcard_constraints: + ENERGY="[0-9]+[kMG]eV", + PARTICLE="(neutron|pi-|gamma)", + PHASE_SPACE="3to50deg", + 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 INFO \ + --numberOfEvents {params.N_EVENTS} \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --outputFile {output} +""" + + +rule lfhcal_recon: + input: + "sim_output/lfhcal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.edm4hep.root", + output: + "sim_output/lfhcal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.eicrecon.tree.edm4eic.root", + log: + "sim_output/lfhcal/{DETECTOR_CONFIG}/{PARTICLE}/{ENERGY}/{PHASE_SPACE}/{PARTICLE}_{ENERGY}_{PHASE_SPACE}.{INDEX}.eicrecon.tree.edm4eic.root.log", + wildcard_constraints: + INDEX="\d{4}", + shell: """ +env DETECTOR_CONFIG={wildcards.DETECTOR_CONFIG} \ + eicrecon {input} -Ppodio:output_file={output} \ + -Ppodio:output_collections=MCParticles,ReconstructedParticles,LFHCALTruthClusters,LFHCALClusters,LFHCALHits,EcalEndcapPTruthClusters,EcalEndcapPClusters,EcalEndcapPHits +""" + +rule lfhcal_at_momentum: + input: + script="benchmarks/lfhcal/LFHCAL_Performance.C", + # TODO pass as a file list? + sim=lambda wildcards: + expand( + "sim_output/lfhcal/{DETECTOR_CONFIG}/{{PARTICLE}}/{ENERGY}/{PHASE_SPACE}/{{PARTICLE}}_{ENERGY}_{PHASE_SPACE}.{INDEX:04d}.eicrecon.tree.edm4eic.root", + DETECTOR_CONFIG="epic_craterlake", + ENERGY=f"{float(wildcards.MOMENTUM):.0f}GeV" if float(wildcards.MOMENTUM) >= 1 else f"{float(wildcards.MOMENTUM) * 1000:.0f}MeV", + PHASE_SPACE=["3to50deg"], + INDEX=range(1), + ) + if wildcards.CAMPAIGN == "local" else + ancient(expand( + "EPIC/RECO/{{CAMPAIGN}}/epic_craterlake/SINGLE/{{PARTICLE}}/{ENERGY}/{PHASE_SPACE}/{{PARTICLE}}_{ENERGY}_{PHASE_SPACE}.{INDEX:04d}.eicrecon.tree.edm4eic.root", + DETECTOR_CONFIG="epic_craterlake", + ENERGY=f"{float(wildcards.MOMENTUM):.0f}GeV" if float(wildcards.MOMENTUM) >= 1 else f"{float(wildcards.MOMENTUM) * 1000:.0f}MeV", + PHASE_SPACE=["3to50deg"], + INDEX=range(1), + )), + output: + "{CAMPAIGN}/{PARTICLE}/mom/lfhcal_mom_{MOMENTUM}_mom_resol_{PARTICLE}.root", + combined_root=temp("{CAMPAIGN}/lfhcal_sim_{MOMENTUM}_{PARTICLE}.root"), + shell: + """ +hadd {output.combined_root} {input.sim} +cd {wildcards.CAMPAIGN} +root -l -b -q ../{input.script}'("../{output.combined_root}", "{wildcards.PARTICLE}", {wildcards.MOMENTUM}, 0.15)' +""" + +rule lfhcal_summary_at_eta: + input: + expand( + [ + "{{CAMPAIGN}}/{{PARTICLE}}/mom/lfhcal_mom_{MOMENTUM:.1f}_mom_resol_{{PARTICLE}}.root", + ], + MOMENTUM=[0.5, 1.0, 2.0, 5.0, 10.0, 20.0], + ), + script="benchmarks/lfhcal/doCompare_widebins_mom.C", + output: + "{CAMPAIGN}/Final_Results/{PARTICLE}/mom/lfhcal_mom_resol_{PARTICLE}_{ETA_MIN}_eta_{ETA_MAX}.png", + "{CAMPAIGN}/Final_Results/{PARTICLE}/mom/lfhcal_mom_resol_{PARTICLE}_{ETA_MIN}_eta_{ETA_MAX}.root", + shell: + """ +if [[ "{wildcards.CAMPAIGN}" == "local" ]]; then + set +e + EPIC_VERSION="${{DETECTOR_VERSION:-}}" + EICRECON_VERSION="$(eicrecon -v | sed -n -e 's/.*\(v[0-9\.]\+\).*/\\1/p')" + # Legacy detection + : ${{EPIC_VERSION:="$(echo $DETECTOR_PATH | sed -n -e 's/.*epic-\([^-/]\+\).*/\\1/p')"}} + set -e + + echo "ePIC version: $EPIC_VERSION" + echo "EICrecon version: $EICRECON_VERSION" + EXTRA_LEGEND="ePIC $EPIC_VERSION / EICrecon $EICRECON_VERSION" +else + EXTRA_LEGEND="ePIC Simulation {wildcards.CAMPAIGN}" +fi +cd {wildcards.CAMPAIGN} +root -l -b -q ../{input.script}'("{wildcards.PARTICLE}", {wildcards.ETA_MIN}, {wildcards.ETA_MAX}, 1., true, "'"$EXTRA_LEGEND"'")' +""" + +LFHCAL_ETA_BINS = [1.2,1.5,2,2.5,3,3.5] + +rule lfhcal: + input: + lambda wildcards: expand( + [ + "{{CAMPAIGN}}/Final_Results/{PARTICLE}/mom/lfhcal_mom_resol_{PARTICLE}_{ETA_BIN}.png", + "{{CAMPAIGN}}/Final_Results/{PARTICLE}/mom/lfhcal_mom_resol_{PARTICLE}_{ETA_BIN}.root", + ], + ETA_BIN=[f"{eta_min:.1f}_eta_{eta_max:.1f}" for eta_min, eta_max in zip(LFHCAL_ETA_BINS[:-1], LFHCAL_ETA_BINS[1:])], + PARTICLE=["neutron", "pi-", "gamma"] if wildcards.CAMPAIGN == "local" else ["pi-"], + ) + output: + directory("results/lfhcal/{CAMPAIGN}/") + shell: + """ +mkdir {output} +cp {input} {output} +""" + + +rule lfhcal_local: + input: + "results/lfhcal/local", + + +rule lfhcal_campaigns: + input: + expand( + "results/lfhcal/{CAMPAIGN}", + CAMPAIGN=[ + "23.10.0", + "23.11.0", + "23.12.0", + "24.03.1", + "24.04.0", + ], + ) diff --git a/benchmarks/lfhcal/config.yml b/benchmarks/lfhcal/config.yml new file mode 100644 index 00000000..8dfc7130 --- /dev/null +++ b/benchmarks/lfhcal/config.yml @@ -0,0 +1,48 @@ +sim:lfhcal: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - PARTICLE: ["gamma", "neutron", "pi-"] + MOMENTUM: ["500MeV", "1GeV", "2GeV", "5GeV", "10GeV", "20GeV"] + script: + - | + snakemake --cache --cores 5 \ + sim_output/lfhcal/epic_craterlake/${PARTICLE}/${MOMENTUM}/3to50deg/${PARTICLE}_${MOMENTUM}_3to50deg.0000.eicrecon.tree.edm4eic.root + +bench:lfhcal: + extends: .det_benchmark + stage: benchmarks + needs: + - ["sim:lfhcal"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 3 lfhcal_local + +collect_results:lfhcal: + extends: .det_benchmark + stage: collect + needs: + - "bench:lfhcal" + script: + - ls -lrht + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output lfhcal_local + - mv results{_save,}/ + +bench:lfhcal_campaigns: + extends: .det_benchmark + stage: benchmarks + #when: manual + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 lfhcal_campaigns + +collect_results:lfhcal_campaigns: + extends: .det_benchmark + stage: collect + needs: + - "bench:lfhcal_campaigns" + script: + - ls -lrht + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output lfhcal_campaigns + - mv results{_save,}/ diff --git a/benchmarks/lfhcal/doCompare_widebins_mom.C b/benchmarks/lfhcal/doCompare_widebins_mom.C new file mode 100644 index 00000000..5258e5ef --- /dev/null +++ b/benchmarks/lfhcal/doCompare_widebins_mom.C @@ -0,0 +1,176 @@ +// Code to compare the tracking performances: Truth seeding vs real seeding +// Shyam Kumar; shyam.kumar@ba.infn.it; shyam055119@gmail.com + +#include "TGraphErrors.h" +#include "TF1.h" +#include "TRandom.h" +#include "TCanvas.h" +#include "TLegend.h" +#include "TMath.h" +#define mpi 0.139 // 1.864 GeV/c^2 + +//void draw_req_Mom(double etamin, double etamax, double xmin=0., double xmax=0.); +void doCompare_widebins_mom(TString particle = "pi-",double etamin=-1.0, double etamax=1.0, double range =0.3, Bool_t drawreq=1, TString extra_legend = "") // name = p, pt for getting p or pt dependence fitted results +{ + + //=== style of the plot========= + gStyle->SetPalette(1); + gStyle->SetOptTitle(1); + gStyle->SetTitleOffset(1.0,"XY"); + gStyle->SetTitleSize(.04,"XY"); + gStyle->SetLabelSize(.04,"XY"); + gStyle->SetHistLineWidth(2); + gStyle->SetOptFit(1); + gStyle->SetOptStat(1); + + const Int_t nfiles = 6; + double mom[nfiles] ={0.5,1.0,2.0,5.0,10.0,20.0}; + std::vector momV, momresolV, err_momresolV; + momV.clear(); momresolV.clear(); err_momresolV.clear(); + TString symbolname = ""; + if (particle == "pi-") symbolname = "#pi^{-}"; + else symbolname = particle; + ofstream outfile; + outfile.open ("Mom_resol.txt",ios_base::app); + + TF1 *f1=new TF1("f1","FitMomentumResolution",0.,30.0,2); + f1->SetParLimits(0,0.,0.1); + f1->SetParLimits(1,0.,5.0); + + TCanvas *c_mom = new TCanvas("cmom","cmom",1400,1000); + c_mom->SetMargin(0.10, 0.05 ,0.1,0.05); + c_mom->SetGridy(); + + //Reading the root file + TFile *fmom[nfiles]; + TGraphErrors *gr_mom; + TMultiGraph *mgMom; + TLegend *lmom; + mgMom = new TMultiGraph("mgMom",";p (GeV/c); #sigmap/p %"); + + lmom = new TLegend(0.65,0.80,0.90,0.93); + lmom->SetTextSize(0.03); + lmom->SetBorderSize(0); + lmom->SetHeader(extra_legend.Data(), "C"); + lmom->AddEntry((TObject*)0, Form("%s, %1.1f < #eta < %1.1f", symbolname.Data(), etamin, etamax), "C"); + + TF1 *func = new TF1("func","gaus",-0.5,0.5); + + for (int i =0; iSetMargin(0.10, 0.05 ,0.1,0.07); + + //pi-/mom/lfhcal_mom_20.0_mom_resol_pi-.root + fmom[i] = TFile::Open(Form("./%s/mom/lfhcal_mom_%1.1f_mom_resol_%s.root",particle.Data(),mom[i],particle.Data())); + + TH1D *hist = (TH1D*) fmom[i]->Get(Form("hist_mom_%1.1f_%1.1f_pmax_%1.1f",mom[i],etamin,etamax)); + hist->Rebin(2); + hist->SetName(Form("hist_mom_%1.1f_%1.1f_eta_%1.1f_%s",mom[i],etamin,etamax,particle.Data())); + hist->SetTitle(Form("Momentum = %1.1f && %1.1f<#eta<%1.1f;#Delta p/p; Entries(a.u.)",particle.Data(),mom[i],etamin,etamax)); + + double mu = hist->GetMean(); + double sigma = hist->GetRMS(); + double sigma_err = hist->GetRMSError(); + /* + hist->GetXaxis()->SetRangeUser(-1.0*range,1.0*range); + func->SetRange(mu-2.0*sigma,mu+2.0*sigma); // fit with in 2 sigma range + hist->Fit(func,"NR+"); + mu = func->GetParameter(1); + sigma = func->GetParameter(2); + func->SetRange(mu-2.0*sigma,mu+2.0*sigma); + hist->Fit(func,"R+"); + float par2 = func->GetParameter(2)*100; + float par2_err = func->GetParError(2)*100; + */ + momV.push_back(mom[i]); + momresolV.push_back(sigma); + err_momresolV.push_back(sigma_err); + + cp->cd(); + hist->Draw(); + //cp->SaveAs(Form("Debug_Plots/%s/mom/mom_resol_mom%1.1f_%1.1f_eta_%1.1f.png",particle.Data(),mom[i],etamin,etamax)); + } // all files + + const int size = momV.size(); + double p[size], err_p[size], sigma_p[size], err_sigma_p[size]; + + for (int i=0; iSetName("grseed"); + gr1->SetMarkerStyle(25); + gr1->SetMarkerColor(kBlue); + gr1->SetMarkerSize(2.0); + gr1->SetTitle(";p (GeV/c);#sigmap/p"); + gr1->GetXaxis()->CenterTitle(); + gr1->GetYaxis()->CenterTitle(); + + + mgMom->Add(gr1); + c_mom->cd(); + mgMom->GetXaxis()->SetRangeUser(0.40,20.2); + mgMom->GetYaxis()->SetRangeUser(0.0,1.50*TMath::MaxElement(gr1->GetN(),gr1->GetY())); // 50% more of the maximum value on yaxis + mgMom->Draw("AP"); + lmom->AddEntry(gr1,"Nominal"); + lmom->Draw("same"); + //draw_req_Mom(etamin,etamax,0.,mgMom->GetXaxis()->GetXmax()); + c_mom->SaveAs(Form("Final_Results/%s/mom/lfhcal_mom_resol_%s_%1.1f_eta_%1.1f.png",particle.Data(),particle.Data(),etamin,etamax)); + + // Write the numbers in output file for comparisons + outfile << extra_legend << endl; + outfile<<"Etamin"<GetN(); ++i){ + double x,y; + gr1->GetPoint(i,x,y); + outfile<cd(); + mgMom->SetName(Form("mom_resol_%1.1f_eta_%1.1f",etamin,etamax)); + mgMom->Write(); + fout->Close(); +} + +//===Fit Momentum Resolution +float FitMomentumResolution(Double_t *x, Double_t *par) +{ + float func = sqrt(par[0]*par[0]*x[0]*x[0]+par[1]*par[1]); + return func; +} + +//From Yellow report from section 11.2.2 + +// 1.2,1.5,2,2.5,3,3.5 + +/* +void draw_req_Mom(double etamin, double etamax, double xmin=0., double xmax=0.) +{ + + TF1 *dd4hep_p; + if (etamin >= -3.5 && etamax <= -2.5) dd4hep_p = new TF1("dd4hep_p", "TMath::Sqrt((0.1*x)^2+2.0^2)",xmin,xmax); + else if (etamin >= -2.5 && etamax <= -1.0) dd4hep_p = new TF1("dd4hep_p", "TMath::Sqrt((0.05*x)^2+1.0^2)",xmin,xmax); + else if (etamin >= -1.0 && etamax <= 1.0) dd4hep_p = new TF1("dd4hep_p", "TMath::Sqrt((0.05*x)^2+0.5^2)",xmin,xmax); + else if (etamin >= 1.0 && etamax <= 2.5) dd4hep_p = new TF1("dd4hep_p", "TMath::Sqrt((0.05*x)^2+1.0^2)",xmin,xmax); + else if (etamin >= 2.5 && etamax <= 3.5) dd4hep_p = new TF1("dd4hep_p", "TMath::Sqrt((0.1*x)^2+2.0^2)",xmin,xmax); + else return; + dd4hep_p->SetLineStyle(7); + dd4hep_p->SetLineColor(kMagenta); + dd4hep_p->SetLineWidth(3.0); + dd4hep_p->Draw("same"); + + TLegend *l= new TLegend(0.70,0.75,0.90,0.80); + l->SetTextSize(0.03); + l->SetBorderSize(0); + l->AddEntry(dd4hep_p,"PWGReq","l"); + l->Draw("same"); + } +*/ diff --git a/benchmarks/material_scan/config.yml b/benchmarks/material_scan/config.yml index f8bc0822..3c87305e 100644 --- a/benchmarks/material_scan/config.yml +++ b/benchmarks/material_scan/config.yml @@ -2,7 +2,7 @@ bench:material_scan: extends: .det_benchmark stage: benchmarks script: - - snakemake --cores 1 epic_craterlake/results/material_scan_details.pdf + - snakemake $SNAKEMAKE_FLAGS --cores 1 epic_craterlake/results/material_scan_details.pdf collect_results:material_scan: extends: .det_benchmark diff --git a/benchmarks/tracking_performances/config.yml b/benchmarks/tracking_performances/config.yml index 90a0d0f6..2419000b 100644 --- a/benchmarks/tracking_performances/config.yml +++ b/benchmarks/tracking_performances/config.yml @@ -18,7 +18,7 @@ bench:tracking_performance: needs: - ["sim:tracking_performance"] script: - - snakemake --cores 3 tracking_performance_local + - snakemake $SNAKEMAKE_FLAGS --cores 3 tracking_performance_local collect_results:tracking_performance: extends: .det_benchmark @@ -27,14 +27,14 @@ collect_results:tracking_performance: - "bench:tracking_performance" script: - ls -lrht - - snakemake --cores 1 --delete-all-output tracking_performance_local + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output tracking_performance_local bench:tracking_performance_campaigns: extends: .det_benchmark stage: benchmarks #when: manual script: - - snakemake --cores 1 tracking_performance_campaigns + - snakemake $SNAKEMAKE_FLAGS --cores 1 tracking_performance_campaigns collect_results:tracking_performance_campaigns: extends: .det_benchmark @@ -44,5 +44,5 @@ collect_results:tracking_performance_campaigns: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output tracking_performance_campaigns + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output tracking_performance_campaigns - mv results{_save,}/ diff --git a/benchmarks/tracking_performances_dis/config.yml b/benchmarks/tracking_performances_dis/config.yml index 961df549..066e8df7 100644 --- a/benchmarks/tracking_performances_dis/config.yml +++ b/benchmarks/tracking_performances_dis/config.yml @@ -2,7 +2,7 @@ sim:tracking_performances_dis: extends: .det_benchmark stage: simulate script: - - snakemake --cache --cores 5 results/tracking_performances_dis/epic_craterlake_tracking_only/pythia8NCDIS_18x275_minQ2=1_combined_5/hists.root + - snakemake $SNAKEMAKE_FLAGS --cores 5 results/tracking_performances_dis/epic_craterlake_tracking_only/pythia8NCDIS_18x275_minQ2=1_combined_5/hists.root retry: max: 2 when: @@ -14,7 +14,7 @@ bench:tracking_performances_dis: needs: - ["sim:tracking_performances_dis"] script: - - snakemake --cores 1 trk_dis_run_locally_trk_only + - snakemake $SNAKEMAKE_FLAGS --cores 1 trk_dis_run_locally_trk_only # avoid uploading intermediate results - find results/tracking_performances_dis/ -mindepth 2 -maxdepth 2 -type d ! -name "*combined*" | xargs rm -rfv @@ -26,7 +26,7 @@ collect_results:tracking_performances_dis: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output trk_dis_run_locally_trk_only + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output trk_dis_run_locally_trk_only - mv results{_save,}/ # convert to png - | diff --git a/benchmarks/zdc_lambda/Snakefile b/benchmarks/zdc_lambda/Snakefile new file mode 100644 index 00000000..9ffd1701 --- /dev/null +++ b/benchmarks/zdc_lambda/Snakefile @@ -0,0 +1,59 @@ +rule zdc_lambda_generate: + input: + script="benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx", + params: + NEVENTS_GEN=1000, + output: + GEN_FILE="sim_output/zdc_lambda/lambda_decay_{P}GeV.hepmc" + shell: + """ +root -l -b -q '{input.script}({params.NEVENTS_GEN},0,"{output.GEN_FILE}",{wildcards.P},{wildcards.P})' +""" + +rule zdc_lambda_simulate: + input: + GEN_FILE="sim_output/zdc_lambda/lambda_decay_{P}GeV.hepmc", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_sim_lambda_dec_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +NEVENTS_SIM=200 +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --skipNEvents $(( $NEVENTS_SIM * {wildcards.INDEX} )) \ + --numberOfEvents $NEVENTS_SIM \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule zdc_lambda_recon: + input: + SIM_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_sim_lambda_dec_{P}GeV_{INDEX}.edm4hep.root" + output: + REC_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_rec_lambda_dec_{P}GeV_{INDEX}.edm4eic.root" + shell: + """ +NEVENTS_REC=200 +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalFarForwardZDCClusters,HcalFarForwardZDCRecHits,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_lambda_analysis: + input: + expand("sim_output/zdc_lambda/{DETECTOR_CONFIG}_rec_lambda_dec_{P}GeV_{INDEX}.edm4eic.root", + P=[100, 125, 150,175, 200, 225, 250, 275], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"], + INDEX=range(5), + ), + script="benchmarks/zdc_lambda/analysis/lambda_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/zdc_lambda"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx b/benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx new file mode 100644 index 00000000..1b399dda --- /dev/null +++ b/benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx @@ -0,0 +1,240 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include + +#include +#include +#include + +using namespace HepMC3; + +std::tuple GetParticleInfo(TDatabasePDG* pdg, TString particle_name) +{ + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + const double lifetime = particle->Lifetime(); + return std::make_tuple(mass, pdgID, lifetime); +} +// Calculates the decay length of a particle. Samples from an exponential decay. +double GetDecayLength(TRandom3* r1, double lifetime, double mass, double momentum_magnitude) +{ + double c_speed = TMath::C() * 1000.; // speed of light im mm/sec + double average_decay_length = (momentum_magnitude/mass) * lifetime * c_speed; + return r1->Exp(average_decay_length); +} + +// Generate single lambda mesons and decay them to a neutron + 2 photons +void gen_lambda_decay(int n_events = 100000, UInt_t seed = 0, char* out_fname = "lambda_decay.hepmc", + double p_min = 100., // in GeV/c + double p_max = 275.) // in GeV/c +{ + + const double theta_min = 0.0; // in mRad + const double theta_max = 3.0; // in mRad + //const double p_min = 100.; // in GeV/c + //const double p_max = 275.; // in GeV/c + + WriterAscii hepmc_output(out_fname); + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(seed); //Default = 0, which uses clock to set seed + cout<<"Random number seed is "<GetSeed()<<"!"<(lambda_info); + int lambda_pdgID = std::get<1>(lambda_info); + double lambda_lifetime = std::get<2>(lambda_info); + + auto neutron_info = GetParticleInfo(pdg, "neutron"); + double neutron_mass = std::get<0>(neutron_info); + int neutron_pdgID = std::get<1>(neutron_info); + + auto pi0_info = GetParticleInfo(pdg, "pi0"); + double pi0_mass = std::get<0>(pi0_info); + int pi0_pdgID = std::get<1>(pi0_info); + double pi0_lifetime = std::get<2>(pi0_info); + + auto photon_info = GetParticleInfo(pdg, "gamma"); + double photon_mass = std::get<0>(photon_info); + int photon_pdgID = std::get<1>(photon_info); + + int accepted_events = 0; + while (accepted_events < n_events) { + + //Set the event number + evt.set_event_number(accepted_events); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to EIC proton beam direction + Double_t lambda_p = r1->Uniform(p_min, p_max); + Double_t lambda_phi = r1->Uniform(0.0, 2.0 * M_PI); + Double_t lambda_th = r1->Uniform(theta_min/1000., theta_max/1000.); // Divide by 1000 for radians + Double_t lambda_px = lambda_p * TMath::Cos(lambda_phi) * TMath::Sin(lambda_th); + Double_t lambda_py = lambda_p * TMath::Sin(lambda_phi) * TMath::Sin(lambda_th); + Double_t lambda_pz = lambda_p * TMath::Cos(lambda_th); + Double_t lambda_E = TMath::Sqrt(lambda_p*lambda_p + lambda_mass*lambda_mass); + + // Rotate to lab coordinate system + TVector3 lambda_pvec(lambda_px, lambda_py, lambda_pz); + double cross_angle = -25./1000.; // in Rad + TVector3 pbeam_dir(TMath::Sin(cross_angle), 0, TMath::Cos(cross_angle)); //proton beam direction + lambda_pvec.RotateY(cross_angle); // Theta is returned positive, beam in negative X + + // type 2 is state that will decay + GenParticlePtr p_lambda = std::make_shared( + FourVector(lambda_pvec.X(), lambda_pvec.Y(), lambda_pvec.Z(), lambda_E), lambda_pdgID, 2 ); + + // Generating lambda particle, will be generated at origin + // Must have input electron + proton for vertex + GenVertexPtr lambda_initial_vertex = std::make_shared(); + lambda_initial_vertex->add_particle_in(p1); + lambda_initial_vertex->add_particle_in(p2); + lambda_initial_vertex->add_particle_out(p_lambda); + evt.add_vertex(lambda_initial_vertex); + + // Generate neutron + pi0 in lambda rest frame + TLorentzVector neutron_rest, pi0_rest; + + // Generating uniformly along a sphere + double cost_neutron_rest = r1->Uniform(-1,1); + double th_neutron_rest = TMath::ACos(cost_neutron_rest); + double sint_neutron_rest = TMath::Sin(th_neutron_rest); + + double phi_neutron_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_neutron_rest = TMath::Cos(phi_neutron_rest); + double sinp_neutron_rest = TMath::Sin(phi_neutron_rest); + + // Calculate energy of each particle in the lambda rest frame + // See problem 3.19 in Introduction to Elementary Particles, 2nd edition by D. Griffiths + double E_neutron_rest = (-TMath::Power(pi0_mass, 2.) + TMath::Power(lambda_mass, 2.) + TMath::Power(neutron_mass, 2.) ) / (2. * lambda_mass) ; + double E_pi0_rest = (-TMath::Power(neutron_mass, 2.) + TMath::Power(lambda_mass, 2.) + TMath::Power(pi0_mass, 2.) ) / (2. * lambda_mass) ; + + // Both particles will have the same momentum, so just use neutron variables + double momentum_rest = TMath::Sqrt( E_neutron_rest*E_neutron_rest - neutron_mass*neutron_mass ); + + neutron_rest.SetE(E_neutron_rest); + neutron_rest.SetPx( momentum_rest * sint_neutron_rest * cosp_neutron_rest ); + neutron_rest.SetPy( momentum_rest * sint_neutron_rest * sinp_neutron_rest ); + neutron_rest.SetPz( momentum_rest * cost_neutron_rest ); + + pi0_rest.SetE(E_pi0_rest); + pi0_rest.SetPx( -neutron_rest.Px() ); + pi0_rest.SetPy( -neutron_rest.Py() ); + pi0_rest.SetPz( -neutron_rest.Pz() ); + + // Boost neutron & pion to lab frame + TLorentzVector lambda_lab(lambda_pvec.X(), lambda_pvec.Y(), lambda_pvec.Z(), lambda_E); + TVector3 lambda_boost = lambda_lab.BoostVector(); + TLorentzVector neutron_lab, pi0_lab; + neutron_lab = neutron_rest; + neutron_lab.Boost(lambda_boost); + pi0_lab = pi0_rest; + pi0_lab.Boost(lambda_boost); + + // Calculating position for lambda decay + TVector3 lambda_unit = lambda_lab.Vect().Unit(); + double lambda_decay_length = GetDecayLength(r1, lambda_lifetime, lambda_mass, lambda_lab.P()); + TVector3 lambda_decay_position = lambda_unit * lambda_decay_length; + double lambda_decay_time = lambda_decay_length / lambda_lab.Beta() ; // Decay time in lab frame in length units (mm) + + // Generating vertex for lambda decay + GenParticlePtr p_neutron = std::make_shared( + FourVector(neutron_lab.Px(), neutron_lab.Py(), neutron_lab.Pz(), neutron_lab.E()), neutron_pdgID, 1 ); + + GenParticlePtr p_pi0 = std::make_shared( + FourVector(pi0_lab.Px(), pi0_lab.Py(), pi0_lab.Pz(), pi0_lab.E()), pi0_pdgID, 2 ); + + GenVertexPtr v_lambda_decay = std::make_shared(FourVector(lambda_decay_position.X(), lambda_decay_position.Y(), lambda_decay_position.Z(), lambda_decay_time)); + v_lambda_decay->add_particle_in(p_lambda); + v_lambda_decay->add_particle_out(p_neutron); + v_lambda_decay->add_particle_out(p_pi0); + + evt.add_vertex(v_lambda_decay); + + // Generate two photons from pi0 decay + TLorentzVector gamma1_rest, gamma2_rest; + + // Generating uniformly along a sphere + double cost_gamma1_rest = r1->Uniform(-1,1); + double th_gamma1_rest = TMath::ACos(cost_gamma1_rest); + double sint_gamma1_rest = TMath::Sin(th_gamma1_rest); + + double phi_gamma1_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_gamma1_rest = TMath::Cos(phi_gamma1_rest); + double sinp_gamma1_rest = TMath::Sin(phi_gamma1_rest); + + // Photons are massless so they each get equal energies + gamma1_rest.SetE(pi0_mass/2.); + gamma1_rest.SetPx( (pi0_mass/2.)*sint_gamma1_rest*cosp_gamma1_rest ); + gamma1_rest.SetPy( (pi0_mass/2.)*sint_gamma1_rest*sinp_gamma1_rest ); + gamma1_rest.SetPz( (pi0_mass/2.)*cost_gamma1_rest ); + + gamma2_rest.SetE(pi0_mass/2.); + gamma2_rest.SetPx( -gamma1_rest.Px() ); + gamma2_rest.SetPy( -gamma1_rest.Py() ); + gamma2_rest.SetPz( -gamma1_rest.Pz() ); + + // Boost neutron & pion to lab frame + TVector3 pi0_boost = pi0_lab.BoostVector(); + TLorentzVector gamma1_lab, gamma2_lab; + gamma1_lab = gamma1_rest; + gamma1_lab.Boost(pi0_boost); + gamma2_lab = gamma2_rest; + gamma2_lab.Boost(pi0_boost); + + GenParticlePtr p_gamma1 = std::make_shared( + FourVector(gamma1_lab.Px(), gamma1_lab.Py(), gamma1_lab.Pz(), gamma1_lab.E()), photon_pdgID, 1 ); + + GenParticlePtr p_gamma2 = std::make_shared( + FourVector(gamma2_lab.Px(), gamma2_lab.Py(), gamma2_lab.Pz(), gamma2_lab.E()), photon_pdgID, 1 ); + + // Generate pi0 at same position as the lambda. Approximating pi0 decay as instantaneous + GenVertexPtr v_pi0_decay = std::make_shared(FourVector(lambda_decay_position.X(), lambda_decay_position.Y(), lambda_decay_position.Z(), lambda_decay_time)); + v_pi0_decay->add_particle_in(p_pi0); + v_pi0_decay->add_particle_out(p_gamma1); + v_pi0_decay->add_particle_out(p_gamma2); + + //std::cout<< lambda_pvec.Angle(pbeam_dir) << " " << neutron_lab.Angle(pbeam_dir) << " " << gamma1_lab.Angle(pbeam_dir) << " " << gamma2_lab.Angle(pbeam_dir) << std::endl; + + evt.add_vertex(v_pi0_decay); + + if (accepted_events == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + double zdc_z=35800; + TVector3 extrap_neutron=lambda_decay_position+neutron_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(neutron_lab.Vect()))); + TVector3 extrap_gamma1=lambda_decay_position+gamma1_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(gamma1_lab.Vect()))); + TVector3 extrap_gamma2=lambda_decay_position+gamma2_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(gamma2_lab.Vect()))); + if (extrap_neutron.Angle(pbeam_dir)<0.004 && extrap_gamma1.Angle(pbeam_dir)<0.004 && extrap_gamma2.Angle(pbeam_dir)<0.004 && lambda_decay_position.Dot(pbeam_dir)max_val): + max_val=largest_eigenvalue + index_of_max=j + if index_of_max >=0: + is_neutron_cand[p][i][index_of_max]=1 + eigs.sort() + + is_neutron_cand[p]=ak.Array(is_neutron_cand[p]) + + +#with the position of the vertex determined by assuming the mass of the pi0 +#corrected pt* and theta* recon +pt_recon_corr={} +theta_recon_corr={} +mass_recon_corr={} +mass_pi0_recon_corr={} +pi0_converged={} +zvtx_recon={} + +maxZ=35800 +for p in momenta: + xvtx=0 + yvtx=0 + zvtx=0 + + for iteration in range(20): + + #compute the value of theta* using the clusters in the ZDC + xc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.x"] + yc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.y"] + zc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.z"] + E=arrays_sim[p][f"HcalFarForwardZDCClusters.energy"] + #apply correction to the neutron candidates only + A,B,C=-0.0756, -1.91, 2.30 + neutron_corr=(1-is_neutron_cand[p])+is_neutron_cand[p]/(1+A+B/np.sqrt(E)+C/E) + E=E*neutron_corr + + E_recon=np.sum(E, axis=-1) + pabs=np.sqrt(E**2-is_neutron_cand[p]*.9406**2) + tilt=-0.025 + xcp=xc*np.cos(tilt)-zc*np.sin(tilt) + ycp=yc + zcp=zc*np.cos(tilt)+xc*np.sin(tilt) + rcp=np.sqrt(xcp**2+ycp**2+zcp**2) + + ux=(xcp-xvtx) + uy=(ycp-yvtx) + uz=(zcp-zvtx) + + norm=np.sqrt(ux**2+uy**2+uz**2) + ux=ux/norm + uy=uy/norm + uz=uz/norm + + px_recon,py_recon,pz_recon=np.sum(pabs*ux, axis=-1),np.sum(pabs*uy, axis=-1),np.sum(pabs*uz, axis=-1) + + pt_recon_corr[p]=np.hypot(px_recon,py_recon) + theta_recon_corr[p]=np.arctan2(pt_recon_corr[p], pz_recon) + + mass_recon_corr[p]=np.sqrt((E_recon)**2\ + -(px_recon)**2\ + -(py_recon)**2\ + -(pz_recon)**2) + mass_pi0_recon_corr[p]=np.sqrt(np.sum(pabs*(1-is_neutron_cand[p]), axis=-1)**2\ + -np.sum(pabs*ux*(1-is_neutron_cand[p]), axis=-1)**2\ + -np.sum(pabs*uy*(1-is_neutron_cand[p]), axis=-1)**2\ + -np.sum(pabs*uz*(1-is_neutron_cand[p]), axis=-1)**2) + alpha=1 + if iteration==0: + u=np.sqrt(px_recon**2+py_recon**2+pz_recon**2) + ux=px_recon/u + uy=py_recon/u + uz=pz_recon/u + zeta=1/2 + zvtx=maxZ*np.power(zeta,alpha) + xvtx=ux/uz*zvtx + yvtx=uy/uz*zvtx + else : + u=np.sqrt(px_recon**2+py_recon**2+pz_recon**2) + ux=px_recon/u + uy=py_recon/u + uz=pz_recon/u + s=2*(mass_pi0_recon_corr[p]<0.135)-1 + zeta=np.power(zvtx/maxZ, 1/alpha) + zeta=zeta+s*1/2**(1+iteration) + zvtx=maxZ*np.power(zeta,alpha) + xvtx=ux/uz*zvtx + yvtx=uy/uz*zvtx + #print(zvtx) + pi0_converged[p]=np.abs(mass_pi0_recon_corr[p]-0.135)<0.01 + zvtx_recon[p]=zvtx + +fig,axs=plt.subplots(1,3, figsize=(24, 8)) +plt.sca(axs[0]) +plt.title(f"$E_{{\\Lambda}}=100-275$ GeV") +x=[] +y=[] +for p in momenta: + accept=(nclusters[p]==3) &(pi0_converged[p]) + x+=list(theta_truth[p][accept]*1000) + y+=list(theta_recon_corr[p][accept]*1000) +plt.scatter(x,y) +plt.xlabel("$\\theta^{*\\rm truth}_{\\Lambda}$ [mrad]") +plt.ylabel("$\\theta^{*\\rm recon}_{\\Lambda}$ [mrad]") +plt.xlim(0,3.2) +plt.ylim(0,3.2) + +plt.sca(axs[1]) +plt.title(f"$E_{{\\Lambda}}=100-275$ GeV") +y,x,_=plt.hist(y-np.array(x), bins=50, range=(-1,1)) +bc=(x[1:]+x[:-1])/2 + +from scipy.optimize import curve_fit +slc=abs(bc)<0.3 +fnc=gauss +p0=[100, 0, 0.05] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0), maxfev=10000) +x=np.linspace(-1, 1) +plt.plot(x, gauss(x, *coeff), color='tab:orange') +plt.xlabel("$\\theta^{*\\rm recon}_{\\Lambda}-\\theta^{*\\rm truth}_{\\Lambda}$ [mrad]") +plt.ylabel("events") + +plt.sca(axs[2]) +sigmas=[] +dsigmas=[] +xvals=[] +for p in momenta: + + accept=(nclusters[p]==3) &(pi0_converged[p]) + y,x=np.histogram((theta_recon_corr[p]-theta_truth[p])[accept]*1000, bins=100, range=(-1,1)) + bc=(x[1:]+x[:-1])/2 + + from scipy.optimize import curve_fit + slc=abs(bc)<0.3 + fnc=gauss + p0=(100, 0, 0.06) + #print(bc[slc],y[slc]) + sigma=np.sqrt(y[slc])+(y[slc]==0) + try: + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + sigmas.append(coeff[2]) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + print("fit failed") +plt.ylim(0, 0.3) + +plt.errorbar(xvals, sigmas, dsigmas, ls='', marker='o', color='k') +x=np.linspace(100, 275, 100) +plt.plot(x, 3/np.sqrt(x), color='tab:orange') +plt.text(170, .23, "YR requirement:\n 3 mrad/$\\sqrt{E}$") +plt.xlabel("$E_{\\Lambda}$ [GeV]") +plt.ylabel("$\\sigma[\\theta^*_{\\Lambda}]$ [mrad]") +plt.tight_layout() +plt.savefig(outdir+"thetastar_recon.pdf") +#plt.show() + + +fig,axs=plt.subplots(1,3, figsize=(24, 8)) +plt.sca(axs[0]) +plt.title(f"$E_{{\\Lambda}}=100-275$ GeV") +x=[] +y=[] +for p in momenta: + accept=(nclusters[p]==3) &(pi0_converged[p]) + x+=list(arrays_sim[p]['MCParticles.vertex.z'][:,3][accept]/1000) + y+=list(zvtx_recon[p][accept]/1000) +plt.scatter(x,y) +#print(x,y) +plt.xlabel("$z^{\\rm truth}_{\\rm vtx}$ [m]") +plt.ylabel("$z^{\\rm recon}_{\\rm vtx}$ [m]") +plt.xlim(0,40) +plt.ylim(0,40) + +plt.sca(axs[1]) +plt.title(f"$E_{{\\Lambda}}=100-275$ GeV") +y,x,_=plt.hist(y-np.array(x), bins=50, range=(-10,10)) +bc=(x[1:]+x[:-1])/2 + +from scipy.optimize import curve_fit +slc=abs(bc)<5 +fnc=gauss +p0=[100, 0, 1] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0), maxfev=10000) +x=np.linspace(-5, 5) +plt.plot(x, gauss(x, *coeff), color='tab:orange') +print(coeff[2], np.sqrt(var_matrix[2][2])) +plt.xlabel("$z^{*\\rm recon}_{\\rm vtx}-z^{*\\rm truth}_{\\rm vtx}$ [m]") +plt.ylabel("events") + +plt.sca(axs[2]) +sigmas=[] +dsigmas=[] +xvals=[] +for p in momenta: + + accept=(nclusters[p]==3) &(pi0_converged[p]) + a=list((zvtx_recon[p]-arrays_sim[p]['MCParticles.vertex.z'][:,3])[accept]/1000) + y,x=np.histogram(a, bins=100, range=(-10,10)) + bc=(x[1:]+x[:-1])/2 + + from scipy.optimize import curve_fit + slc=abs(bc)<5 + fnc=gauss + p0=(100, 0, 1) + #print(bc[slc],y[slc]) + sigma=np.sqrt(y[slc])+(y[slc]==0) + try: + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, sigma=list(sigma), maxfev=10000) + sigmas.append(coeff[2]) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + print("fit failed") +plt.ylim(0, 2) + +plt.errorbar(xvals, sigmas, dsigmas, ls='', marker='o', color='k') +x=np.linspace(100, 275, 100) + +avg=np.sum(sigmas/np.array(dsigmas)**2)/np.sum(1/np.array(dsigmas)**2) +plt.axhline(avg, color='tab:orange') +plt.text(150, 1.25,f"$\\sigma\\approx${avg:.1f} m") + +plt.xlabel("$E_{\\Lambda}$ [GeV]") +plt.ylabel("$\\sigma[z_{\\rm vtx}]$ [m]") +plt.tight_layout() +plt.savefig(outdir+"zvtx_recon.pdf") +#plt.show() + +p=100 +fig,axs=plt.subplots(1,2, figsize=(16, 8)) +plt.sca(axs[0]) +lambda_mass=1.115683 +vals=[] +for p in momenta: + accept=(nclusters[p]==3) &(pi0_converged[p]) + vals+=list(mass_recon_corr[p][accept]) + +y,x,_= plt.hist(vals, bins=100, range=(1.0, 1.25)) +bc=(x[1:]+x[:-1])/2 +plt.axvline(lambda_mass, ls='--', color='tab:green', lw=3) +plt.text(lambda_mass+.01, np.max(y)*1.05, "PDG mass", color='tab:green') +plt.xlabel("$m_{\\Lambda}^{\\rm recon}$ [GeV]") +plt.ylim(0, np.max(y)*1.2) +plt.xlim(1.0, 1.25) + +from scipy.optimize import curve_fit +slc=abs(bc-lambda_mass)<0.07 +fnc=gauss +p0=[100, lambda_mass, 0.04] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0), maxfev=10000) +x=np.linspace(0.8, 1.3, 200) +plt.plot(x, gauss(x, *coeff), color='tab:orange') +print(coeff[2], np.sqrt(var_matrix[2][2])) +plt.xlabel("$m^{\\rm recon}_{\\Lambda}$ [GeV]") +plt.ylabel("events") +plt.title(f"$E_{{\\Lambda}}=100-275$ GeV") + +plt.sca(axs[1]) +xvals=[] +sigmas=[] +dsigmas=[] +for p in momenta: + accept=(nclusters[p]==3) &(pi0_converged[p]) + y,x= np.histogram(mass_recon_corr[p][accept], bins=100, range=(0.6,1.4)) + bc=(x[1:]+x[:-1])/2 + + from scipy.optimize import curve_fit + slc=abs(bc-lambda_mass)<0.07 + fnc=gauss + p0=[100, lambda_mass, 0.05] + try: + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0)), maxfev=10000) + x=np.linspace(0.8, 1.3, 200) + sigmas.append(coeff[2]) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + print("fit failed") + +plt.errorbar(xvals, sigmas, dsigmas, ls='', marker='o', color='k') +avg=np.sum(sigmas/np.array(dsigmas)**2)/np.sum(1/np.array(dsigmas)**2) +plt.axhline(avg, color='tab:orange') +plt.text(150, 0.01,f"$\\sigma\\approx${avg*1000:.0f} MeV") +plt.xlabel("$E_{\\Lambda}$ [GeV]") +plt.ylabel("$\\sigma[m_{\\Lambda}]$ [GeV]") +plt.ylim(0, 0.02) +plt.tight_layout() +plt.savefig(outdir+"lambda_mass_rec.pdf") diff --git a/benchmarks/zdc_lambda/config.yml b/benchmarks/zdc_lambda/config.yml new file mode 100644 index 00000000..609942f5 --- /dev/null +++ b/benchmarks/zdc_lambda/config.yml @@ -0,0 +1,36 @@ +sim:zdc_lambda: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 100 + - P: 125 + - P: 150 + - P: 175 + - P: 200 + - P: 225 + - P: 250 + - P: 275 + script: + - snakemake $SNAKEMAKE_FLAGS --cores 5 sim_output/zdc_lambda/epic_zdc_sipm_on_tile_only_rec_lambda_dec_${P}GeV_{0,1,2,3,4}.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_lambda: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:zdc_lambda"] + script: + - snakemake $SNAKEMAKE_FLAGS --cores 1 results/epic_zdc_sipm_on_tile_only/zdc_lambda + +collect_results:zdc_lambda: + extends: .det_benchmark + stage: collect + needs: ["bench:zdc_lambda"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output results/epic_zdc_sipm_on_tile_only/zdc_lambda + - mv results{_save,}/ diff --git a/benchmarks/zdc_lyso/analysis/analysis.py b/benchmarks/zdc_lyso/analysis/analysis.py index eace6112..8994fa24 100644 --- a/benchmarks/zdc_lyso/analysis/analysis.py +++ b/benchmarks/zdc_lyso/analysis/analysis.py @@ -82,7 +82,7 @@ def rotateY(xdata, zdata, angle): hist, x = np.histogram(temp,bins=np.linspace(min(temp),max(temp)+np.std(abs(temp)),2*int(np.sqrt(len(temp))))) x = x[1:]/2 + x[:-1]/2 plt.errorbar(x,hist,yerr=np.sqrt(hist),fmt='-o',label='Cluster') - coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0]))) + coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0])),maxfev=10000) #plt.plot(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),gaussian(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),*coeff)) mu.append(coeff[1]) sigma.append(coeff[2]) @@ -91,7 +91,7 @@ def rotateY(xdata, zdata, angle): hist, x = np.histogram(temp,bins=np.linspace(min(temp),max(temp)+np.std(abs(temp)),2*int(np.sqrt(len(temp))))) x = x[1:]/2 + x[:-1]/2 plt.errorbar(x,hist,yerr=np.sqrt(hist),fmt='-o',label='Digitization') - coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0]))) + coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0])),maxfev=10000) #plt.plot(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),gaussian(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),*coeff)) mu.append(coeff[1]) sigma.append(coeff[2]) @@ -100,7 +100,7 @@ def rotateY(xdata, zdata, angle): hist, x = np.histogram(temp,bins=np.linspace(min(temp),max(temp)+np.std(abs(temp)),2*int(np.sqrt(len(temp))))) x = x[1:]/2 + x[:-1]/2 plt.errorbar(x,hist,yerr=np.sqrt(hist),fmt='-o',label='Simulation') - coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0]))) + coeff, covar = curve_fit(gaussian,x[1:],hist[1:],p0=(max(hist[x>=np.std(abs(temp))]),np.mean(temp[temp!=0]),np.std(temp[temp!=0])),maxfev=10000) #plt.plot(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),gaussian(np.linspace(coeff[1]-3*coeff[2],coeff[1]+3*coeff[2],50),*coeff)) mu.append(coeff[1]) sigma.append(coeff[2]) diff --git a/benchmarks/zdc_lyso/config.yml b/benchmarks/zdc_lyso/config.yml index eb0a763b..990aaef2 100644 --- a/benchmarks/zdc_lyso/config.yml +++ b/benchmarks/zdc_lyso/config.yml @@ -2,7 +2,7 @@ sim:zdc_lyso: extends: .det_benchmark stage: simulate script: - - snakemake --cache --cores 1 zdc_lyso_local + - snakemake $SNAKEMAKE_FLAGS --cores 1 zdc_lyso_local retry: max: 2 when: @@ -16,5 +16,5 @@ collect_results:zdc_lyso: script: - ls -lrht - mv results{,_save}/ # move results directory out of the way to preserve it - - snakemake --cores 1 --delete-all-output zdc_lyso_local + - snakemake $SNAKEMAKE_FLAGS --cores 1 --delete-all-output zdc_lyso_local - mv results{_save,}/ diff --git a/benchmarks/zdc_photon/Snakefile b/benchmarks/zdc_photon/Snakefile new file mode 100644 index 00000000..2ca2ad3d --- /dev/null +++ b/benchmarks/zdc_photon/Snakefile @@ -0,0 +1,62 @@ +rule zdc_photon_generate: + input: + script="benchmarks/zdc_photon/analysis/gen_particles.cxx", + params: + th_max=0.23, + th_min=0 + output: + GEN_FILE="sim_output/zdc_photon/zdc_photon_{P}GeV.hepmc" + shell: + """ +NEVENTS_GEN=200 +mkdir -p sim_output/zdc_photon +root -l -b -q '{input.script}('$NEVENTS_GEN',"{output.GEN_FILE}", "gamma", {params.th_min}, {params.th_max}, 0., 360., {wildcards.P})' +""" + +rule zdc_photon_simulate: + input: + GEN_FILE="sim_output/zdc_photon/zdc_photon_{P}GeV.hepmc", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_photon/{DETECTOR_CONFIG}_sim_zdc_photon_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +# Running simulation +NEVENTS_SIM=200 +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --physicsList {params.PHYSICS_LIST} \ + --skipNEvents $(( $NEVENTS_SIM * {wildcards.INDEX} )) \ + --numberOfEvents $NEVENTS_SIM \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule zdc_photon_recon: + input: + SIM_FILE="sim_output/zdc_photon/{DETECTOR_CONFIG}_sim_zdc_photon_{P}GeV_{INDEX}.edm4hep.root" + output: + REC_FILE="sim_output/zdc_photon/{DETECTOR_CONFIG}_rec_zdc_photon_{P}GeV_{INDEX}.edm4eic.root" + shell: + """ +NEVENTS_REC=200 +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalFarForwardZDCRecHits,HcalFarForwardZDCClusters,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_photon_analysis: + input: + expand("sim_output/zdc_photon/{DETECTOR_CONFIG}_rec_zdc_photon_{P}GeV_{INDEX}.edm4eic.root", + P=[20, 30, 50, 70, 100, 150, 200, 275], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"], + INDEX=range(5), + ), + script="benchmarks/zdc_photon/analysis/zdc_photon_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/zdc_photon"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/zdc_photon/analysis/gen_particles.cxx b/benchmarks/zdc_photon/analysis/gen_particles.cxx new file mode 100644 index 00000000..0877a852 --- /dev/null +++ b/benchmarks/zdc_photon/analysis/gen_particles.cxx @@ -0,0 +1,127 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace HepMC3; + +// Generate single electron as input to the Insert simulation. +// -- +// We generate events with a constant polar angle with respect to +// the proton direction and then rotate so that the events are given +// in normal lab coordinate system +// -- +void gen_particles( + int n_events = 1000, + const char* out_fname = "gen_particles.hepmc", + TString particle_name = "e-", + double th_min = 3., // Minimum polar angle, in degrees + double th_max = 3., // Maximum polar angle, in degrees + double phi_min = 0., // Minimum azimuthal angle, in degrees + double phi_max = 360., // Maximum azimuthal angle, in degrees + double p = 10., // Momentum in GeV/c + int dist = 0, //Momentum distribution: 0=fixed, 1=uniform, 2=Gaussian + int useCrossingAngle=1 // 0= no rotation, 1 = -25 mrad + ) +{ + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(0); //Use time as random seed + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 111 - pi0 + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to proton direction + double phi = r1->Uniform(phi_min*TMath::DegToRad(),phi_max*TMath::DegToRad()); + double th = r1->Uniform(th_min*TMath::DegToRad(),th_max*TMath::DegToRad()); + + //Total momentum distribution + double pevent = -1; + if(dist==0){ //fixed + pevent = p; + } + else if(dist==1){ //Uniform: +-50% variation + pevent = p*(1. + r1->Uniform(-0.5,0.5) ); + } + else if(dist==2){ //Gaussian: Sigma = 0.1*mean + while(pevent<0) //Avoid negative values + pevent = r1->Gaus(p,0.1*p); + } + + double px = pevent * std::cos(phi) * std::sin(th); + double py = pevent * std::sin(phi) * std::sin(th); + double pz = pevent * std::cos(th); + TVector3 pvec(px,py,pz); + + //Rotate to lab coordinate system + double cross_angle = -25./1000.*useCrossingAngle; //in Rad + TVector3 pbeam_dir(sin(cross_angle),0,cos(cross_angle)); //proton beam direction + pvec.RotateY(-pbeam_dir.Theta()); // Theta is returned positive, beam in negative X + // type 1 is final state + // pdgid 11 - electron 0.510 MeV/c^2 + GenParticlePtr p3 = std::make_shared( + FourVector( + pvec.X(), pvec.Y(), pvec.Z(), + sqrt(pevent*pevent + (mass * mass))), + pdgID, 1); + + //If wanted, set non-zero vertex + double vx = 0.; + double vy = 0.; + double vz = 0.; + double vt = 0.; + + GenVertexPtr v1 = std::make_shared(); + evt.shift_position_by(FourVector(vx, vy, vz, vt)); + v1->add_particle_in(p1); + v1->add_particle_in(p2); + + v1->add_particle_out(p3); + evt.add_vertex(v1); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + + hepmc_output.write_event(evt); + if (events_parsed % 100 == 0) { + std::cout << "Event: " << events_parsed << std::endl; + } + evt.clear(); + } + hepmc_output.close(); + std::cout << "Events parsed and written: " << events_parsed << std::endl; +} diff --git a/benchmarks/zdc_photon/analysis/zdc_photon_plots.py b/benchmarks/zdc_photon/analysis/zdc_photon_plots.py new file mode 100644 index 00000000..a2cf5339 --- /dev/null +++ b/benchmarks/zdc_photon/analysis/zdc_photon_plots.py @@ -0,0 +1,158 @@ +import numpy as np, pandas as pd, matplotlib.pyplot as plt, matplotlib as mpl, awkward as ak, sys +import mplhep as hep +hep.style.use("CMS") + +plt.rcParams['figure.facecolor']='white' +plt.rcParams['savefig.facecolor']='white' +plt.rcParams['savefig.bbox']='tight' + + +outdir=sys.argv[1]+"/" +config=outdir.split("/")[1] +try: + import os + os.mkdir(outdir[:-1]) +except: + pass + +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) + +#load the files +import uproot as ur +arrays_sim={} +momenta=20, 30, 50, 70, 100, 150, 200, 275 +for p in momenta: + arrays_sim[p] = ur.concatenate({ + f'sim_output/zdc_photon/{config}_rec_zdc_photon_{p}GeV_{index}.edm4eic.root': 'events' + for index in range(5) + }) + +fig,axs=plt.subplots(1,3, figsize=(24, 8)) +pvals=[] +resvals=[] +dresvals=[] +scalevals=[] +dscalevals=[] +for p in momenta: + selection=[len(arrays_sim[p]["HcalFarForwardZDCClusters.energy"][i])==1 for i in range(len(arrays_sim[p]))] + E=arrays_sim[p][selection]["HcalFarForwardZDCClusters.energy"] + + Etot=np.sum(E, axis=-1) + #print(len(Etot)) + #print(p, res, mrecon) + if p==100: + plt.sca(axs[0]) + y, x, _=plt.hist(Etot, bins=100, range=(p*.75, p*1.25), histtype='step') + plt.ylabel("events") + plt.title(f"$p_{{\gamma}}$={p} GeV") + plt.xlabel("$E^{\\gamma}_{recon}$ [GeV]") + else: + y, x = np.histogram(Etot, bins=100, range=(p*.75, p*1.25)) + + bc=(x[1:]+x[:-1])/2 + from scipy.optimize import curve_fit + slc=abs(bc-p)<10 + fnc=gauss + p0=[100, p, 10] + #print(list(y), list(x)) + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0)), maxfev=10000) + if p==100: + xx=np.linspace(p*0.75,p*1.25, 100) + plt.plot(xx, fnc(xx,*coeff)) + pvals.append(p) + resvals.append(np.abs(coeff[2])/coeff[1]) + dresvals.append(np.sqrt(var_matrix[2][2])/coeff[1]) + scalevals.append(np.abs(coeff[1])/p) + dscalevals.append(np.sqrt(var_matrix[2][2])/p) + +plt.sca(axs[1]) +plt.errorbar(pvals, resvals, dresvals, ls='', marker='o') +plt.ylim(0) +plt.ylabel("$\\sigma[E_{\\gamma}]/\\mu[E_{\\gamma}]$") +plt.xlabel("$p_{\\gamma}$ [GeV]") + +xx=np.linspace(15, 275, 100) + +fnc=lambda E,a: a/np.sqrt(E) +#pvals, resvals, dresvals +coeff, var_matrix = curve_fit(fnc, pvals, resvals, p0=(1,), + sigma=dresvals, maxfev=10000) + +xx=np.linspace(15, 275, 100) +plt.plot(xx, fnc(xx, *coeff), label=f'fit: $\\frac{{{coeff[0]*100:.0f}\\%}}{{\\sqrt{{E}}}}$') +plt.legend() +plt.sca(axs[2]) +plt.errorbar(pvals, scalevals, dscalevals, ls='', marker='o') +plt.ylim(0.8, 1.2) +plt.ylabel("$\\mu[E_{\\gamma}]/E_{\\gamma}$") +plt.xlabel("$p_{\\gamma}$ [GeV]") +plt.axhline(1, ls='--', alpha=0.7, color='0.5') +plt.tight_layout() +plt.savefig(outdir+"photon_energy_res.pdf") + +#theta res +fig,axs=plt.subplots(1,2, figsize=(16, 8)) +pvals=[] +resvals=[] +dresvals=[] +for p in momenta: + selection=[len(arrays_sim[p]["HcalFarForwardZDCClusters.energy"][i])==1 for i in range(len(arrays_sim[p]))] + x=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.x"][::,0] + y=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.y"][::,0] + z=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.z"][::,0] + + theta_recon=np.arctan2(np.hypot(x*np.cos(-.025)-z*np.sin(-.025), y), z*np.cos(-.025)+x*np.sin(-.025)) + + px=arrays_sim[p][selection]["MCParticles.momentum.x"][::,2] + py=arrays_sim[p][selection]["MCParticles.momentum.y"][::,2] + pz=arrays_sim[p][selection]["MCParticles.momentum.z"][::,2] + + theta_truth=np.arctan2(np.hypot(px*np.cos(-.025)-pz*np.sin(-.025), py), pz*np.cos(-.025)+px*np.sin(-.025)) + + Etot=np.sum(E, axis=-1) + #print(p, res, mrecon) + if p==100: + plt.sca(axs[0]) + y, x, _=plt.hist(1000*(theta_recon-theta_truth), bins=100, range=(-0.5, 0.5), histtype='step') + plt.ylabel("events") + plt.title(f"$p_{{\gamma}}$={p} GeV") + plt.xlabel("$\\theta^{\\gamma}_{recon}$ [mrad]") + else: + y, x = np.histogram(1000*(theta_recon-theta_truth), bins=100, range=(-0.5, 0.5)) + + bc=(x[1:]+x[:-1])/2 + from scipy.optimize import curve_fit + slc=abs(bc)<0.2#1.5*np.std(1000*(theta_recon-theta_truth)) + fnc=gauss + p0=[100, 0, 0.1] + #print(list(y), list(x)) + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0)), maxfev=10000) + if p==100: + xx=np.linspace(-0.5,0.5, 100) + plt.plot(xx, fnc(xx,*coeff)) + pvals.append(p) + resvals.append(np.abs(coeff[2])) + dresvals.append(np.sqrt(var_matrix[2][2])) +plt.sca(axs[1]) +plt.errorbar(pvals, resvals, dresvals, ls='', marker='o') +#print(dresvals) + +fnc=lambda E,a, b: np.hypot(a/np.sqrt(E), b) +#pvals, resvals, dresvals +coeff, var_matrix = curve_fit(fnc, pvals, resvals, p0=(1,.1), + sigma=dresvals, maxfev=10000) + +xx=np.linspace(15, 275, 100) + +plt.plot(xx, fnc(xx, *coeff), label=f'fit: $\\frac{{{coeff[0]:.2f}}}{{\\sqrt{{E}}}}\\oplus {coeff[1]:.3f}$ mrad') + +plt.ylabel("$\\sigma[\\theta_{\\gamma}]$ [mrad]") +plt.xlabel("$p_{\\gamma}$ [GeV]") + +plt.ylim(0, 0.1) +plt.legend() +plt.tight_layout() +plt.savefig(outdir+"photon_theta_res.pdf") diff --git a/benchmarks/zdc_photon/config.yml b/benchmarks/zdc_photon/config.yml new file mode 100644 index 00000000..9814792a --- /dev/null +++ b/benchmarks/zdc_photon/config.yml @@ -0,0 +1,34 @@ +sim:zdc_photon: + extends: .det_benchmark + stage: simulate + parallel: + matrix: + - P: 20 + - P: 30 + - P: 50 + - P: 70 + - P: 100 + - P: 150 + - P: 200 + - P: 275 + script: + - snakemake $SNAKEMAKE_FLAGS --cores 5 sim_output/zdc_photon/epic_zdc_sipm_on_tile_only_rec_zdc_photon_${P}GeV_{0,1,2,3,4}.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_photon: + extends: .det_benchmark + stage: benchmarks + needs: ["sim:zdc_photon"] + script: + - mkdir -p results/epic_zdc_sipm_on_tile_only + - python benchmarks/zdc_photon/analysis/zdc_photon_plots.py results/epic_zdc_sipm_on_tile_only/zdc_photon + +collect_results:zdc_photon: + extends: .det_benchmark + stage: collect + needs: ["bench:zdc_photon"] + script: + - ls -al diff --git a/benchmarks/zdc_pi0/Snakefile b/benchmarks/zdc_pi0/Snakefile new file mode 100644 index 00000000..c2a67993 --- /dev/null +++ b/benchmarks/zdc_pi0/Snakefile @@ -0,0 +1,60 @@ +rule zdc_pi0_generate: + input: + script="benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx", + params: + NEVENTS_GEN=1000, + output: + GEN_FILE="sim_output/zdc_pi0/zdc_pi0_{P}GeV.hepmc" + shell: + """ +mkdir -p sim_output/zdc_pi0 +root -l -b -q '{input.script}({params.NEVENTS_GEN},0,"{output.GEN_FILE}",{wildcards.P},{wildcards.P})' +""" + +rule zdc_pi0_simulate: + input: + GEN_FILE="sim_output/zdc_pi0/zdc_pi0_{P}GeV.hepmc", + warmup="warmup/{DETECTOR_CONFIG}.edm4hep.root", + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_pi0/{DETECTOR_CONFIG}_sim_zdc_pi0_{P}GeV_{INDEX}.edm4hep.root" + shell: + """ +NEVENTS_SIM=200 +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --skipNEvents $(( $NEVENTS_SIM * {wildcards.INDEX} )) \ + --numberOfEvents $NEVENTS_SIM \ + --physicsList {params.PHYSICS_LIST} \ + --inputFiles {input.GEN_FILE} \ + --outputFile {output.SIM_FILE} +""" + +rule zdc_pi0_recon: + input: + SIM_FILE="sim_output/zdc_pi0/{DETECTOR_CONFIG}_sim_zdc_pi0_{P}GeV_{INDEX}.edm4hep.root" + output: + REC_FILE="sim_output/zdc_pi0/{DETECTOR_CONFIG}_rec_zdc_pi0_{P}GeV_{INDEX}.edm4eic.root" + shell: + """ +NEVENTS_REC=200 +eicrecon {input.SIM_FILE} -Ppodio:output_file={output.REC_FILE} -Pdd4hep:xml_files=$DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml -Ppodio:output_collections=MCParticles,HcalFarForwardZDCRecHits,HcalFarForwardZDCClusters,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_pi0_analysis: + input: + expand("sim_output/zdc_pi0/{DETECTOR_CONFIG}_rec_zdc_pi0_{P}GeV_{INDEX}.edm4eic.root", + P=[60, 80, 100, 130, 160], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"], + INDEX=range(5), + ), + script="benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/zdc_pi0"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx b/benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx new file mode 100644 index 00000000..13b3d807 --- /dev/null +++ b/benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx @@ -0,0 +1,182 @@ +#include "HepMC3/GenEvent.h" +#include "HepMC3/ReaderAscii.h" +#include "HepMC3/WriterAscii.h" +#include "HepMC3/Print.h" + +#include "TRandom3.h" +#include "TVector3.h" + +#include +#include + +#include +#include +#include + +using namespace HepMC3; + +std::tuple GetParticleInfo(TDatabasePDG* pdg, TString particle_name) +{ + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + const double lifetime = particle->Lifetime(); + return std::make_tuple(mass, pdgID, lifetime); +} +// Calculates the decay length of a particle. Samples from an exponential decay. +double GetDecayLength(TRandom3* r1, double lifetime, double mass, double momentum_magnitude) +{ + double c_speed = TMath::C() * 1000.; // speed of light im mm/sec + double average_decay_length = (momentum_magnitude/mass) * lifetime * c_speed; + return r1->Exp(average_decay_length); +} + +// Generate single pi0 mesons and decay them to 2 photons +void gen_pi0_decay(int n_events = 100000, UInt_t seed = 0, char* out_fname = "pi0_decay.hepmc", + double p_min = 60., // in GeV/c + double p_max = 275.) // in GeV/c +{ + + const double theta_min = 0.0; // in mRad + const double theta_max = 4; // in mRad + //const double p_min = 100.; // in GeV/c + //const double p_max = 275.; // in GeV/c + + WriterAscii hepmc_output(out_fname); + int events_parsed = 0; + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(seed); //Default = 0, which uses clock to set seed + cout<<"Random number seed is "<GetSeed()<<"!"<(pi0_info); + int pi0_pdgID = std::get<1>(pi0_info); + double pi0_lifetime = std::get<2>(pi0_info); + + auto photon_info = GetParticleInfo(pdg, "gamma"); + double photon_mass = std::get<0>(photon_info); + int photon_pdgID = std::get<1>(photon_info); + + for (events_parsed = 0; events_parsed < n_events; events_parsed++) { + + //Set the event number + evt.set_event_number(events_parsed); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to EIC proton beam direction + Double_t pi0_p = r1->Uniform(p_min, p_max); + Double_t pi0_phi = r1->Uniform(0.0, 2.0 * M_PI); + Double_t pi0_th = r1->Uniform(theta_min/1000., theta_max/1000.); // Divide by 1000 for radians + Double_t pi0_px = pi0_p * TMath::Cos(pi0_phi) * TMath::Sin(pi0_th); + Double_t pi0_py = pi0_p * TMath::Sin(pi0_phi) * TMath::Sin(pi0_th); + Double_t pi0_pz = pi0_p * TMath::Cos(pi0_th); + Double_t pi0_E = TMath::Sqrt(pi0_p*pi0_p + pi0_mass*pi0_mass); + + // Rotate to lab coordinate system + TVector3 pi0_pvec(pi0_px, pi0_py, pi0_pz); + double cross_angle = -25./1000.; // in Rad + TVector3 pbeam_dir(TMath::Sin(cross_angle), 0, TMath::Cos(cross_angle)); //proton beam direction + pi0_pvec.RotateY(cross_angle); // Theta is returned positive, beam in negative X + + // type 2 is state that will decay + GenParticlePtr p_pi0 = std::make_shared( + FourVector(pi0_pvec.X(), pi0_pvec.Y(), pi0_pvec.Z(), pi0_E), pi0_pdgID, 2 ); + + // Generating pi0 particle, will be generated at origin + // Must have input electron + proton for vertex + GenVertexPtr pi0_initial_vertex = std::make_shared(); + pi0_initial_vertex->add_particle_in(p1); + pi0_initial_vertex->add_particle_in(p2); + pi0_initial_vertex->add_particle_out(p_pi0); + evt.add_vertex(pi0_initial_vertex); + + // Generate gamma1 + gamma1 in pi0 rest frame + TLorentzVector gamma1_rest, gamma2_rest; + + // Generating uniformly along a sphere + double cost_gamma1_rest = r1->Uniform(-1,1); + double th_gamma1_rest = TMath::ACos(cost_gamma1_rest); + double sint_gamma1_rest = TMath::Sin(th_gamma1_rest); + + double phi_gamma1_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_gamma1_rest = TMath::Cos(phi_gamma1_rest); + double sinp_gamma1_rest = TMath::Sin(phi_gamma1_rest); + + // Calculate energy of each particle in the pi0 rest frame + // This is half the pi0 mass + double E_gamma1_rest = pi0_mass/2; + double E_gamma2_rest = pi0_mass/2; + + // Both particles will have the same momentum, and are massless + double momentum_rest = pi0_mass/2; + + gamma1_rest.SetE(E_gamma1_rest); + gamma1_rest.SetPx( momentum_rest * sint_gamma1_rest * cosp_gamma1_rest ); + gamma1_rest.SetPy( momentum_rest * sint_gamma1_rest * sinp_gamma1_rest ); + gamma1_rest.SetPz( momentum_rest * cost_gamma1_rest ); + + gamma2_rest.SetE(E_gamma2_rest); + gamma2_rest.SetPx( -gamma1_rest.Px() ); + gamma2_rest.SetPy( -gamma1_rest.Py() ); + gamma2_rest.SetPz( -gamma1_rest.Pz() ); + + // Boost both gammas to lab frame + TLorentzVector pi0_lab(pi0_pvec.X(), pi0_pvec.Y(), pi0_pvec.Z(), pi0_E); + TVector3 pi0_boost = pi0_lab.BoostVector(); + TLorentzVector gamma1_lab, gamma2_lab; + gamma1_lab = gamma1_rest; + gamma1_lab.Boost(pi0_boost); + gamma2_lab = gamma2_rest; + gamma2_lab.Boost(pi0_boost); + + // Calculating position for pi0 decay + TVector3 pi0_unit = pi0_lab.Vect().Unit(); + double pi0_decay_length = GetDecayLength(r1, pi0_lifetime, pi0_mass, pi0_lab.P()); + TVector3 pi0_decay_position = pi0_unit * pi0_decay_length; + double pi0_decay_time = pi0_decay_length / pi0_lab.Beta() ; // Decay time in lab frame in length units (mm) + + // Generating vertex for pi0 decay + GenParticlePtr p_gamma1 = std::make_shared( + FourVector(gamma1_lab.Px(), gamma1_lab.Py(), gamma1_lab.Pz(), gamma1_lab.E()), photon_pdgID, 1 ); + + GenParticlePtr p_gamma2 = std::make_shared( + FourVector(gamma2_lab.Px(), gamma2_lab.Py(), gamma2_lab.Pz(), gamma2_lab.E()), photon_pdgID, 1 ); + + GenVertexPtr v_pi0_decay = std::make_shared(FourVector(pi0_decay_position.X(), pi0_decay_position.Y(), pi0_decay_position.Z(), pi0_decay_time)); + v_pi0_decay->add_particle_in(p_pi0); + v_pi0_decay->add_particle_out(p_gamma1); + v_pi0_decay->add_particle_out(p_gamma2); + + evt.add_vertex(v_pi0_decay); + + if (events_parsed == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + double zdc_z=35800; + TVector3 extrap_gamma1=pi0_decay_position+gamma1_lab.Vect()*((zdc_z-pbeam_dir.Dot(pi0_decay_position))/(pbeam_dir.Dot(gamma1_lab.Vect()))); + TVector3 extrap_gamma2=pi0_decay_position+gamma2_lab.Vect()*((zdc_z-pbeam_dir.Dot(pi0_decay_position))/(pbeam_dir.Dot(gamma2_lab.Vect()))); + if (extrap_gamma1.Angle(pbeam_dir)<0.004 && extrap_gamma2.Angle(pbeam_dir)<0.004 && pi0_decay_position.Dot(pbeam_dir) +#include + +#include +#include +#include + +using namespace HepMC3; + +std::tuple GetParticleInfo(TDatabasePDG* pdg, TString particle_name) +{ + TParticlePDG *particle = pdg->GetParticle(particle_name); + const double mass = particle->Mass(); + const int pdgID = particle->PdgCode(); + const double lifetime = particle->Lifetime(); + return std::make_tuple(mass, pdgID, lifetime); +} +// Calculates the decay length of a particle. Samples from an exponential decay. +double GetDecayLength(TRandom3* r1, double lifetime, double mass, double momentum_magnitude) +{ + double c_speed = TMath::C() * 1000.; // speed of light im mm/sec + double average_decay_length = (momentum_magnitude/mass) * lifetime * c_speed; + return r1->Exp(average_decay_length); +} + +// Generate single sigma baryons and decay them to a neutron + 2 photons +void gen_sigma_decay(int n_events = 100000, UInt_t seed = 0, char* out_fname = "sigma_decay.hepmc", + double p_min = 100., // in GeV/c + double p_max = 275.) // in GeV/c +{ + const double theta_min = 0.0; // in mRad + const double theta_max = 3.0; // in mRad + //const double p_min = 100.; // in GeV/c + //const double p_max = 275.; // in GeV/c + + WriterAscii hepmc_output(out_fname); + GenEvent evt(Units::GEV, Units::MM); + + // Random number generator + TRandom3 *r1 = new TRandom3(seed); //Default = 0, which uses clock to set seed + cout<<"Random number seed is "<GetSeed()<<"!"<(sigma_info); + int sigma_pdgID = std::get<1>(sigma_info); + double sigma_lifetime = std::get<2>(sigma_info); + + auto lambda_info = GetParticleInfo(pdg, "Lambda0"); + double lambda_mass = std::get<0>(lambda_info); + int lambda_pdgID = std::get<1>(lambda_info); + double lambda_lifetime = std::get<2>(lambda_info); + + auto neutron_info = GetParticleInfo(pdg, "neutron"); + double neutron_mass = std::get<0>(neutron_info); + int neutron_pdgID = std::get<1>(neutron_info); + + auto pi0_info = GetParticleInfo(pdg, "pi0"); + double pi0_mass = std::get<0>(pi0_info); + int pi0_pdgID = std::get<1>(pi0_info); + double pi0_lifetime = std::get<2>(pi0_info); + + auto photon_info = GetParticleInfo(pdg, "gamma"); + double photon_mass = std::get<0>(photon_info); + int photon_pdgID = std::get<1>(photon_info); + + int accepted_events = 0; + while (accepted_events < n_events) { + + //Set the event number + evt.set_event_number(accepted_events); + + // FourVector(px,py,pz,e,pdgid,status) + // type 4 is beam + // pdgid 11 - electron + // pdgid 2212 - proton + GenParticlePtr p1 = + std::make_shared(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared( + FourVector(0.0, 0.0, 0.0, 0.938), 2212, 4); + + // Define momentum with respect to EIC proton beam direction + Double_t sigma_p = r1->Uniform(p_min, p_max); + Double_t sigma_phi = r1->Uniform(0.0, 2.0 * M_PI); + Double_t sigma_th = r1->Uniform(theta_min/1000., theta_max/1000.); // Divide by 1000 for radians + Double_t sigma_px = sigma_p * TMath::Cos(sigma_phi) * TMath::Sin(sigma_th); + Double_t sigma_py = sigma_p * TMath::Sin(sigma_phi) * TMath::Sin(sigma_th); + Double_t sigma_pz = sigma_p * TMath::Cos(sigma_th); + Double_t sigma_E = TMath::Sqrt(sigma_p*sigma_p + sigma_mass*sigma_mass); + + + TVector3 sigma_pvec(sigma_px, sigma_py, sigma_pz); + + double cross_angle = -25./1000.; // in Rad + TVector3 pbeam_dir(TMath::Sin(cross_angle), 0, TMath::Cos(cross_angle)); //proton beam direction + sigma_pvec.RotateY(cross_angle); // Theta is returned positive, beam in negative X + + // type 2 is state that will decay + GenParticlePtr p_sigma = std::make_shared( + FourVector(sigma_pvec.X(), sigma_pvec.Y(), sigma_pvec.Z(), sigma_E), sigma_pdgID, 2 ); + // Generating sigma particle, will be generated at origin + // Must have input electron + proton for vertex + GenVertexPtr sigma_initial_vertex = std::make_shared(); + sigma_initial_vertex->add_particle_in(p1); + sigma_initial_vertex->add_particle_in(p2); + sigma_initial_vertex->add_particle_out(p_sigma); + evt.add_vertex(sigma_initial_vertex); + + // Generate lambda + gamma in sigma rest frame + TLorentzVector lambda_rest, gamma_rest; + + // Generating uniformly along a sphere + double cost_lambda_rest = r1->Uniform(-1,1); + double th_lambda_rest = TMath::ACos(cost_lambda_rest); + double sint_lambda_rest = TMath::Sin(th_lambda_rest); + + double phi_lambda_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_lambda_rest = TMath::Cos(phi_lambda_rest); + double sinp_lambda_rest = TMath::Sin(phi_lambda_rest); + + // Calculate energy of each particle in the sigma rest frame + // See problem 3.19 in Introduction to Elementary Particles, 2nd edition by D. Griffiths + double E_lambda_rest = (-TMath::Power(photon_mass, 2.) + TMath::Power(sigma_mass, 2.) + TMath::Power(lambda_mass, 2.) ) / (2. * sigma_mass) ; + double E_gamma_rest = (-TMath::Power(lambda_mass, 2.) + TMath::Power(sigma_mass, 2.) + TMath::Power(photon_mass, 2.) ) / (2. * sigma_mass) ; + + // Both particles will have the same momentum, so just use lambda variables + double momentum_rest = TMath::Sqrt( E_lambda_rest*E_lambda_rest - lambda_mass*lambda_mass ); + + lambda_rest.SetE(E_lambda_rest); + lambda_rest.SetPx( momentum_rest * sint_lambda_rest * cosp_lambda_rest ); + lambda_rest.SetPy( momentum_rest * sint_lambda_rest * sinp_lambda_rest ); + lambda_rest.SetPz( momentum_rest * cost_lambda_rest ); + + gamma_rest.SetE(E_gamma_rest); + gamma_rest.SetPx( -lambda_rest.Px() ); + gamma_rest.SetPy( -lambda_rest.Py() ); + gamma_rest.SetPz( -lambda_rest.Pz() ); + + // Boost lambda & pion to lab frame + TLorentzVector sigma_lab(sigma_pvec.X(), sigma_pvec.Y(), sigma_pvec.Z(), sigma_E); + TVector3 sigma_boost = sigma_lab.BoostVector(); + TLorentzVector lambda_lab, gamma_lab; + lambda_lab = lambda_rest; + lambda_lab.Boost(sigma_boost); + gamma_lab = gamma_rest; + gamma_lab.Boost(sigma_boost); + + // Calculating position for sigma decay + TVector3 sigma_unit = sigma_lab.Vect().Unit(); + double sigma_decay_length = GetDecayLength(r1, sigma_lifetime, sigma_mass, sigma_lab.P()); + TVector3 sigma_decay_position = sigma_unit * sigma_decay_length; + double sigma_decay_time = sigma_decay_length / sigma_lab.Beta() ; // Decay time in lab frame in length units (mm) + + // Generating vertex for sigma decay + GenParticlePtr p_lambda = std::make_shared( + FourVector(lambda_lab.Px(), lambda_lab.Py(), lambda_lab.Pz(), lambda_lab.E()), lambda_pdgID, 2 ); + + GenParticlePtr p_gamma = std::make_shared( + FourVector(gamma_lab.Px(), gamma_lab.Py(), gamma_lab.Pz(), gamma_lab.E()), photon_pdgID, 1 ); + + GenVertexPtr v_sigma_decay = std::make_shared(FourVector(sigma_decay_position.X(), sigma_decay_position.Y(), sigma_decay_position.Z(), sigma_decay_time)); + v_sigma_decay->add_particle_in(p_sigma); + v_sigma_decay->add_particle_out(p_lambda); + v_sigma_decay->add_particle_out(p_gamma); + + evt.add_vertex(v_sigma_decay); + + // Generate neutron + pi0 in lambda rest frame + TLorentzVector neutron_rest, pi0_rest; + + // Generating uniformly along a sphere + double cost_neutron_rest = r1->Uniform(-1,1); + double th_neutron_rest = TMath::ACos(cost_neutron_rest); + double sint_neutron_rest = TMath::Sin(th_neutron_rest); + + double phi_neutron_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_neutron_rest = TMath::Cos(phi_neutron_rest); + double sinp_neutron_rest = TMath::Sin(phi_neutron_rest); + + // Calculate energy of each particle in the lambda rest frame + // See problem 3.19 in Introduction to Elementary Particles, 2nd edition by D. Griffiths + double E_neutron_rest = (-TMath::Power(pi0_mass, 2.) + TMath::Power(lambda_mass, 2.) + TMath::Power(neutron_mass, 2.) ) / (2. * lambda_mass) ; + double E_pi0_rest = (-TMath::Power(neutron_mass, 2.) + TMath::Power(lambda_mass, 2.) + TMath::Power(pi0_mass, 2.) ) / (2. * lambda_mass) ; + + // Both particles will have the same momentum, so just use neutron variables + momentum_rest = TMath::Sqrt( E_neutron_rest*E_neutron_rest - neutron_mass*neutron_mass ); + + neutron_rest.SetE(E_neutron_rest); + neutron_rest.SetPx( momentum_rest * sint_neutron_rest * cosp_neutron_rest ); + neutron_rest.SetPy( momentum_rest * sint_neutron_rest * sinp_neutron_rest ); + neutron_rest.SetPz( momentum_rest * cost_neutron_rest ); + + pi0_rest.SetE(E_pi0_rest); + pi0_rest.SetPx( -neutron_rest.Px() ); + pi0_rest.SetPy( -neutron_rest.Py() ); + pi0_rest.SetPz( -neutron_rest.Pz() ); + + // Boost neutron & pion to lab frame + TVector3 lambda_boost = lambda_lab.BoostVector(); + TLorentzVector neutron_lab, pi0_lab; + neutron_lab = neutron_rest; + neutron_lab.Boost(lambda_boost); + pi0_lab = pi0_rest; + pi0_lab.Boost(lambda_boost); + + // Calculating position for lambda decay + TVector3 lambda_unit = lambda_lab.Vect().Unit(); + double lambda_decay_length = GetDecayLength(r1, lambda_lifetime, lambda_mass, lambda_lab.P()); + TVector3 lambda_decay_position = lambda_unit * lambda_decay_length; + double lambda_decay_time = lambda_decay_length / lambda_lab.Beta() ; // Decay time in lab frame in length units (mm) + + // Generating vertex for lambda decay + GenParticlePtr p_neutron = std::make_shared( + FourVector(neutron_lab.Px(), neutron_lab.Py(), neutron_lab.Pz(), neutron_lab.E()), neutron_pdgID, 1 ); + + GenParticlePtr p_pi0 = std::make_shared( + FourVector(pi0_lab.Px(), pi0_lab.Py(), pi0_lab.Pz(), pi0_lab.E()), pi0_pdgID, 2 ); + + GenVertexPtr v_lambda_decay = std::make_shared(FourVector(lambda_decay_position.X(), lambda_decay_position.Y(), lambda_decay_position.Z(), lambda_decay_time)); + v_lambda_decay->add_particle_in(p_lambda); + v_lambda_decay->add_particle_out(p_neutron); + v_lambda_decay->add_particle_out(p_pi0); + + evt.add_vertex(v_lambda_decay); + + // Generate two photons from pi0 decay + TLorentzVector gamma1_rest, gamma2_rest; + + // Generating uniformly along a sphere + double cost_gamma1_rest = r1->Uniform(-1,1); + double th_gamma1_rest = TMath::ACos(cost_gamma1_rest); + double sint_gamma1_rest = TMath::Sin(th_gamma1_rest); + + double phi_gamma1_rest = r1->Uniform(-1.*TMath::Pi(),1.*TMath::Pi()); + double cosp_gamma1_rest = TMath::Cos(phi_gamma1_rest); + double sinp_gamma1_rest = TMath::Sin(phi_gamma1_rest); + + // Photons are massless so they each get equal energies + gamma1_rest.SetE(pi0_mass/2.); + gamma1_rest.SetPx( (pi0_mass/2.)*sint_gamma1_rest*cosp_gamma1_rest ); + gamma1_rest.SetPy( (pi0_mass/2.)*sint_gamma1_rest*sinp_gamma1_rest ); + gamma1_rest.SetPz( (pi0_mass/2.)*cost_gamma1_rest ); + + gamma2_rest.SetE(pi0_mass/2.); + gamma2_rest.SetPx( -gamma1_rest.Px() ); + gamma2_rest.SetPy( -gamma1_rest.Py() ); + gamma2_rest.SetPz( -gamma1_rest.Pz() ); + + // Boost neutron & pion to lab frame + TVector3 pi0_boost = pi0_lab.BoostVector(); + TLorentzVector gamma1_lab, gamma2_lab; + gamma1_lab = gamma1_rest; + gamma1_lab.Boost(pi0_boost); + gamma2_lab = gamma2_rest; + gamma2_lab.Boost(pi0_boost); + + GenParticlePtr p_gamma1 = std::make_shared( + FourVector(gamma1_lab.Px(), gamma1_lab.Py(), gamma1_lab.Pz(), gamma1_lab.E()), photon_pdgID, 1 ); + + GenParticlePtr p_gamma2 = std::make_shared( + FourVector(gamma2_lab.Px(), gamma2_lab.Py(), gamma2_lab.Pz(), gamma2_lab.E()), photon_pdgID, 1 ); + + // Generate pi0 at same position as the lambda. Approximating pi0 decay as instantaneous + GenVertexPtr v_pi0_decay = std::make_shared(FourVector(lambda_decay_position.X(), lambda_decay_position.Y(), lambda_decay_position.Z(), lambda_decay_time)); + v_pi0_decay->add_particle_in(p_pi0); + v_pi0_decay->add_particle_out(p_gamma1); + v_pi0_decay->add_particle_out(p_gamma2); + + //std::cout<< lambda_pvec.Angle(pbeam_dir) << " " << neutron_lab.Angle(pbeam_dir) << " " << gamma1_lab.Angle(pbeam_dir) << " " << gamma2_lab.Angle(pbeam_dir) << std::endl; + + evt.add_vertex(v_pi0_decay); + + if (accepted_events == 0) { + std::cout << "First event: " << std::endl; + Print::listing(evt); + } + double zdc_z=35800; + TVector3 extrap_gamma=sigma_decay_position+gamma_lab.Vect()*((zdc_z-pbeam_dir.Dot(sigma_decay_position))/(pbeam_dir.Dot(gamma_lab.Vect()))); + TVector3 extrap_neutron=lambda_decay_position+neutron_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(neutron_lab.Vect()))); + TVector3 extrap_gamma1=lambda_decay_position+gamma1_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(gamma1_lab.Vect()))); + TVector3 extrap_gamma2=lambda_decay_position+gamma2_lab.Vect()*((zdc_z-pbeam_dir.Dot(lambda_decay_position))/(pbeam_dir.Dot(gamma2_lab.Vect()))); + if (extrap_neutron.Angle(pbeam_dir)<0.004 && extrap_gamma1.Angle(pbeam_dir)<0.004 && extrap_gamma2.Angle(pbeam_dir)<0.004 && extrap_gamma.Angle(pbeam_dir)<0.004 && lambda_decay_position.Dot(pbeam_dir)max_val): + max_val=largest_eigenvalue + index_of_max=j + if index_of_max >=0: + is_neutron_cand[p][i][index_of_max]=1 + eigs.sort() + + is_neutron_cand[p]=ak.Array(is_neutron_cand[p]) + +import ROOT + +lambda_mass=1.115683 +pi0_mass=0.1349768 +pt_recon_corr={} +theta_recon_corr={} +mass_recon_corr={} +mass_lambda_recon_corr={} +mass_pi0_recon_corr={} +pi0_converged={} +zvtx_recon={} + +#run this event-by-event: +maxZ=35800 +for p in momenta: + pt_recon_corr[p]=[] + theta_recon_corr[p]=[] + mass_recon_corr[p]=[] + mass_lambda_recon_corr[p]=[] + mass_pi0_recon_corr[p]=[] + zvtx_recon[p]=[] + for evt in range(len(arrays_sim[p])): + if nclusters[p][evt]!=4: + nan=-1 + pt_recon_corr[p].append(nan) + theta_recon_corr[p].append(nan) + mass_recon_corr[p].append(nan) + mass_lambda_recon_corr[p].append(nan) + mass_pi0_recon_corr[p].append(nan) + zvtx_recon[p].append(nan) + continue + xc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.x"][evt] + yc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.y"][evt] + zc=arrays_sim[p][f"HcalFarForwardZDCClusters.position.z"][evt] + E=arrays_sim[p][f"HcalFarForwardZDCClusters.energy"][evt] + + #apply correction to the neutron candidates only + A,B,C=-0.0756, -1.91, 2.30 + neutron_corr=(1-is_neutron_cand[p][evt])+is_neutron_cand[p][evt]/(1+A+B/np.sqrt(E)+C/E) + E=E*neutron_corr + + pabs=np.sqrt(E**2-is_neutron_cand[p][evt]*.9406**2) + tilt=-0.025 + xcp=xc*np.cos(tilt)-zc*np.sin(tilt) + ycp=yc + zcp=zc*np.cos(tilt)+xc*np.sin(tilt) + + #search for the combination of photons that would give the best lambda mass + pt_best=-999 + theta_best=-999 + mass_lambda_best=-999 + mass_sigma_best=-999 + mass_pi0_best=-999 + zvtx_best=-999 + for hypothesis in range(4): + if is_neutron_cand[p][evt][hypothesis]: + continue + + xvtx=0 + yvtx=0 + zvtx=0 + #find the vertex position that reconstructs the pi0 mass + for iteration in range(20): + tot=ROOT.TLorentzVector(0,0,0,0) + Lambda=ROOT.TLorentzVector(0,0,0,0) + pi0=ROOT.TLorentzVector(0,0,0,0) + + for i in range(4): + + if i!=hypothesis: + ux=xcp[i]-xvtx + uy=ycp[i]-yvtx + uz=zcp[i]-zvtx + else: + ux=xcp[i] + uy=ycp[i] + uz=zcp[i] + u=np.sqrt(ux**2+uy**2+uz**2) + ux/=u + uy/=u + uz/=u + + P=ROOT.TLorentzVector(pabs[i]*ux, pabs[i]*uy, pabs[i]*uz, E[i]) + tot+=P + if not is_neutron_cand[p][evt][i] and i!=hypothesis: + pi0+=P + if i!=hypothesis: + Lambda+=P + alpha=1 + if iteration==0: + zeta=1/2 + zvtx=maxZ*np.power(zeta,alpha) + xvtx=Lambda.X()/Lambda.Z()*zvtx + yvtx=Lambda.Y()/Lambda.Z()*zvtx + else : + s=2*(pi0.M()