diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 002bb608794dcb43a4a08fd6e8a9d1263aeff90a..2e836ec66971160841267b79c98dc1d7143119b9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -145,6 +145,10 @@ include: - local: 'benchmarks/pid/config.yml' - local: 'benchmarks/timing/config.yml' - local: 'benchmarks/b0_tracker/config.yml' + - local: 'benchmarks/zdc_sigma/config.yml' + - local: 'benchmarks/zdc_lambda/config.yml' + - local: 'benchmarks/insert_neutron/config.yml' + deploy_results: allow_failure: true @@ -159,6 +163,9 @@ deploy_results: - "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" diff --git a/Snakefile b/Snakefile index 738bb5e5289c458a0f957fdf02f3a2047800caed..4a12021b8a726b4dc9b2d57084b6088b7b29e21c 100644 --- a/Snakefile +++ b/Snakefile @@ -8,6 +8,10 @@ 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_sigma/Snakefile" +include: "benchmarks/insert_neutron/Snakefile" + use_s3 = config["remote_provider"].lower() == "s3" use_xrootd = config["remote_provider"].lower() == "xrootd" diff --git a/benchmarks/insert_neutron/Snakefile b/benchmarks/insert_neutron/Snakefile new file mode 100644 index 0000000000000000000000000000000000000000..55e3ff9e09e06786c99d89ff414d5fa5c3e0bf37 --- /dev/null +++ b/benchmarks/insert_neutron/Snakefile @@ -0,0 +1,58 @@ +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" + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_sim_neutron_{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 insert_neutron_recon: + input: + SIM_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_sim_neutron_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/insert_neutron/{DETECTOR_CONFIG}_rec_neutron_{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 -Pjana:nevents=$NEVENTS_REC +""" + +rule insert_neutron_analysis: + input: + expand("sim_output/insert_neutron/{DETECTOR_CONFIG}_rec_neutron_{P}GeV.edm4eic.root", + P=[20, 30, 40, 50, 60, 70, 80], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + 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 0000000000000000000000000000000000000000..0877a85223bbc1af27af91e7dedfb212de7cb20b --- /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 <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/insert_neutron/analysis/neutron_plots.py b/benchmarks/insert_neutron/analysis/neutron_plots.py new file mode 100644 index 0000000000000000000000000000000000000000..9314a9745027c3fd77782c09a8636ab8797f6fa0 --- /dev/null +++ b/benchmarks/insert_neutron/analysis/neutron_plots.py @@ -0,0 +1,281 @@ +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.open(f'sim_output/insert_neutron/{config}_rec_neutron_{p}GeV.edm4eic.root:events')\ + .arrays() + +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_max)], bins=50, + range=(-10,10), histtype='step') +bc=(x[1:]+x[:-1])/2 +slc=abs(bc)<3 +# try: +fnc=gauss +sigma=np.sqrt(y[slc])+(y[slc]==0) +p0=(100, 0, 5) +coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0,sigma=list(sigma)) +xx=np.linspace(-5,5,100) +plt.plot(xx,fnc(xx,*coeff)) +# except: +# pass +plt.xlabel("$\\theta_{rec}-\\theta_{truth}$ [mrad]") +plt.ylabel("events") +plt.title(f"$p={p}$ GeV, ${eta_min}<\\eta<{eta_max}$") + +r=[3.2,3.4,3.6,3.8,4.0] +for eta_min, eta_max in zip(r[:-1],r[1:]): + xvals=[] + sigmas=[] + dsigmas=[] + for p in 20,30,40, 50, 60, 70, 80: + y,x=np.histogram(1000*(arrays_sim[p]['theta_recon']-arrays_sim[p]['theta_truth'])\ + [(arrays_sim[p]['eta_truth']>eta_min)&(arrays_sim[p]['eta_truth']<eta_max)], + bins=50, range=(-10,10)) + bc=(x[1:]+x[:-1])/2 + slc=abs(bc)<3 + fnc=gauss + p0=(100, 0, 5) + #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)) + sigmas.append(np.abs(coeff[2])) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + pass + plt.sca(axs[1]) + plt.errorbar(xvals, sigmas, dsigmas, ls='', marker='o', label=f"${eta_min}<\\eta<{eta_max}$") +plt.xlabel("$p_{n}$ [GeV]") +plt.ylabel("$\\sigma[\\theta]$ [mrad]") +plt.ylim(0) +plt.legend() +plt.tight_layout() +plt.savefig(outdir+"neutron_theta_recon.pdf") + +#now determine the energy recon parameters +pvals=[] +resvals=[] +reserrs=[] +wvals=[] +svals=[] +Euncorr=[] + +print("determining the energy recon correction parameters") +fig,axs=plt.subplots(1,2, figsize=(20,10)) +eta_min=3.4;eta_max=3.6 +for p in 20, 30,40,50,60,70, 80: + best_res=1000 + res_err=1000 + best_s=1000 + wrange=np.linspace(30, 70, 41)*0.0257 + coeff_best=None + + wbest=0 + a=arrays_sim[p] + h=np.sum(a[f'HcalEndcapPInsertClusters.energy'], axis=-1) + e=np.sum(a[f'EcalEndcapPInsertClusters.energy'], axis=-1) + for w in wrange: + + r=(e/w+h)[(h>0)&(a['eta_truth']>eta_min)&(a['eta_truth']<eta_max)] + 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)) + res=np.abs(coeff[2]/coeff[1]) + + if res<best_res: + best_res=res + coeff_best=coeff + res_err=res*np.hypot(np.sqrt(var_matrix[2][2])/coeff[2], np.sqrt(var_matrix[1][1])/coeff[1]) + wbest=w + best_s=np.hypot(p,0.9406)/coeff[1] + Euncorr_best=coeff[1] + except : + print("fit failed") + + if p==50: + r=(e/wbest+h)[(h>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']<eta_max)]#&(abs(phi)>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)) + res=np.abs(coeff[2]/coeff[1]) + + if res<best_res: + best_res=res + res_err=res*np.hypot(np.sqrt(var_matrix[2][2])/coeff[2], np.sqrt(var_matrix[1][1])/coeff[1]) + wbest=w + scale=coeff[1]/np.sqrt(p**2+0.9406**2) + dscale=np.sqrt(var_matrix[1][1]/np.sqrt(p**2+0.9406**2)) + except : + print("fit failed") + if p==50 and eta_min==3.4: + plt.sca(axs[0]) + plt.errorbar(bcs, y, np.sqrt(y)+(y==0),marker='o', ls='', ) + plt.title(f'p={p} GeV, ${eta_min}<\\eta<{eta_max}$') + plt.xlabel("$E_{recon}$ [GeV]") + plt.ylabel("events") + #plt.ylim(0) + xx=np.linspace(40, 70, 50) + plt.plot(xx, fnc(xx, *coeff)) + resvals.append(best_res) + reserrs.append(res_err) + scalevals.append(scale) + scaleerrs.append(dscale) + pvals.append(p) + plt.sca(axs[1]) + plt.errorbar(pvals, resvals, reserrs, marker='o', ls='', label=f"${eta_min}<\\eta<{eta_max}$") + #plt.ylim(0) + plt.ylabel("$\\sigma[E]/\\mu[E]$") + plt.xlabel("$p_{n}$ [GeV]") + + plt.sca(axs[2]) + plt.errorbar(pvals, scalevals, scaleerrs, marker='o', ls='', label=f"${eta_min}<\\eta<{eta_max}$") + + + plt.ylabel("$\\mu[E]/E$") + + +axs[2].set_xlabel("$p_n$ [GeV]") +axs[2].axhline(1, ls='--', color='0.5', alpha=0.7) +axs[0].set_ylim(0) +axs[1].set_ylim(0, 0.35) +axs[2].set_ylim(0) +axs[1].legend() +axs[2].legend() +plt.tight_layout() +plt.savefig(outdir+"neutron_energy_recon.pdf") diff --git a/benchmarks/insert_neutron/config.yml b/benchmarks/insert_neutron/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..aac0a7327aac706ea388f4b90d8b1b432ab840e1 --- /dev/null +++ b/benchmarks/insert_neutron/config.yml @@ -0,0 +1,35 @@ +sim:insert_neutron: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 20 + - P: 30 + - P: 40 + - P: 50 + - P: 60 + - P: 70 + - P: 80 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/insert_neutron/epic_craterlake_rec_neutron_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:insert_neutron: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:insert_neutron"] + script: + - snakemake --cores 1 results/epic_craterlake/insert_neutron + +collect_results:insert_neutron: + stage: collect + needs: ["bench:insert_neutron"] + 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/insert_neutron + - mv results{_save,}/ diff --git a/benchmarks/zdc_lambda/Snakefile b/benchmarks/zdc_lambda/Snakefile new file mode 100644 index 0000000000000000000000000000000000000000..21563330bb0683079b22b7099870ff269976c261 --- /dev/null +++ b/benchmarks/zdc_lambda/Snakefile @@ -0,0 +1,63 @@ +rule zdc_lambda_generate: + input: + script="benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx", + params: + NEVENTS_GEN=100000, + 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" + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_sim_lambda_dec_{P}GeV.edm4hep.root" + shell: + """ +if [[ {wildcards.P} -gt 220 ]]; then + NEVENTS_SIM=500 +else + NEVENTS_SIM=1000 +fi +# 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_lambda_recon: + input: + SIM_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_sim_lambda_dec_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/zdc_lambda/{DETECTOR_CONFIG}_rec_lambda_dec_{P}GeV.edm4eic.root" + shell: + """ +if [[ {wildcards.P} -gt 220 ]]; then + NEVENTS_REC=500 +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,HcalFarForwardZDCClusters,HcalFarForwardZDCRecHits,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_lambda_analysis: + input: + expand("sim_output/zdc_lambda/{DETECTOR_CONFIG}_rec_lambda_dec_{P}GeV.edm4eic.root", + P=[100, 125, 150,175, 200, 225, 250, 275], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + 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 0000000000000000000000000000000000000000..567eda5bcf5497f1b64596745ef5820e7a54eff9 --- /dev/null +++ b/benchmarks/zdc_lambda/analysis/gen_lambda_decay.cxx @@ -0,0 +1,239 @@ +#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 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); + 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 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); + + 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 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<GenParticle>( + 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<GenVertex>(); + 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<GenParticle>( + FourVector(neutron_lab.Px(), neutron_lab.Py(), neutron_lab.Pz(), neutron_lab.E()), neutron_pdgID, 1 ); + + GenParticlePtr p_pi0 = std::make_shared<GenParticle>( + FourVector(pi0_lab.Px(), pi0_lab.Py(), pi0_lab.Pz(), pi0_lab.E()), pi0_pdgID, 2 ); + + GenVertexPtr v_lambda_decay = std::make_shared<GenVertex>(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<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 ); + + // Generate pi0 at same position as the lambda. Approximating pi0 decay as instantaneous + GenVertexPtr v_pi0_decay = std::make_shared<GenVertex>(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 (events_parsed == 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)<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_lambda/analysis/lambda_plots.py b/benchmarks/zdc_lambda/analysis/lambda_plots.py new file mode 100644 index 0000000000000000000000000000000000000000..88aff7f38b130967a0062ec5d193562e50ced06a --- /dev/null +++ b/benchmarks/zdc_lambda/analysis/lambda_plots.py @@ -0,0 +1,372 @@ +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) + +outdir=sys.argv[1]+"/" +config=outdir.split("/")[1] +try: + import os + os.mkdir(outdir[:-1]) +except: + pass +import uproot as ur +arrays_sim={} +momenta=100, 125, 150, 175,200,225,250,275 +for p in momenta: + filename=f'sim_output/zdc_lambda/{config}_rec_lambda_dec_{p}GeV.edm4eic.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) + +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) + +#keep track of the number of clusters per event +nclusters={} + +for p in momenta: + plt.figure() + nclusters[p]=[] + for i in range(len(arrays_sim[p])): + nclusters[p].append(len(arrays_sim[p]["HcalFarForwardZDCClusters.position.x"][i])) + nclusters[p]=np.array(nclusters[p]) + plt.hist(nclusters[p],bins=20, range=(0,20)) + plt.xlabel("number of clusters") + plt.yscale('log') + plt.title(f"$p_\Lambda={p}$ GeV") + plt.ylim(1) + plt.savefig(outdir+f"nclust_{p}GeV_recon.pdf") + print("saved file ", outdir+f"nclust_{p}GeV_recon.pdf") + + + +pt_truth={} +theta_truth={} + +for p in momenta: + #get the truth value of theta* and pt* + px=arrays_sim[p]["MCParticles.momentum.x"][:,2] + py=arrays_sim[p]["MCParticles.momentum.y"][:,2] + pz=arrays_sim[p]["MCParticles.momentum.z"][:,2] + tilt=-0.025 + pt_truth[p]=np.hypot(px*np.cos(tilt)-pz*np.sin(tilt), py) + theta_truth[p]=np.arctan2(pt_truth[p],pz*np.cos(tilt)+px*np.sin(tilt)) + + +#create an array with the same shape as the cluster-level arrays +is_neutron_cand={} +for p in momenta: + is_neutron_cand[p]=(0*arrays_sim[p][f"HcalFarForwardZDCClusters.energy"]).to_list() + + #largest_eigenvalues + for i in range(len(arrays_sim[p])): + pars=arrays_sim[p]["_HcalFarForwardZDCClusters_shapeParameters"][i] + index_of_max=-1 + max_val=0 + eigs=[] + shape_params_per_cluster=7 + for j in range(len(pars)//shape_params_per_cluster): + largest_eigenvalue=max(pars[shape_params_per_cluster*j+4:shape_params_per_cluster*j+7]) + eigs.append(largest_eigenvalue) + if(largest_eigenvalue>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)) +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)) + 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)) +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)) + 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)) +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))) + 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 0000000000000000000000000000000000000000..396b192491e929267a8d56cc4809bab37cdb1987 --- /dev/null +++ b/benchmarks/zdc_lambda/config.yml @@ -0,0 +1,36 @@ +sim:zdc_lambda: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 100 + - P: 125 + - P: 150 + - P: 175 + - P: 200 + - P: 225 + - P: 250 + - P: 275 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/zdc_lambda/epic_zdc_sipm_on_tile_only_rec_lambda_dec_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_lambda: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:zdc_lambda"] + script: + - snakemake --cores 1 results/epic_zdc_sipm_on_tile_only/zdc_lambda + +collect_results:zdc_lambda: + stage: collect + needs: ["bench:zdc_lambda"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake --cores 1 --delete-all-output results/epic_zdc_sipm_on_tile_only/zdc_lambda + - mv results{_save,}/ diff --git a/benchmarks/zdc_sigma/Snakefile b/benchmarks/zdc_sigma/Snakefile new file mode 100644 index 0000000000000000000000000000000000000000..a92a37819bae72e8842ada3bb1a2995fe37694af --- /dev/null +++ b/benchmarks/zdc_sigma/Snakefile @@ -0,0 +1,63 @@ +rule zdc_sigma_generate: + input: + script="benchmarks/zdc_sigma/analysis/gen_sigma_decay.cxx", + params: + NEVENTS_GEN=100000, + output: + GEN_FILE="sim_output/zdc_sigma/sigma_decay_{P}GeV.hepmc" + shell: + """ +root -l -b -q '{input.script}({params.NEVENTS_GEN},0,"{output.GEN_FILE}",{wildcards.P},{wildcards.P})' +""" + +rule zdc_sigma_simulate: + input: + GEN_FILE="sim_output/zdc_sigma/sigma_decay_{P}GeV.hepmc" + params: + PHYSICS_LIST="FTFP_BERT" + output: + SIM_FILE="sim_output/zdc_sigma/{DETECTOR_CONFIG}_sim_sigma_dec_{P}GeV.edm4hep.root" + shell: + """ +if [[ {wildcards.P} -gt 220 ]]; then + NEVENTS_SIM=500 +else + NEVENTS_SIM=1000 +fi +# 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_sigma_recon: + input: + SIM_FILE="sim_output/zdc_sigma/{DETECTOR_CONFIG}_sim_sigma_dec_{P}GeV.edm4hep.root" + output: + REC_FILE="sim_output/zdc_sigma/{DETECTOR_CONFIG}_rec_sigma_dec_{P}GeV.edm4eic.root" + shell: + """ +if [[ {wildcards.P} -gt 220 ]]; then + NEVENTS_REC=500 +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,HcalFarForwardZDCClusters,HcalFarForwardZDCRecHits,HcalFarForwardZDCSubcellHits -Pjana:nevents=$NEVENTS_REC +""" + +rule zdc_sigma_analysis: + input: + expand("sim_output/zdc_sigma/{DETECTOR_CONFIG}_rec_sigma_dec_{P}GeV.edm4eic.root", + P=[100, 125, 150,175, 200, 225, 250, 275], + DETECTOR_CONFIG=["{DETECTOR_CONFIG}"]), + script="benchmarks/zdc_sigma/analysis/sigma_plots.py", + output: + results_dir=directory("results/{DETECTOR_CONFIG}/zdc_sigma"), + shell: + """ +mkdir -p {output.results_dir} +python {input.script} {output.results_dir} +""" diff --git a/benchmarks/zdc_sigma/analysis/gen_sigma_decay.cxx b/benchmarks/zdc_sigma/analysis/gen_sigma_decay.cxx new file mode 100644 index 0000000000000000000000000000000000000000..4da92801257e861bca80cc6638722fff360019c7 --- /dev/null +++ b/benchmarks/zdc_sigma/analysis/gen_sigma_decay.cxx @@ -0,0 +1,305 @@ +#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 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 +{ + int accepted_events=0; + 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); + 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 sigma_info = GetParticleInfo(pdg, "Sigma0"); + double sigma_mass = std::get<0>(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); + + 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 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<GenParticle>( + 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<GenVertex>(); + 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<GenParticle>( + FourVector(lambda_lab.Px(), lambda_lab.Py(), lambda_lab.Pz(), lambda_lab.E()), lambda_pdgID, 2 ); + + GenParticlePtr p_gamma = std::make_shared<GenParticle>( + FourVector(gamma_lab.Px(), gamma_lab.Py(), gamma_lab.Pz(), gamma_lab.E()), photon_pdgID, 1 ); + + GenVertexPtr v_sigma_decay = std::make_shared<GenVertex>(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<GenParticle>( + FourVector(neutron_lab.Px(), neutron_lab.Py(), neutron_lab.Pz(), neutron_lab.E()), neutron_pdgID, 1 ); + + GenParticlePtr p_pi0 = std::make_shared<GenParticle>( + FourVector(pi0_lab.Px(), pi0_lab.Py(), pi0_lab.Pz(), pi0_lab.E()), pi0_pdgID, 2 ); + + GenVertexPtr v_lambda_decay = std::make_shared<GenVertex>(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<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 ); + + // Generate pi0 at same position as the lambda. Approximating pi0 decay as instantaneous + GenVertexPtr v_pi0_decay = std::make_shared<GenVertex>(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 (events_parsed == 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)<zdc_z){ + hepmc_output.write_event(evt); + accepted_events ++; + } + if (events_parsed % 1000 == 0) { + std::cout << "Event: " << events_parsed << " ("<<accepted_events<<" accepted)"<< std::endl; + } + evt.clear(); + } + hepmc_output.close(); + + std::cout << "Events generated: " << events_parsed << " ("<<accepted_events<<" accepted)"<< std::endl; +} diff --git a/benchmarks/zdc_sigma/analysis/sigma_plots.py b/benchmarks/zdc_sigma/analysis/sigma_plots.py new file mode 100644 index 0000000000000000000000000000000000000000..6488b128ee74387df22079729a2d2c522505e1fd --- /dev/null +++ b/benchmarks/zdc_sigma/analysis/sigma_plots.py @@ -0,0 +1,481 @@ +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) + +outdir=sys.argv[1]+"/" +config=outdir.split("/")[1] +try: + import os + os.mkdir(outdir[:-1]) +except: + pass +import uproot as ur +arrays_sim={} +momenta=100, 125, 150, 175,200,225,250,275 +for p in momenta: + filename=f'sim_output/zdc_sigma/{config}_rec_sigma_dec_{p}GeV.edm4eic.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) + +def gauss(x, A,mu, sigma): + return A * np.exp(-(x-mu)**2/(2*sigma**2)) + +#keep track of the number of clusters per event +nclusters={} + +for p in momenta: + plt.figure() + nclusters[p]=[] + for i in range(len(arrays_sim[p])): + nclusters[p].append(len(arrays_sim[p]["HcalFarForwardZDCClusters.position.x"][i])) + nclusters[p]=np.array(nclusters[p]) + plt.hist(nclusters[p],bins=20, range=(0,20)) + plt.xlabel("number of clusters") + plt.yscale('log') + plt.title(f"$p_\Sigma={p}$ GeV") + plt.ylim(1) + plt.savefig(outdir+f"nclust_{p}GeV_recon.pdf") + print("saved file ", outdir+f"nclust_{p}GeV_recon.pdf") + + + +pt_truth={} +theta_truth={} + +for p in momenta: + #get the truth value of theta* and pt* + px=arrays_sim[p]["MCParticles.momentum.x"][:,2] + py=arrays_sim[p]["MCParticles.momentum.y"][:,2] + pz=arrays_sim[p]["MCParticles.momentum.z"][:,2] + tilt=-0.025 + pt_truth[p]=np.hypot(px*np.cos(tilt)-pz*np.sin(tilt), py) + theta_truth[p]=np.arctan2(pt_truth[p],pz*np.cos(tilt)+px*np.sin(tilt)) + +#create an array with the same shape as the cluster-level arrays +is_neutron_cand={} +for p in momenta: + is_neutron_cand[p]=(0*arrays_sim[p][f"HcalFarForwardZDCClusters.energy"]).to_list() + + #largest_eigenvalues + for i in range(len(arrays_sim[p])): + pars=arrays_sim[p]["_HcalFarForwardZDCClusters_shapeParameters"][i] + index_of_max=-1 + max_val=0 + eigs=[] + #Must make sure this doesn't get messed up if someone changes the number of shape parameters in EICrecon. + nClust=nclusters[p][i] + nShapePars=len(pars)//nClust + for j in range(nClust): + largest_eigenvalue=max(pars[nShapePars*j+4:nShapePars*j+7]) + eigs.append(largest_eigenvalue) + if(largest_eigenvalue>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()<pi0_mass)-1 + zeta=np.power(zvtx/maxZ, 1/alpha) + zeta=zeta+s*1/2**(1+iteration) + zvtx=maxZ*np.power(zeta,alpha) + xvtx=Lambda.X()/Lambda.Z()*zvtx + yvtx=Lambda.Y()/Lambda.Z()*zvtx + + if abs(Lambda.M()-lambda_mass)< abs(mass_lambda_best-lambda_mass): + pt_best=tot.Pt() + theta_best=tot.Theta() + mass_lambda_best=Lambda.M() + mass_sigma_best=tot.M() + mass_pi0_best=pi0.M() + zvtx_best=zvtx + + pt_recon_corr[p].append(pt_best) + theta_recon_corr[p].append(theta_best) + mass_recon_corr[p].append(mass_sigma_best) + mass_lambda_recon_corr[p].append(mass_lambda_best) + mass_pi0_recon_corr[p].append(mass_pi0_best) + zvtx_recon[p].append(zvtx_best) + pt_recon_corr[p]=ak.Array(pt_recon_corr[p]) + theta_recon_corr[p]=ak.Array(theta_recon_corr[p]) + mass_recon_corr[p]=ak.Array(mass_recon_corr[p]) + mass_lambda_recon_corr[p]=ak.Array(mass_lambda_recon_corr[p]) + mass_pi0_recon_corr[p]=ak.Array(mass_pi0_recon_corr[p]) + zvtx_recon[p]=ak.Array(zvtx_recon[p]) + +#now make plots + +#reconstructed vertex position plot +fig,axs=plt.subplots(1,3, figsize=(24, 8)) +plt.sca(axs[0]) +plt.title(f"$E_{{\\Sigma}}=100-275$ GeV") +x=[] +y=[] +for p in momenta: + accept=(nclusters[p]==4)# &(pi0_converged[p]) + x+=list(theta_truth[p][accept]*1000) + y+=list(theta_recon_corr[p][accept]*1000) +#print(x) +plt.scatter(x,y) +plt.xlabel("$\\theta^{*\\rm truth}_{\\Sigma}$ [mrad]") +plt.ylabel("$\\theta^{*\\rm recon}_{\\Sigma}$ [mrad]") +plt.xlim(0,3.2) +plt.ylim(0,3.2) + +plt.sca(axs[1]) +plt.title(f"$E_{{\\Sigma}}=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.6 +fnc=gauss +p0=[100, 0, 0.5] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0)) +x=np.linspace(-1, 1) +plt.plot(x, gauss(x, *coeff), color='tab:orange') +plt.xlabel("$\\theta^{*\\rm recon}_{\\Sigma}-\\theta^{*\\rm truth}_{\\Sigma}$ [mrad]") +plt.ylabel("events") + +plt.sca(axs[2]) +sigmas=[] +dsigmas=[] +xvals=[] +for p in momenta: + + accept=(nclusters[p]==4) + y,x=np.histogram((theta_recon_corr[p]-theta_truth[p])[accept]*1000, bins=100, range=(-0.5,0.5)) + bc=(x[1:]+x[:-1])/2 + + from scipy.optimize import curve_fit + slc=abs(bc)<0.3 + fnc=gauss + p0=(100, 0, 0.15) + #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)) + sigmas.append(np.abs(coeff[2])) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + print(f"fit failed for p={p}") +print(xvals) +print(sigmas) +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_{\\Sigma}$ [GeV]") +plt.ylabel("$\\sigma[\\theta^*_{\\Sigma}]$ [mrad]") +plt.tight_layout() +plt.savefig(outdir+"thetastar_recon.pdf") + +#reconstructed vertex position plot +fig,axs=plt.subplots(1,3, figsize=(24, 8)) +plt.sca(axs[0]) +plt.title(f"$E_{{\\Sigma}}=100-275$ GeV") +x=[] +y=[] +for p in momenta: + accept=(nclusters[p]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + tilt=-0.025 + x+=list(arrays_sim[p]['MCParticles.vertex.z'][:,5][accept]*np.cos(tilt)/1000 + +np.sin(tilt)*arrays_sim[p]['MCParticles.vertex.z'][:,5][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_{{\\Sigma}}=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)) +x=np.linspace(-5, 5) +plt.plot(x, gauss(x, *coeff), color='tab:orange') +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]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + a=list((zvtx_recon[p]-arrays_sim[p]['MCParticles.vertex.z'][:,5])[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)) + sigmas.append(abs(coeff[2])) + dsigmas.append(np.sqrt(var_matrix[2][2])) + xvals.append(p) + except: + print(f"fit failed for p={p}") +plt.ylim(0, 3) + +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_{\\Sigma}$ [GeV]") +plt.ylabel("$\\sigma[z_{\\rm vtx}]$ [m]") +plt.tight_layout() +plt.savefig(outdir+"zvtx_recon.pdf") + +#lambda mass reconstruction +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]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + vals+=list(mass_lambda_recon_corr[p][accept]) + +y,x,_= plt.hist(vals, bins=100, range=(0.9, 1.3)) +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(0.9, 1.3) + +from scipy.optimize import curve_fit +slc=abs(bc-lambda_mass)<0.05 +fnc=gauss +p0=[100, lambda_mass, 0.03] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0)) +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_{{\\Sigma}}=100-275$ GeV") + +plt.sca(axs[1]) +xvals=[] +sigmas=[] +dsigmas=[] +for p in momenta: + accept=(nclusters[p]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + y,x= np.histogram(mass_lambda_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.05 + fnc=gauss + p0=[100, lambda_mass, 0.03] + try: + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0))) + 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_{\\Sigma}$ [GeV]") +plt.ylabel("$\\sigma[m_{\\Lambda}]$ [GeV]") +plt.ylim(0, 0.02) +plt.tight_layout() +plt.savefig(outdir+"lambda_mass_rec_from_sigma_decay.pdf") + +#sigma mass reconstruction +p=100 +fig,axs=plt.subplots(1,2, figsize=(16, 8)) +plt.sca(axs[0]) +sigma_mass=1.192 +vals=[] +for p in momenta: + accept=(nclusters[p]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + vals+=list(mass_recon_corr[p][accept]) + +y,x,_= plt.hist(vals, bins=100, range=(1.0, 1.4)) +bc=(x[1:]+x[:-1])/2 +plt.axvline(sigma_mass, ls='--', color='tab:green', lw=3) +plt.text(sigma_mass+.01, np.max(y)*1.05, "PDG mass", color='tab:green') +plt.xlabel("$m_{\\Sigma}^{\\rm recon}$ [GeV]") +plt.ylim(0, np.max(y)*1.2) +plt.xlim(1.0, 1.45) + +from scipy.optimize import curve_fit +slc=abs(bc-sigma_mass)<0.02 +fnc=gauss +p0=[100, sigma_mass, 0.03] +coeff, var_matrix = curve_fit(fnc, bc[slc], y[slc], p0=p0, + sigma=np.sqrt(y[slc])+(y[slc]==0)) +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}_{\\Sigma}$ [GeV]") +plt.ylabel("events") +plt.title(f"$E_{{\\Sigma}}=100-275$ GeV") + +plt.sca(axs[1]) +xvals=[] +sigmas=[] +dsigmas=[] +for p in momenta: + accept=(nclusters[p]==4)&(abs(mass_pi0_recon_corr[p]-pi0_mass)<.01) + y,x= np.histogram(mass_recon_corr[p][accept], bins=100, range=(1.0,1.4)) + bc=(x[1:]+x[:-1])/2 + + from scipy.optimize import curve_fit + slc=abs(bc-sigma_mass)<0.02 + fnc=gauss + p0=[100, sigma_mass, 0.03] + try: + coeff, var_matrix = curve_fit(fnc, list(bc[slc]), list(y[slc]), p0=p0, + sigma=list(np.sqrt(y[slc])+(y[slc]==0))) + sigmas.append(abs(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_{\\Sigma}$ [GeV]") +plt.ylabel("$\\sigma[m_{\\Sigma}]$ [GeV]") +plt.ylim(0, 0.1) +plt.tight_layout() +plt.savefig(outdir+"sigma_mass_rec.pdf") diff --git a/benchmarks/zdc_sigma/config.yml b/benchmarks/zdc_sigma/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..fc81428a64275692721d236bec268f5cb087e534 --- /dev/null +++ b/benchmarks/zdc_sigma/config.yml @@ -0,0 +1,36 @@ +sim:zdc_sigma: + stage: simulate + extends: .det_benchmark + parallel: + matrix: + - P: 100 + - P: 125 + - P: 150 + - P: 175 + - P: 200 + - P: 225 + - P: 250 + - P: 275 + timeout: 1 hours + script: + - snakemake --cores 1 sim_output/zdc_sigma/epic_zdc_sipm_on_tile_only_rec_sigma_dec_${P}GeV.edm4eic.root + retry: + max: 2 + when: + - runner_system_failure + +bench:zdc_sigma: + stage: benchmarks + extends: .det_benchmark + needs: ["sim:zdc_sigma"] + script: + - snakemake --cores 1 results/epic_zdc_sipm_on_tile_only/zdc_sigma + +collect_results:zdc_sigma: + stage: collect + needs: ["bench:zdc_sigma"] + script: + - ls -al + - mv results{,_save}/ # move results directory out of the way to preserve it + - snakemake --cores 1 --delete-all-output results/epic_zdc_sipm_on_tile_only/zdc_sigma + - mv results{_save,}/