codexbeer-lambert-absorbance-transmission-calibration.py
beer-lambert-absorbance-transmission-calibration.py
"""Draw an original Beer-Lambert calibration model, not measured material data.
Tested with Python 3.14, matplotlib 3.10.7, and numpy 2.3.5.
Run from the output directory; no input files are needed.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
epsilon, length = 8000.0, 1.0
standards = np.array([0, 25, 50, 75, 100], dtype=float)
concentration = np.linspace(0, 105, 500)
absorbance = epsilon * length * concentration * 1e-6
standard_absorbance = epsilon * length * standards * 1e-6
transmission = 10.0 ** -absorbance
reading = 0.4
inferred_absorbance = -np.log10(reading)
inferred_concentration = inferred_absorbance / (epsilon * length) * 1e6
plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(1, 2, figsize=(9, 4.6), dpi=140, facecolor="white")
fig.subplots_adjust(left=0.07, right=0.975, bottom=0.18, top=0.74, wspace=0.25)
fig.suptitle("One calibration, two scales", fontsize=18, weight="bold", y=0.97)
fig.text(0.5, 0.87, r"Stipulated model: $\varepsilon=8000\ \mathrm{L\,mol^{-1}\,cm^{-1}},\quad l=1\ \mathrm{cm}$",
ha="center", fontsize=12)
fig.text(0.5, 0.04, "Circles: chosen standards | Diamond: stipulated T = 0.40 reading, c = 49.7425 μM",
ha="center", fontsize=11)
blue, orange, highlight = "#147a9a", "#b45019", "#6b3e91"
data = [(absorbance, standard_absorbance, inferred_absorbance, blue,
r"Absorbance $A$", r"$A=\varepsilon lc$"),
(transmission, 10.0 ** -standard_absorbance, reading, orange,
r"Transmittance $T=P/P_0$", r"$T=10^{-\varepsilon lc}$")]
for ax, (curve, dots, value, color, ylabel, formula) in zip(axes, data):
ax.plot(concentration, curve, color=color, linewidth=2.6)
ax.scatter(standards, dots, s=48, color=color, edgecolor="white", zorder=3)
ax.plot([inferred_concentration, inferred_concentration], [0, value],
linestyle="--", color=highlight, linewidth=1.4)
ax.plot([0, inferred_concentration], [value, value],
linestyle="--", color=highlight, linewidth=1.4)
ax.scatter([inferred_concentration], [value], marker="D", s=60,
color=highlight, edgecolor="white", zorder=4)
ax.set(xlim=(-3, 105), ylim=(0, 1.04), xlabel="Concentration c (μM)", ylabel=ylabel,
xticks=standards, yticks=np.arange(0, 1.01, 0.2))
ax.grid(alpha=0.22)
ax.spines[["top", "right"]].set_visible(False)
ax.text(0.97, 0.93, formula, transform=ax.transAxes, ha="right", color=color, fontsize=14)
axes[0].annotate("A = 0.397940", xy=(inferred_concentration, inferred_absorbance),
xytext=(59, 0.20), color=highlight, fontsize=11,
arrowprops={"arrowstyle": "-", "color": highlight})
axes[1].annotate("T = 0.40", xy=(inferred_concentration, reading),
xytext=(60, 0.61), color=highlight, fontsize=11,
arrowprops={"arrowstyle": "-", "color": highlight})
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()