codexalbedo-planet-energy-balance.py
albedo-planet-energy-balance.py
"""Write albedo-planet-energy-balance.png in CWD.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
An illustrative atmosphere-free planet has uniform temperature, prescribed
solar-band reflection, and thermal emissivity one; this is not an Earth forecast.
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Ellipse
import numpy as np
def main():
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
solar, sigma = 1360.0, 5.670374419e-8
albedos = np.array([0.3, 0.6])
incoming = solar / 4
reflected = incoming * albedos
absorbed = incoming * (1 - albedos)
temperatures = (absorbed / sigma) ** 0.25
assert np.allclose(reflected + absorbed, incoming)
assert np.allclose(temperatures, [254.531364159, 221.300193129], atol=1e-8)
fig = plt.figure(figsize=(11.2, 5.2), dpi=100)
grid = fig.add_gridspec(1, 3, width_ratios=[1.0, 1.0, 1.5],
left=0.03, right=0.98, bottom=0.27,
top=0.77, wspace=0.25)
sun_color, thermal_color = "#d58a00", "#a32638"
disk = fig.add_subplot(grid[0, 0])
disk.add_patch(Circle((0, 0), 1, facecolor="#fff1c7",
edgecolor=sun_color, lw=2, hatch="///"))
for y in [-0.65, 0, 0.65]:
end = -np.sqrt(1 - y*y)
disk.annotate("", xy=(end - 0.04, y), xytext=(-2.4, y),
arrowprops={"arrowstyle": "->", "color": sun_color, "lw": 2})
disk.plot([0, 0.70], [0, 0.70], color="0.3", lw=1.4)
disk.text(0.20, 0.55, "R", color="0.25")
disk.text(-1.65, 1.10, "sunlight S", ha="center", color=sun_color)
disk.text(0, -1.35, r"Intercepting area: $\pi R^2$", ha="center")
disk.set_title("Seen from the Sun", pad=20, fontsize=13)
disk.set(xlim=(-2.6, 1.55), ylim=(-1.65, 1.45), aspect="equal")
disk.axis("off")
sphere = fig.add_subplot(grid[0, 1])
sphere.add_patch(Circle((0, 0), 1, facecolor="#edf3f8",
edgecolor="#346a94", lw=2))
for width in [0.55, 1.45]:
sphere.add_patch(Ellipse((0, 0), width, 2, fill=False,
edgecolor="#8ba9bf", lw=1))
sphere.add_patch(Ellipse((0, 0), 2, 0.5, fill=False,
edgecolor="#8ba9bf", lw=1))
for theta in np.linspace(0, 2*np.pi, 8, endpoint=False):
v = np.array([np.cos(theta), np.sin(theta)])
sphere.annotate("", xy=1.50*v, xytext=1.04*v,
arrowprops={"arrowstyle": "->", "color": thermal_color, "lw": 1.6})
sphere.text(0, 0.17, "uniform T", ha="center", color="#254a66")
sphere.text(0, -0.20, r"$\sigma T^4$ per area", ha="center", color=thermal_color)
sphere.text(0, -1.85, r"Emitting area: $4\pi R^2$", ha="center")
sphere.set_title("Whole emitting sphere", pad=20, fontsize=13)
sphere.set(xlim=(-1.85, 1.85), ylim=(-2.10, 1.60), aspect="equal")
sphere.axis("off")
ax = fig.add_subplot(grid[0, 2])
yy = np.array([1.0, 0.0])
ax.barh(yy, reflected, color="#f4c767", label="reflected", height=0.52)
ax.barh(yy, absorbed, left=reflected, color="#3a7ba4", label="absorbed", height=0.52)
for y, a, r, q, t in zip(yy, albedos, reflected, absorbed, temperatures):
ax.text(r/2, y, f"{r:.0f}", ha="center", va="center", color="#332511")
ax.text(r+q/2, y, f"{q:.0f}", ha="center", va="center", color="white")
ax.text(340, y-0.36, f"equilibrium T = {t:.3f} K", ha="right", fontsize=11)
ax.set(yticks=yy, yticklabels=[r"$\alpha=0.30$", r"$\alpha=0.60$"],
xlim=(0, 355), ylim=(-0.65, 1.50), xticks=[0, 102, 204, 340],
xlabel=r"Global average power per area (W m$^{-2}$)")
ax.set_title("Same illumination, different albedo", pad=20, fontsize=13)
ax.spines[["top", "right", "left"]].set_visible(False)
ax.tick_params(axis="y", length=0)
ax.legend(loc="lower center", bbox_to_anchor=(0.52, -0.44),
ncol=2, frameon=False, fontsize=11)
fig.suptitle("Albedo controls absorbed sunlight; geometry supplies the factor four",
y=0.96, fontsize=15)
fig.text(0.31, 0.18,
r"$S\pi R^2\, /\,(4\pi R^2)=S/4=340\ \mathrm{W\,m^{-2}}$",
ha="center", fontsize=14)
fig.text(0.5, 0.035,
"Illustrative model: S = 1360 W m⁻² • no atmosphere • uniform T • thermal emissivity = 1",
ha="center", fontsize=11, color="0.3")
fig.savefig("albedo-planet-energy-balance.png", facecolor="white", transparent=False)
plt.close(fig)
print("Fixed albedos:", list(zip(albedos.tolist(), temperatures.tolist())))
if __name__ == "__main__":
main()