from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
H_SCALE = 100
N_BENCHMARK = 4
def mmh_pmf(rho: float, H: int) -> np.ndarray:
if abs(rho - 1.0) < 1e-10:
return np.ones(H + 1) / (H + 1)
qs = np.arange(H + 1, dtype=float)
unnorm = rho ** qs
return unnorm / unnorm.sum()
def deflection_prob(rho: float, H: int) -> float:
return float(mmh_pmf(rho, H)[H])
def solve_sc(rho0_base: float, N: int, H: int, max_iter: int = 500,
tol: float = 1e-10) -> float:
rho = rho0_base
for _ in range(max_iter):
pd = deflection_prob(rho, H)
rho_new = rho0_base * (1.0 + pd)
if abs(rho_new - rho) < tol:
return rho_new
rho = rho_new
return float("nan")
rho0_sweep = np.linspace(0.5, 0.99, 60)
rho0_markers = [0.5, 0.7, 0.9, 0.95, 0.99]
level0_curve, level1_curve = [], []
for rho0 in rho0_sweep:
rho_star = solve_sc(rho0, N_BENCHMARK, H_SCALE)
pd0 = deflection_prob(rho_star, H_SCALE)
dpd1 = pd0 * rho0 * pd0 / (N_BENCHMARK - 1)
level0_curve.append(max(pd0, 1e-300))
level1_curve.append(max(dpd1, 1e-300))
plt.rcParams.update({"font.family": "serif", "font.size": 10.5})
fig, ax = plt.subplots(figsize=(6.6, 4.4))
ax.plot(rho0_sweep, level0_curve, "-", color="#2E86C1", lw=2.2,
label=r"Level-0 (mean field): $p_d$")
ax.plot(rho0_sweep, level1_curve, "--", color="#E67E22", lw=2.2,
label=r"Level-1 (pair correlation): $\Delta p_d^{(1)}$")
for r in rho0_markers:
rs = solve_sc(r, N_BENCHMARK, H_SCALE)
pd0 = deflection_prob(rs, H_SCALE)
dpd1 = pd0 * r * pd0 / (N_BENCHMARK - 1)
ax.scatter([r], [max(pd0, 1e-300)], color="#2E86C1", zorder=5, s=28)
ax.scatter([r], [max(dpd1, 1e-300)], color="#E67E22", zorder=5, s=28)
ax.set_yscale("log")
ax.set_xlabel(r"Offered load $\varrho_0$")
ax.set_ylabel(r"Deflection probability component")
ax.set_title(f"Level-0/Level-1 decomposition of $p_d$ across load levels\n"
f"(Benchmark: $N={N_BENCHMARK}$, $H={H_SCALE}$)", fontsize=10.5)
ax.legend(fontsize=8.5, loc="lower right")
ax.grid(True, which="both", linestyle="--", alpha=0.3)
fig.tight_layout()
out = Path(__file__).parent.parent / "figure" / "component_breakdown.png"
out.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out, dpi=150)
print(f"Saved: {out}")
for r in rho0_markers:
rs = solve_sc(r, N_BENCHMARK, H_SCALE)
pd0 = deflection_prob(rs, H_SCALE)
dpd1 = pd0 * r * pd0 / (N_BENCHMARK - 1)
print(f" rho0={r}: p_d(L0)={pd0:.6g} Delta_p_d(L1)={dpd1:.6g} "
f"L1/L0 fraction={dpd1/max(pd0,1e-300):.4%}")