Compare commits

...

2 commits

Author SHA1 Message Date
Ava
7e703cd499 comparinson shower geant4 2026-01-12 15:52:29 +01:00
Ava
0644f42d5c shower_theory 2026-01-12 10:27:40 +01:00
13 changed files with 612 additions and 500 deletions

View file

@ -381,7 +381,7 @@ ax.hlines(y=[2.5e-4],xmin=1.0,xmax=11e3,color='tab:blue',alpha=0.2,linewidth=lin
plt.text(0.91, 0.745, 'HET', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center') plt.text(0.91, 0.745, 'HET', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center')
plt.text(0.91, 0.575, 'AMS', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center') plt.text(0.91, 0.575, 'AMS', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center')
plt.text(0.91, 0.41, 'KET', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center') plt.text(0.91, 0.41, 'KET', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center')
plt.text(0.91, 0.24, 'CHAOS', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center') plt.text(0.91, 0.24, 'ProHEPaM', fontsize=15, rotation=0, transform=plt.gcf().transFigure, va='center')
ax.set_ylim(5e-5,2e0) ax.set_ylim(5e-5,2e0)
ax.set_xscale('log') ax.set_xscale('log')
@ -393,7 +393,7 @@ ax.set_ylabel(r'Differential Flux $\Phi$ in (m$^2$ s sr MeV)$^{-1}$', fontsize=1
ax.grid(visible=True, which='both', axis='both', ls='--') ax.grid(visible=True, which='both', axis='both', ls='--')
plt.legend(fontsize=12,loc='upper right') plt.legend(fontsize=12,loc='upper right')
plt.title('GCR Energy Spectra',size=16) plt.title('GCR Energy Spectra',size=16)
plt.savefig('images/adriani-etal-combined-e-p_all_chaos.pdf') plt.savefig('plots/adriani-etal-combined-e-p_all_prohepam.pdf')
# p_flux = simpson(y1*1000, np.sqrt(x1l*x1h))#, dx = x1h - x1l) # p_flux = simpson(y1*1000, np.sqrt(x1l*x1h))#, dx = x1h - x1l)

221
shower.py
View file

@ -1,221 +0,0 @@
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gamma, norm
import pandas as pd
# --------------------------
# Material parameters for BGO
# --------------------------
Z_BGO = (4*83 + 3*32 + 12*8)/19 # Approx. effective Z
X0_cm = 1.118 # Radiation length in cm
Ec_MeV = 10.5 # Critical energy in MeV
b = 0.5 # Gamma profile parameter
dEdx_ion = 9.5 # MeV/cm, approximate ionization loss
Rm_cm = 2.26 # Approximate Molière radius for BGO
# ----------------------------------------------------------------------------
# Critical Energy in BGO: Two Definitions
# --------------------------
# Energy range [MeV]
E = np.logspace(0, 4, 500) # 1 MeV to 10 GeV
# --------------------------
# Losses
# --------------------------
dEdx_rad = E / X0_cm
dEdx_ion_array = np.full_like(E, dEdx_ion)
dEdx_total = dEdx_rad + dEdx_ion_array
# --------------------------
# Tsai/Rossi intersection
# --------------------------
idx = np.argmin(np.abs(dEdx_rad - dEdx_ion_array))
Ec_tsai = E[idx]
dEdx_tsai = dEdx_ion_array[idx] # Höhe des Schnittpunkts
# --------------------------
# Rossi definition: dE/dx_ion * (E / dE/dx_rad) = E
# Equivalent: line parallel to dEdx_rad passing through Ec_rossi
# --------------------------
Ec_rossi = dEdx_ion * X0_cm # MeV
dEdx_rossi_line = E / Ec_rossi * dEdx_ion # parallel to radiation line
# --------------------------
# Plot
# --------------------------
# plt.figure(figsize=(8,6))
# # Radiation, ionization, total
# plt.loglog(E, dEdx_rad, label='Radiation loss dE/dx_rad', linewidth=2, color='blue')
# plt.loglog(E, dEdx_ion_array, label='Ionization loss dE/dx_ion', linewidth=2, color='orange', linestyle='--')
# plt.loglog(E, dEdx_total, label='Total loss', linewidth=2, color='k')
# # Tsai/Rossi intersection
# plt.scatter(Ec_tsai, dEdx_tsai, color='red', s=80, label=f'Tsai/Rossi Ec ≈ {Ec_tsai:.1f} MeV')
# # Rossi definition line (parallel to radiation)
# plt.loglog(E, dEdx_rossi_line, color='green', linestyle=':', linewidth=2, label=f'Rossi Ec ≈ {Ec_rossi:.1f} MeV')
# plt.xlabel("Energy [MeV]")
# plt.ylabel("dE/dx [MeV/cm]")
# plt.title("Critical Energy in BGO: Tsai/Rossi and Rossi Definitions")
# plt.grid(True, which='both', ls='--', alpha=0.5)
# plt.legend()
# plt.tight_layout()
# plt.show()
# ----------------------------------------------------------------------------
# Longitudinal energy deposition profile (Gamma function)
# --------------------------
# Primary electron energies [GeV]
energies_GeV = [0.1, 0.5, 1.0, 2.0]
energies_MeV = [E*1000 for E in energies_GeV] # convert to MeV for calculations
# Detector thicknesses [cm]
thicknesses_cm = [2, 4, 6]
# Font size for all text in plot
fs = 18
# --------------------------
# Functions
# --------------------------
def tmax(E, Ec):
"""Calculate shower maximum in radiation lengths"""
return np.log(E / Ec) - 0.5
def longitudinal_profile(t, E, Ec, b=0.5):
"""Longitudinal energy deposition profile (Gamma function)"""
t_max_val = tmax(E, Ec)
a = b * t_max_val + 1
return gamma.pdf(t, a, scale=1/b) * E # dE/dt in MeV/X0
# --------------------------
# Calculate table values
# --------------------------
rows = []
for E in energies_MeV:
t_max_val = tmax(E, Ec_MeV)
t_max_cm = t_max_val * X0_cm
row = {
'tmax [X0]': round(t_max_val,3),
'tmax [cm]': round(t_max_cm,3)
}
for d in thicknesses_cm:
thickness_X0 = d / X0_cm
row[f'Dicke [X0] ({d}cm)'] = round(thickness_X0,3)
row[f'Dicke/tmax ({d}cm)'] = round(thickness_X0 / t_max_val,3)
rows.append(row)
# Build a DataFrame with energies as columns (like your LaTeX table)
table_df = pd.DataFrame(rows, index=[f"{E} GeV" for E in energies_GeV]).T
# print("\n--- Longitudinal Shower Table ---\n")
# print(table_df)
# --------------------------
# Plot longitudinal profiles
# --------------------------
t = np.linspace(0, 8, 400) # Depth in X0
plt.figure(figsize=(10,6))
for E, E_GeV in zip(energies_MeV, energies_GeV):
profile = longitudinal_profile(t, E, Ec_MeV, b)
plt.plot(t*X0_cm, profile, label=f"{E_GeV} GeV", linewidth=2)
# Vertical lines for detector thicknesses
for d in thicknesses_cm:
plt.axvline(d, color='k', linestyle='--', alpha=0.5, linewidth=1)
plt.text(d+0.05, plt.ylim()[1]*0.9, f"{d} cm", rotation=90, va='top', fontsize=fs)
plt.xlabel("Depth in BGO [cm]", fontsize=fs)
plt.ylabel("dE/dx [MeV/cm]", fontsize=fs)
plt.title("Longitudinal Shower Profile in BGO", fontsize=fs)
plt.legend(fontsize=fs)
plt.grid(True)
plt.xticks(fontsize=fs)
plt.yticks(fontsize=fs)
plt.tight_layout()
plt.savefig("plots/BGO_longitudinal_profile.pdf", format='pdf')
plt.show()
# --------------------------
# Transverse shower functions (Molière distribution) at shower maximum
# --------------------------
def transverse_profile(r, Rm, frac=0.9):
"""
Approximate transverse distribution using Gaussian core of Molière radius
frac: fraction of energy within 1 Rm (~90%)
"""
sigma = Rm / np.sqrt(2 * np.log(1/(1-frac))) # match 90% in Rm
return norm.pdf(r, 0, sigma)
r = np.linspace(0, 10, 400) # transverse distance in cm
###PLOT
plt.figure(figsize=(10,6))
for E, E_GeV in zip(energies_MeV, energies_GeV):
tmax_X0 = tmax(E, Ec_MeV)
dE_total = longitudinal_profile(tmax_X0, E, Ec_MeV, b) # peak energy
trans_profile = transverse_profile(r, Rm_cm)
# normalize to longitudinal peak
trans_profile *= dE_total / np.max(trans_profile)
plt.plot(r, trans_profile, label=f"{E_GeV} GeV", linewidth=2)
plt.axvline(Rm_cm, color='r', linestyle='--', label="RM="+str(Rm_cm)+"cm")
plt.axvline(2*Rm_cm, color='r', linestyle=':', label="2RM="+str(2*Rm_cm)+"cm")
plt.xlabel("Transverse distance [cm]", fontsize=fs)
plt.ylabel("dE/dx [MeV/cm]", fontsize=fs)
plt.title("Transverse Shower Profile in BGO at Shower Maximum", fontsize=fs)
plt.legend(fontsize=fs)
plt.grid(True)
plt.xticks(fontsize=fs)
plt.yticks(fontsize=fs)
plt.tight_layout()
plt.show()
#########################################################
# Radius-Achse in cm (symmetrisch um die Schauerachse)
r = np.linspace(-8, 8, 300) # 300 radial points
z = np.linspace(0, 12, 400) # 400 depth points
# Meshgrid für 2D-Darstellung
Z, R = np.meshgrid(z, r, indexing="ij")
# Transversales Profil (Gaussian Approximation)
sigma_r = Rm_cm * (1 + 0.03 * (Z / X0_cm)) # +3% pro X0 (realistische Streuung)
trans_profile = np.exp(-(R**2) / (2 * sigma_r**2))
t = Z / X0_cm
E0 = 100 # Beispiel: 1 GeV Elektron
long_profile = longitudinal_profile(t, E0, Ec_MeV, b)
#long_profile_2D = long_profile[:, None]
shower_2D = long_profile * trans_profile
plt.figure(figsize=(10, 6))
plt.imshow(
shower_2D,
extent=[r.min(), r.max(), z.max(), z.min()],
aspect='auto',
cmap='inferno',
interpolation='bilinear'
)
plt.axvline(0, color='black', linestyle='-', linewidth=1.5)
plt.colorbar(label="Energy deposition (arb. units)")
plt.xlabel("Radius r [cm]")
plt.ylabel("Depth z [cm]")
plt.title("2D Heatmap of Electromagnetic Shower in BGO (Rm = 2.26 cm)")
plt.tight_layout()
plt.show()

View file

@ -1,6 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Heatmap for a chosen file of Geant4 simulation data
"""
import argparse import argparse
import numpy as np import numpy as np
import pandas as pd import pandas as pd

View file

@ -1,11 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Longitudinal energy deposition profile from Geant4
<dE/dz> averaged per primary event
Comparable to analytic shower theory
"""
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# --------- Dateien --------- # -------------------------------------------------
# Input files
# -------------------------------------------------
files = [ files = [
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
@ -17,32 +24,36 @@ files = [
labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"] labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
colors = ["C0", "C1", "C2", "C3", "C4"] colors = ["C0", "C1", "C2", "C3", "C4"]
fs=18 # -------------------------------------------------
# Parameters
# --------- Parameter --------- # -------------------------------------------------
z_max_cm = 10.0 z_max_cm = 10.0
n_bins = 100 n_bins = 100
fs = 18
# --------- Plot --------- # -------------------------------------------------
# Plot
# -------------------------------------------------
plt.figure(figsize=(12, 6)) plt.figure(figsize=(12, 6))
for file, label, color in zip(files, labels, colors): for file, label, color in zip(files, labels, colors):
df = pd.read_csv(file, sep="\t") df = pd.read_csv(file, sep="\t")
z_edges = np.linspace(0, z_max_cm, n_bins + 1) # number of primary events
N_events = df["event"].nunique()
# z-binning
z_edges = np.linspace(0.0, z_max_cm, n_bins + 1)
dz = z_edges[1] - z_edges[0] dz = z_edges[1] - z_edges[0]
z_centers = (z_edges[:-1] + z_edges[1:]) / 2 z_centers = 0.5 * (z_edges[:-1] + z_edges[1:])
# Energie pro z-Bin # energy sum per z-bin
E_sum, _ = np.histogram(df["z"], bins=z_edges, weights=df["edep"]) E_sum, _ = np.histogram(df["z"], bins=z_edges, weights=df["edep"])
counts, _ = np.histogram(df["z"], bins=z_edges)
# Mittelwert pro Step → dE/dz [MeV/cm] # <dE/dz> [MeV/cm/nuc]
profile = np.zeros_like(E_sum) profile = E_sum / (N_events * dz)
mask = counts > 0
profile[mask] = E_sum[mask] / counts[mask] / dz
# Step-Plot (physikalisch korrekt für Histogramme)
plt.step( plt.step(
z_centers, z_centers,
profile, profile,
@ -52,14 +63,74 @@ for file, label, color in zip(files, labels, colors):
label=label label=label
) )
plt.xlabel("Depth z [cm]",fontsize=fs) plt.xlabel("Depth z [cm]", fontsize=fs)
plt.ylabel("dE/dz [MeV/cm]",fontsize=fs) plt.ylabel(r"$\langle dE/dz \rangle$ [MeV/cm/nuc]", fontsize=fs)
plt.title("Longitudinal shower profile", fontsize=fs+2) plt.title("Longitudinal energy deposition in BGO (Geant4)", fontsize=fs+2)
plt.xlim(0, z_max_cm) plt.xlim(0, z_max_cm)
plt.tick_params(axis='both', which='major', labelsize=fs-3) plt.grid(True, ls="--", lw=0.6)
plt.grid(True, ls="--", lw=0.5) plt.tick_params(labelsize=fs-2)
plt.legend(title="Initial Energy") plt.legend(title="Initial Energy")
plt.tight_layout() plt.tight_layout()
plt.savefig("plots/shower_longitudinal.png", dpi=300) plt.savefig("plots/G4_longitudinal_profile.png", dpi=300)
plt.show() plt.show()
# # --------- Dateien ---------
# files = [
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
# ]
# labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
# colors = ["C0", "C1", "C2", "C3", "C4"]
# fs=18
# # --------- Parameter ---------
# z_max_cm = 10.0
# n_bins = 100
# # --------- Plot ---------
# plt.figure(figsize=(12, 6))
# for file, label, color in zip(files, labels, colors):
# df = pd.read_csv(file, sep="\t")
# z_edges = np.linspace(0, z_max_cm, n_bins + 1)
# dz = z_edges[1] - z_edges[0]
# z_centers = (z_edges[:-1] + z_edges[1:]) / 2
# # Energie pro z-Bin
# E_sum, _ = np.histogram(df["z"], bins=z_edges, weights=df["edep"])
# counts, _ = np.histogram(df["z"], bins=z_edges)
# # Mittelwert pro Step → dE/dz [MeV/cm]
# profile = np.zeros_like(E_sum)
# mask = counts > 0
# profile[mask] = E_sum[mask] / counts[mask] / dz
# # Step-Plot (physikalisch korrekt für Histogramme)
# plt.step(
# z_centers,
# profile,
# where="mid",
# lw=2,
# color=color,
# label=label
# )
# plt.xlabel("Depth z [cm]",fontsize=fs)
# plt.ylabel("dE/dz [MeV/cm]",fontsize=fs)
# plt.title("Longitudinal shower profile", fontsize=fs+2)
# plt.xlim(0, z_max_cm)
# plt.tick_params(axis='both', which='major', labelsize=fs-3)
# plt.grid(True, ls="--", lw=0.5)
# plt.legend(title="Initial Energy")
# plt.tight_layout()
# plt.savefig("plots/shower_longitudinal.png", dpi=300)
# plt.show()

View file

@ -43,13 +43,13 @@ n_rings = len(rings)
# -------------------------- # --------------------------
fig, axes = plt.subplots(n_rings+1, 1, figsize=(10,12), sharex=True) fig, axes = plt.subplots(n_rings+1, 1, figsize=(10,12), sharex=True)
plt.subplots_adjust(hspace=0.1) plt.subplots_adjust(hspace=0.1)
fig.suptitle("Longitudinal energy deposition in BGO by radial ring", fontsize=16, y=0.95) fig.suptitle("Longitudinal energy deposition in BGO by radial ring (theory)", fontsize=16, y=0.95)
# Dummy-Linien für Startenergie-Legende (nur eine Zeile, 5 Spalten) # Dummy-Linien für Startenergie-Legende (nur eine Zeile, 5 Spalten)
dummy_lines = [axes[0].plot([], [], color=c, linewidth=2)[0] for c in colors] # dummy_lines = [axes[0].plot([], [], color=c, linewidth=2)[0] for c in colors]
axes[0].legend(dummy_lines, [f"{E} MeV" for E in energies], ncol=5, # axes[0].legend(dummy_lines, [f"{E} MeV" for E in energies], ncol=5,
fontsize=12, frameon=True, framealpha=0.85, facecolor="white", # fontsize=12, frameon=True, framealpha=0.85, facecolor="white",
loc='upper center', bbox_to_anchor=(0.5, 1.12)) # loc='upper center', bbox_to_anchor=(0.5, 1.12))
# Gemeinsames Y-Label # Gemeinsames Y-Label
fig.text(0.02, 0.5, r"$\langle dE/dz \rangle$ [MeV/cm]", va='center', rotation='vertical', fontsize=16) fig.text(0.02, 0.5, r"$\langle dE/dz \rangle$ [MeV/cm]", va='center', rotation='vertical', fontsize=16)

View file

@ -1,3 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
overwiev: 2D map, transversal and longitudinal shower profile theory
"""
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec from matplotlib.gridspec import GridSpec
@ -67,6 +73,7 @@ for E, color in zip(energies_MeV, colors):
long_prof = longitudinal_profile(t, E, Ec_MeV, b) / X0_cm long_prof = longitudinal_profile(t, E, Ec_MeV, b) / X0_cm
sigma_r = Rm_cm * (1 + 0.03 * t) sigma_r = Rm_cm * (1 + 0.03 * t)
trans_prof = np.exp(-(R**2)/(2*sigma_r**2)) trans_prof = np.exp(-(R**2)/(2*sigma_r**2))
#trans_prof = np.exp(-(R**2)/(2*sigma_r**2)) / (2*np.pi*sigma_r**2)
shower_2D = long_prof * trans_prof shower_2D = long_prof * trans_prof
shower_norm = shower_2D / np.max(shower_2D) * 100 shower_norm = shower_2D / np.max(shower_2D) * 100
for pct, ls in linestyles.items(): for pct, ls in linestyles.items():
@ -104,14 +111,37 @@ ax_long.tick_params(labelsize=fs)
# -------------------------- # --------------------------
# Transversale Profile (korrekt normiert wie Plot 1.1) # Transversale Profile (korrekt normiert wie Plot 1.1)
# -------------------------- # --------------------------
r_trans = np.linspace(0, 8, 300)
energy_lines = [] energy_lines = []
for E, color in zip(energies_MeV, colors):
zmax = tmax(E, Ec_MeV) * X0_cm #lokal im Schauermaximum
dEdz_max = dEdz(zmax, E) # MeV/cm/nuc # for E, color in zip(energies_MeV, colors):
trans_prof = rho_r(r_trans) * dEdz_max # MeV/cm²/nuc # zmax = tmax(E, Ec_MeV) * X0_cm
ln, = ax_trans.plot(r_trans, trans_prof, color=color, linewidth=2) # dEdz_max = dEdz(zmax, E) # MeV/cm/nuc
# trans_prof = rho_r(r) * dEdz_max # MeV/cm²/nuc
# ln, = ax_trans.plot(r, trans_prof, color=color, linewidth=2)
# energy_lines.append(ln)
#aufintegriert über gesamten kristall
#physikalisch inkorrekt
# for E, color in zip(energies_MeV, colors):
# t = Z / X0_cm
# long_prof = longitudinal_profile(t, E, Ec_MeV, b) / X0_cm
# sigma_r = Rm_cm * (1 + 0.03 * t)
# trans_prof = np.exp(-(R**2)/(2*sigma_r**2)) / (2*np.pi*sigma_r**2)
# shower_2D = long_prof * trans_prof
# radial_profile = np.trapz(shower_2D, z, axis=0) # Integration über Tiefe
# ln, =ax_trans.plot(r, radial_profile, color=color, linewidth=2)
# energy_lines.append(ln)
#physikalisch korrekt
dz = z[1] - z[0]
for E, col in zip(energies_MeV, colors):
# longitudinales Profil
prof_z = dEdz(z, E) # MeV/cm
# Integration über z → Energie pro Fläche
radial_profile = np.sum(prof_z[:, None] * rho_r(r)[None, :] * dz,axis=0) # MeV/cm²
ln, = ax_trans.plot(r, radial_profile, color=col, linewidth=2)
energy_lines.append(ln) energy_lines.append(ln)
# Molière Linien nur positive Linien für Legende # Molière Linien nur positive Linien für Legende
@ -122,11 +152,13 @@ for r_val, lw in zip(molier_radii, [1.5,2.0]):
ax_trans.axvline(-r_val, color='k', linestyle='-', linewidth=lw) ax_trans.axvline(-r_val, color='k', linestyle='-', linewidth=lw)
ax_trans.set_xlabel("Radius r [cm]", fontsize=fs) ax_trans.set_xlabel("Radius r [cm]", fontsize=fs)
ax_trans.set_ylabel(
r"$\langle dE/(dz\,dA) \rangle$" + "\n[MeV/cm$^2$/nuc]", #ax_trans.set_ylabel(r"$\langle dE/(dz\,dA) \rangle$" + "\n[MeV/cm$^2$/nuc]",fontsize=fs)
fontsize=fs #ax_trans.set_title("Transverse Profiles at Shower Maximum", fontsize=fs)
)
ax_trans.set_title("Transverse Profiles at Shower Maximum", fontsize=fs) ax_trans.set_ylabel(r"$\int \langle dE/(dz\,dA) \rangle\,dz$" + "\n[MeV/cm$^2$]",fontsize=fs)
ax_trans.set_title("Transverse energy deposition integrated over 10 cm BGO", fontsize=fs)
ax_trans.grid(True) ax_trans.grid(True)
ax_trans.set_xlim(0,8) ax_trans.set_xlim(0,8)
ax_trans.tick_params(labelsize=fs) ax_trans.tick_params(labelsize=fs)
@ -151,6 +183,6 @@ ax_heat.legend(line_legend, [f"{pct}%" for pct in linestyles.keys()], title="Con
fontsize=fs-2, title_fontsize=fs, loc='lower right', frameon=True) fontsize=fs-2, title_fontsize=fs, loc='lower right', frameon=True)
plt.tight_layout(rect=[0,0,0.95,0.95]) plt.tight_layout(rect=[0,0,0.95,0.95])
plt.savefig("plots/shower_map_theory.pdf") plt.savefig("plots/shower_map_theory_depth.pdf")
plt.savefig("plots/shower_map_theory.png", dpi=300) plt.savefig("plots/shower_map_theory_depth.png", dpi=300)
plt.show() plt.show()

View file

@ -1,163 +0,0 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from scipy.stats import gamma
from matplotlib.lines import Line2D
# --------------------------
# Funktionen
# --------------------------
def tmax(E, Ec):
return np.log(E / Ec) - 0.5
def longitudinal_profile(t, E, Ec, b=0.5):
t_max_val = tmax(E, Ec)
a = b * t_max_val + 1
return gamma.pdf(t, a, scale=1/b) * E # MeV pro X0
def rho_r(r, Rm=2.26):
return np.exp(-r**2/(2*Rm**2)) / (2*np.pi*Rm**2)
# --------------------------
# Parameter
# --------------------------
X0_cm = 1.118
Ec_MeV = 10.5
b = 0.5
Rm_cm = 2.26
max_depth_cm = 10
energies_GeV = [0.1, 0.2, 0.5, 1.0, 2.0]
energies_MeV = [E*1000 for E in energies_GeV]
colors = ['C0', 'C1', 'C2', 'C3', 'C4']
fs = 16
# Heatmap-Konturen
heatmap_levels = [1, 10, 50, 90]
linestyles = {1:':', 10:'--', 50:'-.', 90:'-'}
# --------------------------
# Gitter
# --------------------------
r = np.linspace(0, 8, 300)
z = np.linspace(0, max_depth_cm, 400)
Z, R = np.meshgrid(z, r, indexing='ij')
# --------------------------
# Figure und GridSpec
# --------------------------
fig = plt.figure(figsize=(14,10))
fig.suptitle("Electromagnetic shower in BGO (theory)", fontsize=fs+2, y=0.97)
gs = GridSpec(2,2, width_ratios=[4,1], height_ratios=[4,1], hspace=0.3, wspace=0.1)
ax_heat = fig.add_subplot(gs[0,0])
ax_long = fig.add_subplot(gs[0,1], sharey=ax_heat)
ax_trans = fig.add_subplot(gs[1,0], sharex=ax_heat)
# --------------------------
# Heatmap + Konturen
# --------------------------
energy_lines = []
for E, color in zip(energies_MeV, colors):
t = Z / X0_cm
long_prof = longitudinal_profile(t, E, Ec_MeV, b) / X0_cm
sigma_r = Rm_cm * (1 + 0.03 * t)
trans_prof = np.exp(-(R**2)/(2*sigma_r**2)) / (2*np.pi*sigma_r**2)
shower_2D = long_prof * trans_prof
shower_norm = shower_2D / np.max(shower_2D) * 100
for pct, ls in linestyles.items():
ax_heat.contour(R, Z, shower_norm, levels=[pct], colors=[color], linestyles=[ls])
# Dummy-Linie für Initial Energy Legende
ln, = ax_heat.plot([], [], color=color, linewidth=2)
energy_lines.append(ln)
ax_heat.grid(True, linestyle='--', linewidth=0.5)
ax_heat.set_ylabel("Depth z [cm]", fontsize=fs)
ax_heat.set_ylim(max_depth_cm, 0)
ax_heat.set_xlim(0,8)
ax_heat.set_title("2D contourlines", fontsize=fs)
ax_heat.tick_params(labelsize=fs)
# --------------------------
# Konturlinien-Legende
# --------------------------
line_legend = [Line2D([0],[0], color='k', linestyle=ls, lw=2) for ls in linestyles.values()]
leg_contour = ax_heat.legend(line_legend, [f"{pct}%" for pct in linestyles.keys()],
title="Contour % of max", fontsize=fs-2, title_fontsize=fs,
loc='lower right', frameon=True)
ax_heat.add_artist(leg_contour)
# --------------------------
# Initial Energy Legende oben rechts (Heatmap)
# --------------------------
leg_energy = ax_heat.legend(
handles=energy_lines,
labels=[f"{E/1000:.1f} GeV" for E in energies_MeV],
title="Initial Energy",
fontsize=fs-2,
title_fontsize=fs,
loc="upper right",
frameon=True,
framealpha=0.85,
facecolor="white"
)
ax_heat.add_artist(leg_energy)
# --------------------------
# Longitudinale Profile
# --------------------------
t = np.linspace(0, max_depth_cm/X0_cm, 400)
for E, color in zip(energies_MeV, colors):
profile = longitudinal_profile(t, E, Ec_MeV, b)/X0_cm
ax_long.plot(profile, t*X0_cm, color=color, linewidth=2)
ax_long.set_title("Longitudinal Profiles", fontsize=fs)
ax_long.set_xlabel("dE/dz [MeV/cm]", fontsize=fs)
ax_long.grid(True)
ax_long.set_ylim(max_depth_cm, 0)
ax_long.tick_params(labelsize=fs)
# --------------------------
# Transversale Profile über gesamte Tiefe
# --------------------------
r_trans = np.linspace(0, 8, 300)
for E, color in zip(energies_MeV, colors):
t = Z / X0_cm
long_prof = longitudinal_profile(t, E, Ec_MeV, b) / X0_cm
sigma_r = Rm_cm * (1 + 0.03 * t)
trans_prof = np.exp(-(R**2)/(2*sigma_r**2)) / (2*np.pi*sigma_r**2)
shower_2D = long_prof * trans_prof
radial_profile = np.trapz(shower_2D, z, axis=0) # Integration über Tiefe
ax_trans.plot(r_trans, radial_profile, color=color, linewidth=2)
# --------------------------
# Molier-Radien Linien (Transversal)
# --------------------------
molier_radii = [Rm_cm, 2*Rm_cm]
molier_widths = [1.5, 2.0]
molier_lines = []
for r_val, lw in zip(molier_radii, molier_widths):
ln = ax_trans.axvline(r_val, color='k', linestyle='-', linewidth=lw)
molier_lines.append(ln)
ax_trans.axvline(-r_val, color='k', linestyle='-', linewidth=lw)
ax_trans.set_xlabel("Radius r [cm]", fontsize=fs)
ax_trans.set_ylabel(r"$\int \langle dE/(dz\,dA) \rangle$" + "\n[MeV/cm²/nuc]", fontsize=fs)
ax_trans.set_title("Transverse Profiles integrated over BGO depth", fontsize=fs)
ax_trans.grid(True)
ax_trans.set_xlim(0,8)
ax_trans.tick_params(labelsize=fs)
# --------------------------
# Legende Molier-Radien (Transversal oben rechts)
# --------------------------
leg_molier = ax_trans.legend(molier_lines, [f"{r_val:.2f} cm" for r_val in molier_radii],
title="Molière Radii", fontsize=fs-2, title_fontsize=fs,
loc="upper right", frameon=True, framealpha=0.85, facecolor="white")
ax_trans.add_artist(leg_molier)
plt.tight_layout(rect=[0,0,0.95,0.95])
plt.savefig("plots/shower_map_theory_depth.pdf")
plt.savefig("plots/shower_map_theory_depth.png", dpi=300)
plt.show()

View file

@ -1,3 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
general shower theory, different normalisations
"""
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from scipy.stats import gamma from scipy.stats import gamma
@ -5,7 +11,7 @@ from scipy.stats import gamma
# -------------------------- # --------------------------
# Font size # Font size
# -------------------------- # --------------------------
fs = 16 fs = 18
# -------------------------- # --------------------------
# Material (BGO) # Material (BGO)
@ -40,7 +46,7 @@ def rho_r(r):
# ============================================================ # ============================================================
# Plot 1: Longitudinal profile # Plot 1: Longitudinal profile
# ============================================================ # ============================================================
z = np.linspace(0, 10, 500) z = np.linspace(0, 10, 400)
plt.figure(figsize=(12, 6)) plt.figure(figsize=(12, 6))
@ -51,7 +57,7 @@ for E, lab, col in zip(energies, labels, colors):
plt.xlabel("Depth z [cm]", fontsize=fs) plt.xlabel("Depth z [cm]", fontsize=fs)
plt.ylabel(r"$\langle dE/dz \rangle$ [MeV/cm/nuc]", fontsize=fs) plt.ylabel(r"$\langle dE/dz \rangle$ [MeV/cm/nuc]", fontsize=fs)
plt.title("Longitudinal energy deposition in BGO", fontsize=fs) plt.title("Longitudinal energy deposition in BGO (theory)", fontsize=fs+2)
plt.xlim(0, z.max()) plt.xlim(0, z.max())
plt.ylim(0, None) plt.ylim(0, None)
@ -75,7 +81,7 @@ plt.savefig("plots/BGO_longitudinal_profile_normed.pdf")
# ============================================================ # ============================================================
# Plot 2: Transverse profile at shower maximum # Plot 2: Transverse profile at shower maximum
# ============================================================ # ============================================================
r = np.linspace(0, 8, 400) r = np.linspace(0, 8, 300)
plt.figure(figsize=(12, 6)) plt.figure(figsize=(12, 6))
@ -97,7 +103,7 @@ rm2_line = plt.axvline(2*Rm, color='k', linestyle=':', linewidth=1.6, label=r"$
plt.xlabel("Radius r [cm]", fontsize=fs) plt.xlabel("Radius r [cm]", fontsize=fs)
plt.ylabel(r"$\langle dE/(dz\,dA) \rangle$ [MeV/cm$^2$/nuc]", fontsize=fs) plt.ylabel(r"$\langle dE/(dz\,dA) \rangle$ [MeV/cm$^2$/nuc]", fontsize=fs)
plt.title("Transverse energy density at shower maximum", fontsize=fs) plt.title("Transverse energy density at shower maximum (theory)", fontsize=fs+2)
plt.xlim(0, r.max()) plt.xlim(0, r.max())
plt.ylim(0, None) plt.ylim(0, None)
@ -131,4 +137,62 @@ plt.xticks(fontsize=fs)
plt.yticks(fontsize=fs) plt.yticks(fontsize=fs)
plt.tight_layout() plt.tight_layout()
plt.savefig("plots/BGO_transverse_profile_normed.pdf") plt.savefig("plots/BGO_transverse_profile_normed.pdf")
# ============================================================
# Plot 3: Transverse profile integrated over entire BGO
# ============================================================
plt.figure(figsize=(12, 6))
dz = z[1]-z[0]
energy_lines = []
for E, lab, col in zip(energies, labels, colors):
# longitudinales Profil
prof_z = dEdz(z, E) # MeV/cm
# Integration über z → Energie pro Fläche
radial_profile = np.sum(prof_z[:, None] * rho_r(r)[None, :] * dz,axis=0) # MeV/cm²
ln, = plt.plot(r, radial_profile, color=col, linewidth=1.6,label=lab)
energy_lines.append(ln)
# Geometry markers
rm_line = plt.axvline(Rm, color='k', linestyle='--', linewidth=1.6, label=r"$R_M$")
rm2_line = plt.axvline(2*Rm, color='k', linestyle=':', linewidth=1.6, label=r"$2R_M$")
plt.xlabel("Radius r [cm]", fontsize=fs)
plt.ylabel(r"$\int \langle dE/(dz\,dA) \rangle\,dz$" + "\n[MeV/cm$^2$]",fontsize=fs)
plt.title("Transverse energy deposition integrated over 10 cm BGO (theory)", fontsize=fs+2)
plt.xlim(0, r.max())
plt.ylim(0, None)
# --- Legend 1: Energies ---
leg1 = plt.legend(
handles=energy_lines,
title="Initial Energy",
fontsize=fs-1,
title_fontsize=fs,
loc="upper right",
frameon=True,
framealpha=0.85,
facecolor="white"
)
# --- Legend 2: Geometry ---
leg2 = plt.legend(
handles=[rm_line, rm2_line],
fontsize=fs-1,
loc="lower right",
frameon=True,
framealpha=0.85,
facecolor="white"
)
plt.gca().add_artist(leg1)
plt.grid(True)
plt.xticks(fontsize=fs)
plt.yticks(fontsize=fs)
plt.tight_layout()
plt.savefig("plots/BGO_transverse_profile_depth.pdf")
plt.show() plt.show()

View file

@ -1,56 +1,138 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Transverse energy deposition profile from Geant4
Integrated over full BGO length (z-integrated)
Comparable to analytic shower theory
"""
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# --------- Dateien --------- # -------------------------------------------------
# Input files
# -------------------------------------------------
files = [ files = [
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits", #"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits", #"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
] ]
labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"] labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
colors = ["C0", "C1", "C2", "C3", "C4"] colors = ["C0", "C1", "C2", "C3", "C4"]
fs=18
# --------- Parameter --------- labels = ["100 MeV", "200 MeV", "500 MeV",]
colors = ["C0", "C1", "C2"]
# -------------------------------------------------
# Parameters
# -------------------------------------------------
r_max_cm = 8.0 r_max_cm = 8.0
n_bins = 100 n_bins = 100
fs = 18
# --------- Plot --------- # -------------------------------------------------
plt.figure(figsize=(12,6)) # Plot
# -------------------------------------------------
plt.figure(figsize=(12, 6))
for file, label, color in zip(files, labels, colors): for file, label, color in zip(files, labels, colors):
df = pd.read_csv(file, sep="\t") df = pd.read_csv(file, sep="\t")
# restrict to BGO radius
df = df[df["r"] <= r_max_cm] df = df[df["r"] <= r_max_cm]
r_edges = np.linspace(0, r_max_cm, n_bins + 1) # number of primary events
dr = r_edges[1] - r_edges[0] N_events = df["event"].nunique()
# r-binning
r_edges = np.linspace(0.0, r_max_cm, n_bins + 1)
r_centers = 0.5 * (r_edges[:-1] + r_edges[1:])
# energy sum per radial bin
E_sum, _ = np.histogram(df["r"], bins=r_edges, weights=df["edep"]) E_sum, _ = np.histogram(df["r"], bins=r_edges, weights=df["edep"])
counts, _ = np.histogram(df["r"], bins=r_edges)
profile = np.zeros_like(E_sum) # ring areas
mask = counts > 0 r_in = r_edges[:-1]
profile[mask] = E_sum[mask] / counts[mask] / dr # dE/dr [MeV/cm] r_out = r_edges[1:]
A_ring = np.pi * (r_out**2 - r_in**2)
r_centers = (r_edges[:-1] + r_edges[1:]) / 2 # <∫ dE / dA dz> [MeV/cm^2]
plt.step(r_centers, profile, lw=2, color=color, label=label) profile = E_sum / (N_events * A_ring)
plt.xlabel("Radius r [cm]",fontsize=fs) plt.step(
plt.ylabel("dE/dr [MeV/cm]",fontsize=fs) r_centers,
plt.title("Transversal shower profile", fontsize=fs+2) profile,
where="mid",
lw=2,
color=color,
label=label
)
plt.xlabel("Radius r [cm]", fontsize=fs)
plt.ylabel(r"$\left\langle \int \frac{dE}{dz\,dA}\,dz \right\rangle$ [MeV/cm$^2$]", fontsize=fs)
plt.title("Transverse energy deposition in BGO (z-integrated, Geant4)", fontsize=fs+2)
plt.xlim(0, r_max_cm) plt.xlim(0, r_max_cm)
plt.tick_params(axis='both', which='major', labelsize=fs-5) plt.grid(True, ls="--", lw=0.6)
plt.grid(True, ls="--", lw=0.5) plt.tick_params(labelsize=fs-2)
plt.legend(title="Initial Energy") plt.legend(title="Initial Energy")
plt.tight_layout() plt.tight_layout()
plt.savefig("plots/shower_transverse.png", dpi=300) plt.savefig("plots/G4_transverse_profile_integrated.png", dpi=300)
plt.show() plt.show()
# # --------- Dateien ---------
# files = [
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
# ]
# labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
# colors = ["C0", "C1", "C2", "C3", "C4"]
# fs=18
# # --------- Parameter ---------
# r_max_cm = 8.0
# n_bins = 100
# # --------- Plot ---------
# plt.figure(figsize=(12,6))
# for file, label, color in zip(files, labels, colors):
# df = pd.read_csv(file, sep="\t")
# df = df[df["r"] <= r_max_cm]
# r_edges = np.linspace(0, r_max_cm, n_bins + 1)
# dr = r_edges[1] - r_edges[0]
# E_sum, _ = np.histogram(df["r"], bins=r_edges, weights=df["edep"])
# counts, _ = np.histogram(df["r"], bins=r_edges)
# profile = np.zeros_like(E_sum)
# mask = counts > 0
# profile[mask] = E_sum[mask] / counts[mask] / dr # dE/dr [MeV/cm]
# r_centers = (r_edges[:-1] + r_edges[1:]) / 2
# plt.step(r_centers, profile, lw=2, color=color, label=label)
# plt.xlabel("Radius r [cm]",fontsize=fs)
# plt.ylabel("dE/dr [MeV/cm]",fontsize=fs)
# plt.title("Transversal shower profile", fontsize=fs+2)
# plt.xlim(0, r_max_cm)
# plt.tick_params(axis='both', which='major', labelsize=fs-5)
# plt.grid(True, ls="--", lw=0.5)
# plt.legend(title="Initial Energy")
# plt.tight_layout()
# plt.savefig("plots/shower_transverse.png", dpi=300)
# plt.show()

117
shower_trans_max.py Normal file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Transverse energy density at shower maximum from Geant4
Comparable to analytic shower theory (Plot 2)
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# -------------------------------------------------
# Material & theory parameters (BGO)
# -------------------------------------------------
X0 = 1.118 # cm
Ec = 10.5 # MeV
b = 0.5
def tmax(E):
"""Shower maximum in units of X0"""
return np.log(E / Ec) - 0.5
# -------------------------------------------------
# Input files and energies
# -------------------------------------------------
files = [
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
]
energies = [100, 200, 500, 1000, 2000] # MeV
labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
colors = ["C0", "C1", "C2", "C3", "C4"]
# -------------------------------------------------
# Analysis parameters
# -------------------------------------------------
r_max_cm = 8.0
n_bins = 80
dz_slice = 0.5 # cm (slice thickness around shower maximum)
fs = 18
# -------------------------------------------------
# Plot
# -------------------------------------------------
plt.figure(figsize=(12, 6))
for file, E, label, color in zip(files, energies, labels, colors):
df = pd.read_csv(file, sep="\t")
# Number of primary events
N_events = df["event"].nunique()
# Shower maximum position from theory
z_max = tmax(E) * X0
# Select z-slice around shower maximum
df_slice = df[
(df["z"] >= z_max - dz_slice/2) &
(df["z"] <= z_max + dz_slice/2)
]
# Restrict to detector radius
df_slice = df_slice[df_slice["r"] <= r_max_cm]
# Radial binning
r_edges = np.linspace(0.0, r_max_cm, n_bins + 1)
r_centers = 0.5 * (r_edges[:-1] + r_edges[1:])
# Energy sum per radial bin
E_sum, _ = np.histogram(
df_slice["r"],
bins=r_edges,
weights=df_slice["edep"]
)
# Ring areas
r_in = r_edges[:-1]
r_out = r_edges[1:]
A_ring = np.pi * (r_out**2 - r_in**2)
# <dE / (dz dA)> [MeV / (cm^3)/nuc]
profile = E_sum / (N_events * dz_slice * A_ring)
plt.step(
r_centers,
profile,
where="mid",
lw=2,
color=color,
label=label
)
# -------------------------------------------------
# Plot cosmetics
# -------------------------------------------------
plt.xlabel("Radius r [cm]", fontsize=fs)
plt.ylabel(
r"$\left\langle \frac{dE}{dz\,dA} \right\rangle$ [MeV/cm$^3$]",
fontsize=fs
)
plt.title("Transverse energy density at shower maximum (Geant4)", fontsize=fs+2)
plt.xlim(0, r_max_cm)
plt.grid(True, ls="--", lw=0.6)
plt.tick_params(labelsize=fs-2)
plt.legend(title="Initial Energy")
plt.tight_layout()
plt.savefig("plots/G4_transverse_profile_showermax.png", dpi=300)
plt.show()

View file

@ -1,11 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Transverse energy deposition in 2 cm BGO layers from Geant4 simulation
Step-wise plotting with correct normalization per primary particle
"""
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# --------- Dateien --------- # --------------------------
# Input files and parameters
# --------------------------
files = [ files = [
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
@ -14,56 +20,170 @@ files = [
"/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits", "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
] ]
labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"] energies = [100, 200, 500, 1000, 2000] # MeV
colors = ["C0", "C1", "C2", "C3", "C4"] colors = ["C0","C1","C2","C3","C4"]
fs = 18 # Schriftgröße fs = 14
# --------------------------
# Analysis parameters
# --------------------------
z_max_cm = 10.0
r_max_cm = 8.0 r_max_cm = 8.0
n_bins = 100 dz_layer = 2.0 # cm
n_particles = 10000 # Anzahl der simulierten Teilchen n_bins_r = 100
# --------- Subplots --------- layers = [(0,2), (2,4), (4,6), (6,8), (8,10)] # 2 cm slices
fig, axes = plt.subplots(5, 1, figsize=(8, 12), sharex=True) n_layers = len(layers)
z_slices = [(0, 2), (2, 4), (4, 6), (6, 8)]
dz_slice = 2.0 # Breite der Slice in cm
# --------- Plotten --------- r_edges = np.linspace(0, r_max_cm, n_bins_r + 1)
for file, label, color in zip(files, labels, colors): r_centers = 0.5 * (r_edges[:-1] + r_edges[1:])
df = pd.read_csv(file, sep="\t") dr = r_edges[1] - r_edges[0]
df = df[df["r"] <= r_max_cm]
r_edges = np.linspace(0, r_max_cm, n_bins + 1) # --------------------------
dr = r_edges[1] - r_edges[0] # Figure
r_centers = (r_edges[:-1] + r_edges[1:]) / 2 # --------------------------
fig, axes = plt.subplots(n_layers+1, 1, figsize=(10,12), sharex=True)
plt.subplots_adjust(hspace=0.1)
fig.suptitle("Transverse energy deposition in BGO layers (Geant4)", fontsize=16, y=0.95)
# --------- Obere 4 Slices --------- # Dummy-Linien für Startenergie-Legende
for i, (z_min, z_max) in enumerate(z_slices): dummy_lines = [axes[0].plot([], [], color=c, linewidth=2)[0] for c in colors]
axes[0].legend(dummy_lines, [f"{E} MeV" for E in energies],
ncol=5, fontsize=12, frameon=True, framealpha=0.85,
loc='upper center', bbox_to_anchor=(0.5, 1.05))
# Gemeinsames Y-Label
fig.text(0.02, 0.5, r"$\langle dE/(dz\,dA) \rangle$ [MeV/cm$^2$/prim]",
va='center', rotation='vertical', fontsize=16)
# --------------------------
# Layerwise plotting with integrals
# --------------------------
for i, (z_min, z_max) in enumerate(layers + [(0,z_max_cm)]):
ax = axes[i]
for file, E, color in zip(files, energies, colors):
df = pd.read_csv(file, sep="\t")
# Primärereignisse
N_events = df["event"].nunique()
# Slice mask
df_slice = df[(df["z"] >= z_min) & (df["z"] < z_max)] df_slice = df[(df["z"] >= z_min) & (df["z"] < z_max)]
df_slice = df_slice[df_slice["r"] <= r_max_cm]
# Histogram radial (Summe der Steps pro Ring)
E_sum, _ = np.histogram(df_slice["r"], bins=r_edges, weights=df_slice["edep"]) E_sum, _ = np.histogram(df_slice["r"], bins=r_edges, weights=df_slice["edep"])
# Mittelwert pro Teilchen pro cm
profile = E_sum / (n_particles * dz_slice)
axes[i].step(r_centers, profile, lw=2, color=color, label=label if i==0 else "")
axes[i].set_ylim(0, None)
axes[i].tick_params(axis='both', which='major', labelsize=fs-4)
# kleines y-Label rechts mit Slice
axes[i].text(r_max_cm*1.01, axes[i].get_ylim()[1]*0.9, f"{z_min}-{z_max} cm", rotation=0, va="top", fontsize=fs-6)
# --------- Unterer Plot: gesamte Summe 0-8cm --------- # Ringflächen
df_all = df[df["r"] <= r_max_cm] r_in = r_edges[:-1]
E_sum_total, _ = np.histogram(df_all["r"], bins=r_edges, weights=df_all["edep"]) r_out = r_edges[1:]
profile_total = E_sum_total / (n_particles * 8.0) # mittlerer Energieverlust pro cm A_ring = np.pi * (r_out**2 - r_in**2)
axes[4].step(r_centers, profile_total, lw=2, color='k')
axes[4].set_ylim(0, None)
axes[4].tick_params(axis='both', which='major', labelsize=fs-4)
axes[4].text(r_max_cm*1.01, axes[4].get_ylim()[1]*0.9, "0-8 cm", rotation=0, va="top", fontsize=fs-6)
# --------- Gemeinsames y-Label --------- # Normierung <dE/(dz dA)> pro Primärteilchen
fig.text(0.02, 0.5, "Energy loss [MeV/cm]", va='center', rotation='vertical', fontsize=fs) profile = E_sum / (N_events * (z_max - z_min) * A_ring)
# --------- Achsen & Legende --------- # Gesamtenergie pro Primärteilchen in dieser Schicht
axes[-1].set_xlabel("Radius r [cm]", fontsize=fs) E_layer = E_sum.sum() / N_events
axes[0].legend(title="Initial Energy", loc="upper right", fontsize=fs-4) linestyle = '--' if i==n_layers else '-' # Summe gestrichelt
plt.tight_layout(rect=[0.05,0.03,1,0.97]) # Step-Plot
plt.savefig("plots/shower_transverse_slices.png", dpi=300) ax.step(r_centers, profile, where='mid', color=color, linewidth=2,
linestyle=linestyle, label=f"{E_layer:.1f} MeV")
ax.set_xlim(0, r_max_cm)
ax.set_yscale('log')
ax.set_ylim(0.009, None)
ax.grid(True)
ax.tick_params(labelsize=fs)
if i < n_layers:
ax.set_ylabel(f"{z_min}-{z_max} cm", fontsize=fs)
else:
ax.set_ylabel("Sum", fontsize=fs)
ax.set_xlabel("Radius r [cm]", fontsize=fs)
# Legende innerhalb der Achse
ax.legend(fontsize=12, frameon=True, framealpha=0.85, facecolor="white")
plt.tight_layout(rect=[0.05,0.03,0.97,0.93])
plt.savefig("plots/G4_transverse_layers_sum.pdf")
plt.savefig("plots/G4_transverse_layers_sum.png", dpi=300)
plt.show() plt.show()
# #!/usr/bin/env python3
# # -*- coding: utf-8 -*-
# """
# transversal shower profile form Genatz4 simulations slicewise
# """
# import numpy as np
# import pandas as pd
# import matplotlib.pyplot as plt
# # --------- Dateien ---------
# files = [
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_100MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_200MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_500MeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_1GeV_0.hits",
# "/home/et189/Geant4/showering/build/out/BGO_shower_e_10-5_2GeV_0.hits",
# ]
# labels = ["100 MeV", "200 MeV", "500 MeV", "1 GeV", "2 GeV"]
# colors = ["C0", "C1", "C2", "C3", "C4"]
# fs = 18 # Schriftgröße
# r_max_cm = 8.0
# n_bins = 100
# n_particles = 10000 # Anzahl der simulierten Teilchen
# # --------- Subplots ---------
# fig, axes = plt.subplots(5, 1, figsize=(8, 12), sharex=True)
# z_slices = [(0, 2), (2, 4), (4, 6), (6, 8)]
# dz_slice = 2.0 # Breite der Slice in cm
# # --------- Plotten ---------
# for file, label, color in zip(files, labels, colors):
# df = pd.read_csv(file, sep="\t")
# df = df[df["r"] <= r_max_cm]
# r_edges = np.linspace(0, r_max_cm, n_bins + 1)
# dr = r_edges[1] - r_edges[0]
# r_centers = (r_edges[:-1] + r_edges[1:]) / 2
# # --------- Obere 4 Slices ---------
# for i, (z_min, z_max) in enumerate(z_slices):
# df_slice = df[(df["z"] >= z_min) & (df["z"] < z_max)]
# E_sum, _ = np.histogram(df_slice["r"], bins=r_edges, weights=df_slice["edep"])
# # Mittelwert pro Teilchen pro cm
# profile = E_sum / (n_particles * dz_slice)
# axes[i].step(r_centers, profile, lw=2, color=color, label=label if i==0 else "")
# axes[i].set_ylim(0, None)
# axes[i].tick_params(axis='both', which='major', labelsize=fs-4)
# # kleines y-Label rechts mit Slice
# axes[i].text(r_max_cm*1.01, axes[i].get_ylim()[1]*0.9, f"{z_min}-{z_max} cm", rotation=0, va="top", fontsize=fs-6)
# # --------- Unterer Plot: gesamte Summe 0-8cm ---------
# df_all = df[df["r"] <= r_max_cm]
# E_sum_total, _ = np.histogram(df_all["r"], bins=r_edges, weights=df_all["edep"])
# profile_total = E_sum_total / (n_particles * 8.0) # mittlerer Energieverlust pro cm
# axes[4].step(r_centers, profile_total, lw=2, color='k')
# axes[4].set_ylim(0, None)
# axes[4].tick_params(axis='both', which='major', labelsize=fs-4)
# axes[4].text(r_max_cm*1.01, axes[4].get_ylim()[1]*0.9, "0-8 cm", rotation=0, va="top", fontsize=fs-6)
# # --------- Gemeinsames y-Label ---------
# fig.text(0.02, 0.5, "Energy loss [MeV/cm]", va='center', rotation='vertical', fontsize=fs)
# # --------- Achsen & Legende ---------
# axes[-1].set_xlabel("Radius r [cm]", fontsize=fs)
# axes[0].legend(title="Initial Energy", loc="upper right", fontsize=fs-4)
# plt.tight_layout(rect=[0.05,0.03,1,0.97])
# plt.savefig("plots/shower_transverse_slices.png", dpi=300)
# plt.show()

View file

@ -1,3 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
transversal shower profile theory slicewise
"""
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from scipy.stats import gamma from scipy.stats import gamma
@ -43,7 +49,7 @@ n_layers = len(layers)
# -------------------------- # --------------------------
fig, axes = plt.subplots(n_layers+1, 1, figsize=(10,12), sharex=True) fig, axes = plt.subplots(n_layers+1, 1, figsize=(10,12), sharex=True)
plt.subplots_adjust(hspace=0.1) plt.subplots_adjust(hspace=0.1)
fig.suptitle("Transverse energy deposition in BGO layers", fontsize=16, y=0.95) fig.suptitle("Transverse energy deposition in BGO layers (theory)", fontsize=16, y=0.95)
# Dummy-Linien für Startenergie-Legende # Dummy-Linien für Startenergie-Legende
dummy_lines = [axes[0].plot([], [], color=c, linewidth=2)[0] for c in colors] dummy_lines = [axes[0].plot([], [], color=c, linewidth=2)[0] for c in colors]

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Simulationsdaten plotten im Layout wie Literaturplot Geant4 Simulationdata map like for literature
""" """
import numpy as np import numpy as np