From 7246e0d3b0018ec3968d307da2e6c4b264f50772 Mon Sep 17 00:00:00 2001 From: Sebouh Paul <sebouh.paul@gmail.com> Date: Mon, 23 Sep 2024 13:21:21 -0400 Subject: [PATCH] move zdc pi0 and photon benchmarks from physics benchmarks (#70) Co-authored-by: Dmitry Kalinkin <dmitry.kalinkin@gmail.com> --- .gitlab-ci.yml | 4 + Snakefile | 4 +- benchmarks/zdc_photon/Snakefile | 70 ++++++ .../zdc_photon/analysis/gen_particles.cxx | 127 ++++++++++ .../zdc_photon/analysis/zdc_photon_plots.py | 161 ++++++++++++ benchmarks/zdc_photon/config.yml | 36 +++ benchmarks/zdc_pi0/Snakefile | 56 +++++ benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx | 182 ++++++++++++++ benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py | 232 ++++++++++++++++++ benchmarks/zdc_pi0/config.yml | 34 +++ 10 files changed, 905 insertions(+), 1 deletion(-) create mode 100644 benchmarks/zdc_photon/Snakefile create mode 100644 benchmarks/zdc_photon/analysis/gen_particles.cxx create mode 100644 benchmarks/zdc_photon/analysis/zdc_photon_plots.py create mode 100644 benchmarks/zdc_photon/config.yml create mode 100644 benchmarks/zdc_pi0/Snakefile create mode 100644 benchmarks/zdc_pi0/analysis/gen_pi0_decay.cxx create mode 100644 benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py create mode 100644 benchmarks/zdc_pi0/config.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2e836ec6..4b3c9c8e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -141,6 +141,8 @@ include: - local: 'benchmarks/barrel_hcal/config.yml' - local: 'benchmarks/zdc/config.yml' - local: 'benchmarks/zdc_lyso/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' @@ -169,6 +171,8 @@ deploy_results: - "collect_results:tracking_performances_dis" - "collect_results:zdc" - "collect_results:zdc_lyso" + - "collect_results:zdc_photon" + - "collect_results:zdc_pi0" script: - echo "deploy results!" - find results -print | sort | tee summary.txt diff --git a/Snakefile b/Snakefile index 4a12021b..58357199 100644 --- a/Snakefile +++ b/Snakefile @@ -7,8 +7,10 @@ include: "benchmarks/ecal_gaps/Snakefile" include: "benchmarks/material_scan/Snakefile" include: "benchmarks/tracking_performances/Snakefile" include: "benchmarks/tracking_performances_dis/Snakefile" -include: "benchmarks/zdc_lyso/Snakefile" include: "benchmarks/zdc_lambda/Snakefile" +include: "benchmarks/zdc_lyso/Snakefile" +include: "benchmarks/zdc_photon/Snakefile" +include: "benchmarks/zdc_pi0/Snakefile" include: "benchmarks/zdc_sigma/Snakefile" include: "benchmarks/insert_neutron/Snakefile" diff --git a/benchmarks/zdc_photon/Snakefile b/benchmarks/zdc_photon/Snakefile new file mode 100644 index 00000000..cfb51242 --- /dev/null +++ b/benchmarks/zdc_photon/Snakefile @@ -0,0 +1,70 @@ +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: + """ +if [[ {wildcards.P} -gt 140 ]]; then + NEVENTS_GEN=300 +else + NEVENTS_GEN=1000 +fi +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" + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_photon/{DETECTOR_CONFIG}_sim_zdc_photon_{P}GeV.edm4hep.root" + shell: + """ +# Running simulation +if [[ {wildcards.P} -gt 140 ]]; then + NEVENTS_SIM=300 +else + NEVENTS_SIM=1000 +fi +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --physicsList {params.PHYSICS_LIST} \ + --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.edm4hep.root" + output: + REC_FILE="sim_output/zdc_photon/{DETECTOR_CONFIG}_rec_zdc_photon_{P}GeV.edm4hep.root" + shell: + """ +if [[ {wildcards.P} -gt 140 ]]; then + NEVENTS_REC=300 +else + NEVENTS_REC=1000 +fi +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.edm4hep.root", + P=[20, 30, 50, 70, 100, 150, 200, 275], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + 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 <iostream> +#include <random> +#include <cmath> +#include <math.h> +#include <TMath.h> +#include <TDatabasePDG.h> +#include <TParticlePDG.h> + +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<GenParticle>(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared<GenParticle>( + 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<GenParticle>( + 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<GenVertex>(); + 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..b06cf434 --- /dev/null +++ b/benchmarks/zdc_photon/analysis/zdc_photon_plots.py @@ -0,0 +1,161 @@ +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: + filename=f'sim_output/zdc_photon/{config}_rec_zdc_photon_{p}GeV.edm4hep.root' + print("opening file", filename) + events = ur.open(filename+':events') + arrays_sim[p] = events.arrays()#[:-1] #remove last event, which for some reason is blank + import gc + gc.collect() + print("read", filename) + +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))) + 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) + +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))) + 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) + +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..c8e8bdb5 --- /dev/null +++ b/benchmarks/zdc_photon/config.yml @@ -0,0 +1,36 @@ +sim:zdc_photon: + stage: simulate + extends: .det_benchmark + needs: ["common:detector"] + parallel: + matrix: + - P: 20 + - P: 30 + - P: 50 + - P: 70 + - P: 100 + - P: 150 + - P: 200 + - P: 275 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/zdc_photon/epic_zdc_sipm_on_tile_only_rec_zdc_photon_${P}GeV.edm4hep.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_photon: + stage: benchmarks + extends: .det_benchmark + 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: + stage: collect + extends: .det_benchmark + 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..57fd2c90 --- /dev/null +++ b/benchmarks/zdc_pi0/Snakefile @@ -0,0 +1,56 @@ +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" + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_pi0/{DETECTOR_CONFIG}_sim_zdc_pi0_{P}GeV.edm4hep.root" + shell: + """ +NEVENTS_SIM=1000 +# Running simulation +npsim \ + --compactFile $DETECTOR_PATH/{wildcards.DETECTOR_CONFIG}.xml \ + --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.edm4hep.root" + output: + REC_FILE="sim_output/zdc_pi0/{DETECTOR_CONFIG}_rec_zdc_pi0_{P}GeV.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,HcalFarForwardZDCRecHits,HcalFarForwardZDCClusters,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_pi0_analysis: + input: + expand("sim_output/zdc_pi0/{DETECTOR_CONFIG}_rec_zdc_pi0_{P}GeV.edm4hep.root", + P=[60, 80, 100, 130, 160], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + 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 <TDatabasePDG.h> +#include <TParticlePDG.h> + +#include <iostream> +#include <random> +#include <TMath.h> + +using namespace HepMC3; + +std::tuple<double, int, double> 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 "<<r1->GetSeed()<<"!"<<endl; + + // Getting generated particle information + TDatabasePDG *pdg = new TDatabasePDG(); + + 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); + + 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<GenParticle>(FourVector(0.0, 0.0, 10.0, 10.0), 11, 4); + GenParticlePtr p2 = std::make_shared<GenParticle>( + 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<GenParticle>( + 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<GenVertex>(); + 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<GenParticle>( + FourVector(gamma1_lab.Px(), gamma1_lab.Py(), gamma1_lab.Pz(), gamma1_lab.E()), photon_pdgID, 1 ); + + GenParticlePtr p_gamma2 = std::make_shared<GenParticle>( + FourVector(gamma2_lab.Px(), gamma2_lab.Py(), gamma2_lab.Pz(), gamma2_lab.E()), photon_pdgID, 1 ); + + GenVertexPtr v_pi0_decay = std::make_shared<GenVertex>(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)<zdc_z) + hepmc_output.write_event(evt); + if (events_parsed % 1000 == 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_pi0/analysis/zdc_pi0_plots.py b/benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py new file mode 100644 index 00000000..92adac55 --- /dev/null +++ b/benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py @@ -0,0 +1,232 @@ +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)) + +import uproot as ur +arrays_sim={} +momenta=60, 80, 100, 130, 160, +for p in momenta: + filename=f'sim_output/zdc_pi0/{config}_rec_zdc_pi0_{p}GeV.edm4hep.root' + print("opening file", filename) + events = ur.open(filename+':events') + arrays_sim[p] = events.arrays() + import gc + gc.collect() + print("read", filename) + +#energy res plot +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])==2 for i in range(len(arrays_sim[p]))] + E=arrays_sim[p][selection]["HcalFarForwardZDCClusters.energy"] + + Etot=np.sum(E, axis=-1) + if len(Etot)<25: + continue + #print(p, res, mrecon) + if p==100: + plt.sca(axs[0]) + y, x, _=plt.hist(Etot, bins=100, range=(p*.5, p*1.5), histtype='step') + plt.ylabel("events") + plt.title(f"$p_{{\pi^0}}$={p} GeV") + plt.xlabel("$E^{\\pi^{0}}_{recon}$ [GeV]") + else: + y, x = np.histogram(Etot, bins=100, range=(p*.5, p*1.5)) + + 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))) + if p==100: + xx=np.linspace(p*0.5,p*1.5, 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.ylabel("$\\sigma[E_{\\pi^0}]/\\mu[E_{\\pi^0}]$") +plt.xlabel("$p_{\\pi^0}$ [GeV]") + +fnc=lambda E,a: a/np.sqrt(E) +#pvals, resvals, dresvals +coeff, var_matrix = curve_fit(fnc, pvals, resvals, p0=(1,), + sigma=dresvals) +xx=np.linspace(55, 200, 100) +plt.plot(xx, fnc(xx, *coeff), label=f'fit: $\\frac{{{coeff[0]:.2f}\\%}}{{\\sqrt{{E}}}}$') +plt.legend() +plt.ylim(0) +plt.sca(axs[2]) +plt.errorbar(pvals, scalevals, dscalevals, ls='', marker='o') +plt.ylim(0.8, 1.2) +plt.ylabel("$\\mu[E_{\\pi^0}]/E_{\\pi^0}$") +plt.xlabel("$p_{\\pi^0}$ [GeV]") +plt.axhline(1, ls='--', alpha=0.7, color='0.5') +plt.tight_layout() +plt.savefig(outdir+"/pi0_energy_res.pdf") + + +fig,axs=plt.subplots(1,2, figsize=(16, 8)) +pvals=[] +resvals=[] +dresvals=[] +for p in momenta: + selection=[len(arrays_sim[p]["HcalFarForwardZDCClusters.energy"][i])==2 for i in range(len(arrays_sim[p]))] + x=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.x"] + y=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.y"] + z=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.z"] + E=arrays_sim[p][selection]["HcalFarForwardZDCClusters.energy"] + r=np.sqrt(x**2+y**2+z**2) + px=np.sum(E*x/r, axis=-1) + py=np.sum(E*y/r, axis=-1) + pz=np.sum(E*z/r, axis=-1) + + theta_recon=np.arctan2(np.hypot(px*np.cos(-.025)-pz*np.sin(-.025), py), pz*np.cos(-.025)+px*np.sin(-.025)) + if len(theta_recon)<25: + continue + 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_{{\\pi^0}}$={p} GeV") + plt.xlabel("$\\theta^{\\pi^0}_{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))) + 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: a/np.sqrt(E) +#pvals, resvals, dresvals +coeff, var_matrix = curve_fit(fnc, pvals, resvals, p0=(1,), + sigma=dresvals) + +xx=np.linspace(55, 200, 100) + +plt.plot(xx, fnc(xx, *coeff), label=f'fit: $\\frac{{{coeff[0]:.2f}}}{{\\sqrt{{E}}}}$ mrad') + +plt.ylabel("$\\sigma[\\theta_{\\pi^0}]$ [mrad]") +plt.xlabel("$p_{\\pi^0}$ [GeV]") + +plt.ylim(0, 0.1) +plt.legend() +plt.tight_layout() +plt.savefig(outdir+"/pi0_theta_res.pdf") + +fig,axs=plt.subplots(1,2, figsize=(16, 8)) +pvals=[] +resvals=[] +dresvals=[] +for p in momenta: + selection=[len(arrays_sim[p]["HcalFarForwardZDCClusters.energy"][i])==2 for i in range(len(arrays_sim[p]))] + E=arrays_sim[p][selection]["HcalFarForwardZDCClusters.energy"] + cx=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.x"] + cy=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.y"] + cz=arrays_sim[p][selection]["HcalFarForwardZDCClusters.position.z"] + r=np.sqrt(cx**2+cy**2+cz**2) + px=E*cx/r + py=E*cy/r + pz=E*cz/r + + cos_opening_angle=(cx/r)[::,0]*(cx/r)[::,1]+(cy/r)[::,0]*(cy/r)[::,1]+(cz/r)[::,0]*(cz/r)[::,1] + mrecon=np.sqrt(2*E[::,0]*E[::,1]*(1-cos_opening_angle)) + + if len(mrecon)<25: + continue + + #print(p, res, mrecon) + if p==100: + plt.sca(axs[0]) + y, x, _=plt.hist(mrecon, bins=100, range=(0, 0.2), histtype='step') + plt.ylabel("events") + plt.title(f"$p_{{\pi^0}}$={p} GeV") + plt.xlabel("$m^{\\pi^{0}}_{recon}$ [GeV]") + else: + #y, x, _=plt.hist(mrecon, bins=100, range=(0, 0.2), histtype='step')#y, x =np.histogram(mrecon, bins=100, range=(0, 0.2)) + y, x = np.histogram(mrecon, bins=100, range=(0, 0.2)) + + bc=(x[1:]+x[:-1])/2 + from scipy.optimize import curve_fit + slc=abs(bc-.135)<.1 + fnc=gauss + p0=[100, .135, 0.2] + #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))) + if p==100: + xx=np.linspace(0,0.2) + 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') +plt.ylim(0) +plt.ylabel("$\\sigma[m_{\\pi^0}]$ [GeV]") +plt.xlabel("$p_{\\pi^0}$ [GeV]") + +fnc=lambda E,a,b: a+b*E +#pvals, resvals, dresvals +coeff, var_matrix = curve_fit(fnc, pvals, resvals, p0=(1,1), + sigma=dresvals) +xx=np.linspace(55, 200, 100) +#plt.plot(xx, fnc(xx, *coeff), label=f'fit: ${coeff[0]*1000:.1f}+{coeff[1]*1000:.4f}\\times E$ MeV') +plt.plot(xx, fnc(xx, *coeff), label=f'fit: $({coeff[0]*1000:.1f}+{coeff[1]*1000:.4f}\\times [E\,in\,GeV])$ MeV') +plt.legend() + + +plt.tight_layout() +plt.savefig(outdir+"/pi0_mass_res.pdf") diff --git a/benchmarks/zdc_pi0/config.yml b/benchmarks/zdc_pi0/config.yml new file mode 100644 index 00000000..4059d236 --- /dev/null +++ b/benchmarks/zdc_pi0/config.yml @@ -0,0 +1,34 @@ +sim:zdc_pi0: + stage: simulate + extends: .det_benchmark + needs: ["common:detector"] + parallel: + matrix: + - P: 60 + - P: 80 + - P: 100 + - P: 130 + - P: 160 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/zdc_pi0/epic_zdc_sipm_on_tile_only_rec_zdc_pi0_${P}GeV.edm4hep.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_pi0: + stage: benchmarks + + extends: .det_benchmark + needs: ["sim:zdc_pi0"] + script: + - mkdir -p results/epic_zdc_sipm_on_tile_only + - python benchmarks/zdc_pi0/analysis/zdc_pi0_plots.py results/epic_zdc_sipm_on_tile_only/zdc_pi0 + +collect_results:zdc_pi0: + stage: collect + extends: .det_benchmark + needs: ["bench:zdc_pi0"] + script: + - ls -al -- GitLab