codex/cosmic-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()