codexdistance-modulus-ideal-source.py
distance-modulus-ideal-source.py
"""Original distance/extinction toy diagram; writes its PNG basename to cwd.
Run from _media. Tested with Python 3.14.4, matplotlib 3.10.7, numpy 2.3.5.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "savefig.facecolor": "white"})
fig, (overview, inference) = plt.subplots(1, 2, figsize=(10.8, 4.9),
layout="constrained", facecolor="white")
fig.suptitle(r"One ideal source, fixed absolute magnitude $M=0$", fontsize=16)
distance = np.geomspace(5, 1500, 400)
magnitude = 5 * np.log10(distance / 10)
blue, orange = "#175c9d", "#b95712"
overview.plot(distance, magnitude, color=blue, lw=2.5, label=r"$A=0$ (transparent)")
overview.plot(distance, magnitude + 1, color=orange, lw=2.3,
label=r"$A=1$ (fixed offset)")
overview.set(xscale="log", xlim=(5, 1500), ylim=(-2.3, 12.5),
xlabel="True distance d (parsecs; logarithmic axis)",
ylabel="Apparent magnitude m (larger = fainter)",
title=r"$m=5\log_{10}(d/10\,\mathrm{pc})+A$")
overview.set_xticks([10, 100, 1000], labels=["10", "100", "1000"])
overview.set_yticks([0, 5, 10])
overview.grid(alpha=0.23)
overview.legend(loc="upper left", fontsize=10)
for d, m in [(10, 0), (100, 5), (1000, 10)]:
overview.scatter([d], [m], color=blue, zorder=3)
overview.annotate(f"({d} pc, m={m})", (d, m), xytext=(6, -20),
textcoords="offset points", fontsize=9,
ha="right" if d == 1000 else "left")
zoom = np.geomspace(70, 220, 200)
inference.plot(zoom, 5 * np.log10(zoom / 10), color=blue, lw=2.5)
inference.plot(zoom, 5 * np.log10(zoom / 10) + 1, color=orange, lw=2.3)
true_distance = 100
inferred_distance = true_distance * 10 ** (1 / 5)
observed_magnitude = 6
inference.set(xscale="log", xlim=(70, 220), ylim=(4.5, 7.6),
xlabel="Distance (parsecs; logarithmic axis)",
ylabel="Apparent magnitude m", title="Ignoring extinction overestimates distance")
inference.set_xticks([100, inferred_distance, 200], labels=["100", "158.49", "200"])
inference.set_yticks([5, 6, 7])
inference.grid(alpha=0.23)
inference.hlines(observed_magnitude, 72, 210, color="#555555", linestyle=":")
inference.scatter([true_distance], [observed_magnitude], color=orange, zorder=4)
inference.scatter([inferred_distance], [observed_magnitude], color=blue, zorder=4)
inference.annotate("True: 100 pc\nwith A=1", (true_distance, observed_magnitude),
xytext=(78, 7.05), fontsize=10,
arrowprops={"arrowstyle": "->", "color": orange})
inference.annotate("Inferred: 158.49 pc\nassuming A=0", (inferred_distance, observed_magnitude),
xytext=(125, 4.67), fontsize=10,
arrowprops={"arrowstyle": "->", "color": blue})
inference.annotate("", (inferred_distance, 6.12), (true_distance, 6.12),
arrowprops={"arrowstyle": "<->", "color": "#333333"})
inference.text(126, 6.25, r"$\times 10^{1/5}\approx1.58489$", ha="center", fontsize=10)
fig.supxlabel("Illustrative toy values; unchanged emission, same passband; A=1 is not a dust-distance law",
fontsize=10)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=110,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()