diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a1dc5d8c3af3903a3b73bd1a7b487ece7ada20ee..a56c79d26301ea6fc1afd87145333ffba52a8b8b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -144,7 +144,9 @@ include: - 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 @@ -167,6 +169,9 @@ deploy_results: - "collect_results:insert_muon" - "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/Snakefile b/Snakefile index bce37f8dcab0328b0bfa5aeb6a82a9b8afa19a20..5432328496cf9d827cdcd2f46ef6a1bb18065f6a 100644 --- a/Snakefile +++ b/Snakefile @@ -7,7 +7,6 @@ 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/insert_muon/Snakefile" include: "benchmarks/zdc_lambda/Snakefile" include: "benchmarks/zdc_lyso/Snakefile" @@ -15,7 +14,9 @@ include: "benchmarks/zdc_photon/Snakefile" include: "benchmarks/zdc_pi0/Snakefile" include: "benchmarks/zdc_sigma/Snakefile" include: "benchmarks/insert_neutron/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" diff --git a/benchmarks/femc_electron/Snakefile b/benchmarks/femc_electron/Snakefile new file mode 100644 index 0000000000000000000000000000000000000000..0e8dd0842b2a79fd67f59ccbae14bea41cd0ba22 --- /dev/null +++ b/benchmarks/femc_electron/Snakefile @@ -0,0 +1,58 @@ +rule femc_electron_generate: + input: + script="benchmarks/femc_electron/analysis/gen_particles.cxx", + params: + NEVENTS_GEN=1000, + 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.NEVENTS_GEN},"{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: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_electron/{DETECTOR_CONFIG}_sim_e-_{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 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" + 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,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents=$NEVENTS_REC +""" + +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 0000000000000000000000000000000000000000..cc34633dc30887057bda2e8331f1ed509c6f33b5 --- /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_max)], + bins=np.linspace(0,10,11), histtype='step', label=f'{p} GeV', density=True) + plt.ylabel("events") + plt.xlabel("# of Ecal clusters") + plt.legend() + plt.savefig(outdir+f"/{field}.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"e-, 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(ak.flatten(arrays_sim[p]['EcalEndcapPClusters.energy']), bins=bins, histtype='step') + plt.ylabel("events") + plt.xlabel("$E^{rec}_e$ [GeV]") + else: + y,x=np.histogram(ak.flatten(arrays_sim[p]['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) + + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0,sigma=list(sigma)) + #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) +xx=np.linspace(7, 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(ak.flatten(arrays_sim[p][(eta_truth<eta_max)&(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)) + 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 0000000000000000000000000000000000000000..0877a85223bbc1af27af91e7dedfb212de7cb20b --- /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 <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/femc_electron/config.yml b/benchmarks/femc_electron/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..a600454d1f2a7b20b2450d22f4ed144717d2d901 --- /dev/null +++ b/benchmarks/femc_electron/config.yml @@ -0,0 +1,36 @@ +sim:femc_electron: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/femc_electron/epic_craterlake_rec_e-_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_electron: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:femc_electron"] + script: + - snakemake --cores 1 results/epic_craterlake/femc_electron + +collect_results:femc_electron: + stage: collect + needs: ["bench:femc_electron"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake --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 0000000000000000000000000000000000000000..40524365f7674bc92cb04c4d45160975644a858c --- /dev/null +++ b/benchmarks/femc_photon/Snakefile @@ -0,0 +1,58 @@ +rule femc_photon_generate: + input: + script="benchmarks/femc_photon/analysis/gen_particles.cxx", + params: + NEVENTS_GEN=1000, + 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.NEVENTS_GEN},"{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: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_photon/{DETECTOR_CONFIG}_sim_photon_{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 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" + 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,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents=$NEVENTS_REC +""" + +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 0000000000000000000000000000000000000000..8170f4a1d0ecd006a5f511bab246cdd0124f9cdd --- /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_max)], + bins=np.linspace(0,10,11), histtype='step', label=f'{p} GeV', density=True) + plt.ylabel("events") + plt.xlabel("# of Ecal clusters") + plt.legend() + plt.savefig(outdir+f"/{field}.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"photon, 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(ak.flatten(arrays_sim[p]['EcalEndcapPClusters.energy']), bins=bins, histtype='step') + plt.ylabel("events") + plt.xlabel("$E^{rec}_\\gamma$ [GeV]") + else: + y,x=np.histogram(ak.flatten(arrays_sim[p]['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) + + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0,sigma=list(sigma)) + #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) +xx=np.linspace(7, 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(ak.flatten(arrays_sim[p][(eta_truth<eta_max)&(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)) + 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/femc_photon_plots.py~ b/benchmarks/femc_photon/analysis/femc_photon_plots.py~ new file mode 100644 index 0000000000000000000000000000000000000000..4227b4e3d910ec8dd65f16bf527661f273190fee --- /dev/null +++ b/benchmarks/femc_photon/analysis/femc_photon_plots.py~ @@ -0,0 +1,171 @@ +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/epic_craterlake_rec_e-_{p}GeV.edm4hep.root:events').arrays() for p in (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_max)], + bins=np.linspace(0,10,11), histtype='step', label=f'{p} GeV', density=True) + plt.ylabel("events") + plt.xlabel("# of Ecal clusters") + plt.legend() + plt.savefig(outdir+f"/{field}.pdf") + +fig, axs=plt.subplots(1,2, figsize=(16,8)) +avgs=[] +stds=[] +pvals=[] + +#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)): + 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"e-, 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(ak.flatten(arrays_sim[p]['EcalEndcapPClusters.energy']), bins=bins, histtype='step') + plt.ylabel("events") + plt.xlabel("$E^{rec}_e$ [GeV]") + else: + y,x=np.histogram(ak.flatten(arrays_sim[p]['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) + + coeff, var_matrix = curve_fit(fnc, list(bcs[slc]), list(y[slc]), p0=p0,sigma=list(sigma)) + #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) +xx=np.linspace(15, 85, 100) +plt.plot(xx, 100*fnc(xx,*coeff), label=f'fit:{100*coeff[0]:.0f}%$\\oplus\\frac{{{100*coeff[1]:.1f}\\%}}{{\\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") diff --git a/benchmarks/femc_photon/analysis/gen_particles.cxx b/benchmarks/femc_photon/analysis/gen_particles.cxx new file mode 100644 index 0000000000000000000000000000000000000000..0877a85223bbc1af27af91e7dedfb212de7cb20b --- /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 <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/femc_photon/config.yml b/benchmarks/femc_photon/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..a367e5093dccb1dc5b9642f2fede452a5c46f85a --- /dev/null +++ b/benchmarks/femc_photon/config.yml @@ -0,0 +1,36 @@ +sim:femc_photon: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/femc_photon/epic_craterlake_rec_photon_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_photon: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:femc_photon"] + script: + - snakemake --cores 1 results/epic_craterlake/femc_photon + +collect_results:femc_photon: + stage: collect + needs: ["bench:femc_photon"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake --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 0000000000000000000000000000000000000000..4936040cb832a72cfef4a43fbb4dfc02296695b2 --- /dev/null +++ b/benchmarks/femc_pi0/Snakefile @@ -0,0 +1,58 @@ +rule femc_pi0_generate: + input: + script="benchmarks/femc_pi0/analysis/gen_particles.cxx", + params: + NEVENTS_GEN=1000, + 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.NEVENTS_GEN},"{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: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/femc_pi0/{DETECTOR_CONFIG}_sim_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 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" + 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,EcalEndcapPInsertRecHits,EcalEndcapPClusters -Pjana:nevents=$NEVENTS_REC +""" + +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 0000000000000000000000000000000000000000..cdf9d2319c943721bb8fdf281dd34d7bda7fd069 --- /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_max)], + bins=np.linspace(0,10,11), histtype='step', label=f'{p} GeV', density=True) + plt.ylabel("events") + plt.xlabel("# of Ecal clusters") + plt.legend() + plt.savefig(outdir+f"/{field}.pdf") + +#number of clusters +plt.figure() +for eta_min, eta_max, field in (1.5, 2.8, 'nclust_endcap'),: + pvals=[] + f0=[] + f1=[] + f2=[] + f3=[] + f4=[] + f5p=[] + for p in arrays_sim: + array=arrays_sim[p] + n=array[field][(array['eta_truth']>eta_min)&(array['eta_truth']<eta_max)] + pvals.append(p) + tot=len(n) + f0.append(np.sum(1*(n==0))/tot) + f1.append(np.sum(1*(n==1))/tot) + f2.append(np.sum(1*(n==2))/tot) + f3.append(np.sum(1*(n==3))/tot) + f4.append(np.sum(1*(n==4))/tot) + f5p.append(np.sum(1*(n>=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)) + #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) +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_truth<eta_max)&(eta_truth>eta_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)) + 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 0000000000000000000000000000000000000000..0877a85223bbc1af27af91e7dedfb212de7cb20b --- /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 <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/femc_pi0/config.yml b/benchmarks/femc_pi0/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..bcbd90afd07241217dc64b712fc209a3cf0bf84d --- /dev/null +++ b/benchmarks/femc_pi0/config.yml @@ -0,0 +1,36 @@ +sim:femc_pi0: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 10 + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/femc_pi0/epic_craterlake_rec_pi0_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:femc_pi0: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:femc_pi0"] + script: + - snakemake --cores 1 results/epic_craterlake/femc_pi0 + +collect_results:femc_pi0: + stage: collect + needs: ["bench:femc_pi0"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake --cores 1 --delete-all-output results/epic_craterlake/femc_pi0 + - mv results{_save,}/