codex/zeroth-law-thermometer-bath-calibration.py

zeroth-law-thermometer-bath-calibration.py
"""Draw a bath comparison and constructed calibration, with opaque white output.

Python 3.14, matplotlib 3.10.7, numpy 2.3.5. Run from _media.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Rectangle


def main():
    plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
    fig = plt.figure(figsize=(11.4, 5.0), dpi=110, facecolor="white")
    bath = fig.add_axes((.025, .16, .47, .68))
    plot = fig.add_axes((.60, .24, .36, .57))
    blue, orange, ink = "#2274a5", "#b55236", "#223344"
    bath.set(xlim=(0, 5), ylim=(0, 4))
    bath.axis("off")
    fig.text(.265, .93, "Compare probes in a common bath", ha="center",
             fontsize=15, weight="bold", color=ink)
    fig.text(.78, .93, "Assign a response scale", ha="center",
             fontsize=15, weight="bold", color=ink)

    bath.add_patch(Rectangle((.62, .66), 3.76, 1.96, facecolor="#e9f4fa",
                             edgecolor="none"))
    bath.plot([.6, .6, 4.4, 4.4], [3, .62, .62, 3], color=ink, linewidth=2)
    bath.plot([.62, 4.38], [2.62, 2.62], color=blue, linewidth=1.5)
    bath.add_patch(FancyBboxPatch((.58, .04), 3.84, .37,
                                  boxstyle="round,pad=.04", facecolor="#edf0f3",
                                  edgecolor=ink))
    bath.text(2.5, .225, "Stirrer on • heater off", ha="center", va="center", fontsize=11)
    for x, color, label in [(1.62, blue, "Reference"), (3.35, orange, "Test")]:
        bath.add_patch(FancyBboxPatch((x-.57, 3.30), 1.14, .42,
                                      boxstyle="round,pad=.05", facecolor="white",
                                      edgecolor=color, linewidth=1.5))
        bath.text(x, 3.51, label, ha="center", va="center", color=color, weight="bold")
        bath.plot([x, x], [3.25, 1.32], color=color, linewidth=4, solid_capstyle="round")
        bath.scatter([x], [1.32], s=70, color=color, zorder=4)
    bath.annotate("Tips clear\nof walls", xy=(3.34, 1.32),
                  xytext=(4.26, 1.93), ha="right", fontsize=10,
                  arrowprops={"arrowstyle": "->", "color": ink}, color=ink)
    bath.text(1.12, 2.22, "Water", ha="center", color=blue)
    bath.plot([2.05, 2.8], [.9, .9], color=ink, linewidth=6, solid_capstyle="round")
    bath.add_patch(FancyArrowPatch((1.94, .89), (2.81, 1.08),
                                   connectionstyle="arc3,rad=-.7", arrowstyle="->",
                                   color=blue, mutation_scale=14))
    bath.text(2.45, 1.59, "Gentle\nstirring", ha="center", color=blue, fontsize=11)
    fig.text(.265, .085, "Wait • apply reference correction • reverse reading order",
             ha="center", fontsize=10, color=ink)

    heights = np.linspace(12, 52, 200)
    temperatures = 40 * (heights - 12) / (52 - 12)
    plot.plot(heights, temperatures, color=blue, linewidth=2.5)
    plot.scatter([12, 52], [0, 40], s=70, color=blue, zorder=4, label="Reference inputs")
    plot.scatter([37], [25], s=80, color=orange, zorder=5)
    plot.plot([37, 37, 7], [-5, 25, 25], color=orange, linestyle="--", linewidth=1.2)
    plot.annotate("37 mm → 25 °C", xy=(37, 25), xytext=(24, 32),
                  color=orange, weight="bold", fontsize=11,
                  arrowprops={"arrowstyle": "->", "color": orange})
    plot.set(xlim=(7, 57), ylim=(-5, 45), xticks=[12, 22, 32, 37, 42, 52],
             yticks=[0, 10, 20, 25, 30, 40], xlabel="Column height X (mm)",
             ylabel="Assigned temperature (°C)")
    plot.tick_params(labelsize=10)
    plot.spines[["top", "right"]].set_visible(False)
    plot.grid(alpha=.2)
    fig.text(.78, .085, "Constructed values • linear response assumed", ha="center",
             fontsize=10, color=ink)
    fig.text(.5, .025, "Bath apparatus is schematic; calibration values are a separate worked example, not measured data.",
             ha="center", fontsize=10, color="#566575")
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()