codexcosmic-ray-competing-altitude-models.py
cosmic-ray-competing-altitude-models.py
"""Generate hypothetical altitude attenuation curves and a density confound.
Run from _media. Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Pure illustrative attenuation: no fitted Hess data and no shower production.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def altitude_model(height_km):
"""Return ground, extraterrestrial, fixed-gas and vented responses."""
x = np.exp(-np.asarray(height_km) / 8)
ground = 0.8 * np.exp(-(1 - x) / 0.08)
extraterrestrial = 6.0 * np.exp(-x / 0.45)
fixed = 0.15 + ground + extraterrestrial
return ground, extraterrestrial, fixed, fixed * x
def draw():
plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(1, 2, figsize=(11.2, 5.8), dpi=120, facecolor="white")
fig.subplots_adjust(top=0.77, bottom=0.24, left=0.075, right=0.975, wspace=0.27)
fig.suptitle("Competing altitude trends and the gas-density confound", fontsize=17, y=0.96)
fig.text(0.5, 0.875, "Hypothetical attenuation model • no shower production • no Hess data points",
ha="center", fontsize=11, color="#555555")
z = np.linspace(0, 6, 501)
ground, extraterrestrial, fixed, vented = altitude_model(z)
ax = axes[0]
ax.plot(z, fixed, color="#bb4d21", lw=2.8, label="Ground + extraterrestrial + B")
ax.plot(z, ground + 0.15, color="#245d8d", lw=2.5, label="Ground only + B")
ax.plot(z, extraterrestrial, color="#38815b", lw=2, ls="--", label="Extraterrestrial component E")
ax.axhline(0.15, color="#888888", lw=1, ls=":")
x_min = (1 / 0.08 - np.log(0.8 * 0.45 / (6 * 0.08))) / (1 / 0.08 + 1 / 0.45)
z_min = -8 * np.log(x_min)
q_min = altitude_model(z_min)[2]
ax.annotate("Model minimum\n1.13 km", xy=(z_min, q_min), xytext=(2.9, 0.62),
arrowprops={"arrowstyle": "->", "color": "#555555"}, ha="center", fontsize=10)
ax.set(title="Competing source hypotheses", ylabel="Fixed-gas response (arbitrary units)", ylim=(0, 2.35))
ax.legend(loc="upper left", fontsize=8.5, framealpha=1)
ax = axes[1]
ax.plot(z, fixed / fixed[0], color="#bb4d21", lw=2.8, label="Fixed detector gas density")
ax.plot(z, vented / vented[0], color="#245d8d", lw=2.5, label="Vented, no density correction")
ax.plot(z, np.exp(-z / 8), color="#777777", ls=":", lw=1.8, label=r"Gas density ratio $\rho/\rho_0$")
ax.set(title="Same mixed radiation field", ylabel="Response / own ground response", ylim=(0.4, 1.55))
ax.legend(loc="upper left", fontsize=8.5, framealpha=1)
ax.text(5.85, fixed[-1] / fixed[0] + 0.04, f"{fixed[-1] / fixed[0]:.2f}",
color="#bb4d21", ha="right", fontsize=11)
ax.text(5.85, vented[-1] / vented[0] + 0.04, f"{vented[-1] / vented[0]:.2f}",
color="#245d8d", ha="right", fontsize=11)
for ax in axes:
ax.set(xlabel="Altitude above ground (km)", xlim=(0, 6))
ax.grid(alpha=0.18)
ax.spines[["top", "right"]].set_visible(False)
fig.text(0.5, 0.135, r"$H=8$ km, $G_0=0.8$, $E_{\rm top}=6$, $B=0.15$, "
r"$\Lambda_g/X_0=0.08$, $\Lambda_e/X_0=0.45$", ha="center", fontsize=11)
fig.text(0.5, 0.065, "The vented model assumes response ∝ gas density; dividing by density recovers the fixed-gas curve.",
ha="center", fontsize=10)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
draw()