codex/hess-law-cancellation-and-calorimetry.py

hess-law-cancellation-and-calorimetry.py
"""Draw equation cancellation and an independent calorimetry model.

Tested with Python 3.14 and matplotlib 3.10. Run from _media to emit the PNG.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 13})
    fig, (left, right) = plt.subplots(1, 2, figsize=(11, 5.7), dpi=100, facecolor="white")
    fig.subplots_adjust(left=0.035, right=0.975, bottom=0.11, top=0.81, wspace=0.17)
    fig.suptitle("Two kinds of bookkeeping", fontsize=20, y=0.97)
    fig.text(0.5, 0.89, "Constructed examples: the panels use independent chosen numbers",
             ha="center", fontsize=12, color="#475569")
    blue, orange, green = "#2463A6", "#C36B13", "#16805D"
    for ax in (left, right):
        ax.set(xlim=(0, 1), ylim=(0, 1))
        ax.axis("off")
    left.set_title("Scale, reverse, then cancel", fontsize=16, pad=16)
    left.text(0.02, 0.91, r"$R_1:\ 2A\to2B$", fontsize=15)
    left.text(0.71, 0.91, "+36 kJ", color=blue, fontsize=15)
    left.text(0.02, 0.79, r"$R_2:\ C\to B$", fontsize=15)
    left.text(0.71, 0.79, "+25 kJ", color=orange, fontsize=15)
    left.text(0.02, 0.62, r"$\frac{1}{2} R_1:$", color=blue, fontsize=17)
    left.text(0.33, 0.62, r"$A\to B$", color=blue, fontsize=19)
    left.text(0.71, 0.62, "+18 kJ", color=blue, fontsize=15)
    left.text(0.02, 0.47, r"$-R_2:$", color=orange, fontsize=17)
    left.text(0.33, 0.47, r"$B\to C$", color=orange, fontsize=19)
    left.text(0.71, 0.47, "−25 kJ", color=orange, fontsize=15)
    left.plot([0.02, 0.97], [0.40, 0.40], color="#94A3B8", lw=1)
    left.text(0.02, 0.28, "Sum:", fontsize=15)
    left.text(0.33, 0.28, r"$A\to C$", color=green, fontsize=19)
    left.text(0.71, 0.28, "−7 kJ", color=green, fontsize=15)
    left.text(0.5, 0.10, "B is made, then consumed in equal amount.", ha="center", fontsize=11)
    left.text(0.5, 0.015, "Each unit coefficient represents one mole.", ha="center", fontsize=11, color="#475569")
    right.set_title("Warming fixes the reaction sign", fontsize=16, pad=16)
    for y, color, text in [(0.63, orange, "Reaction\n−360 J"),
                           (0.22, blue, "Solution + hardware\n+360 J")]:
        right.add_patch(FancyBboxPatch((0.15, y), 0.70, 0.22,
                                      boxstyle="round,pad=0.015", facecolor="white",
                                      edgecolor=color, linewidth=2))
        right.text(0.5, y + 0.11, text, ha="center", va="center", color=color, fontsize=16)
    right.annotate("", xy=(0.5, 0.47), xytext=(0.5, 0.60),
                   arrowprops={"arrowstyle": "-|>", "lw": 3, "color": green})
    right.text(0.57, 0.52, "360 J transferred", va="center", color=green, fontsize=11)
    right.text(0.5, 0.125, r"$C_{\rm tot}\Delta T=(120\ \mathrm{J/K})(3\ \mathrm{K})$",
               ha="center", fontsize=13)
    right.text(0.5, 0.015, r"$\xi=0.020\ \mathrm{mol}\quad\Rightarrow\quad\Delta H/\xi=-18\ \mathrm{kJ/mol}$",
               ha="center", fontsize=13)
    fig.text(0.5, 0.027, "Right panel: constant pressure, no outward heat loss, other corrections neglected.",
             ha="center", color="#475569", fontsize=11)
    fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()