codexthermal-equilibrium-entropy-maximum.py
thermal-equilibrium-entropy-maximum.py
"""Plot the total entropy change along a fixed-energy temperature constraint.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Writes the matching PNG into the current working directory.
Entropy is relative to the initial 300 K, 400 K state; values are stipulated.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
c1, c2 = 1.0, 3.0 # whole-body constant-volume heat capacities, kJ/K
initial1, initial2 = 300.0, 400.0 # K
energy = c1 * initial1 + c2 * initial2 # relative internal energy, kJ
temp1 = np.linspace(250.0, 500.0, 1001)
temp2 = (energy - c1 * temp1) / c2
entropy = c1 * np.log(temp1 / initial1) + c2 * np.log(temp2 / initial2)
final = energy / (c1 + c2)
maximum = c1 * np.log(final / initial1) + c2 * np.log(final / initial2)
assert np.all(temp1 > 0) and np.all(temp2 > 0)
assert np.allclose(c1 * temp1 + c2 * temp2, energy)
assert temp1[np.argmax(entropy)] == final
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white",
"axes.spines.top": False, "axes.spines.right": False})
fig, ax = plt.subplots(figsize=(8.2, 5.0), dpi=120)
fig.subplots_adjust(left=0.13, right=0.96, bottom=0.24, top=0.82)
fig.suptitle("Thermal equilibrium maximizes total entropy", fontsize=15)
ax.plot(temp1, entropy, color="#216a99", lw=2.7)
ax.axhline(0, color="#777777", lw=1, linestyle=":")
ax.axvline(final, color="#24765c", lw=1, linestyle="--")
ax.scatter([initial1, final], [0, maximum], c=["#b8531c", "#24765c"], s=35, zorder=4)
ax.annotate("Initial state\nT₁ = 300 K, T₂ = 400 K", (initial1, 0),
xytext=(272, -0.039), fontsize=10,
arrowprops={"arrowstyle": "->", "color": "#b8531c"})
ax.annotate("Maximum: T₁ = T₂ = 375 K\nΔS = 0.02952799 kJ/K", (final, maximum),
xytext=(385, -0.012), fontsize=10, color="#24765c",
arrowprops={"arrowstyle": "->", "color": "#24765c"})
start, end = 321.0, 350.0
def curve(t):
return c1 * np.log(t / initial1) + c2 * np.log(((energy - c1 * t) / c2) / initial2)
ax.annotate("", (end, curve(end)), xytext=(start, curve(start)),
arrowprops={"arrowstyle": "->", "color": "#b8531c", "lw": 2,
"connectionstyle": "arc3,rad=-0.1"})
ax.set(xlim=(250, 500), ylim=(-0.075, 0.045),
xlabel="Body 1 temperature T₁ (K)", ylabel="Total entropy change ΔS (kJ/K)")
ax.set_xticks([250, 300, 350, 375, 400, 450, 500])
ax.grid(alpha=0.18)
fig.text(0.5, 0.125, "Fixed relative energy: T₂ = (1500 − T₁)/3 (temperatures in K)",
ha="center", fontsize=10)
fig.text(0.5, 0.07, "C₁ = 1 kJ/K; C₂ = 3 kJ/K; orange arrow follows the relaxation",
ha="center", fontsize=10)
fig.text(0.5, 0.025, "Only 250–500 K shown; positive-temperature model domain: 0 < T₁ < 1500 K",
ha="center", fontsize=9, color="#555555")
fig.savefig(Path(__file__).with_suffix(".png").name, transparent=False)
plt.close(fig)
print(f"Entropy maximum at T1=T2={final:g} K; delta S={maximum:.12f} kJ/K")
if __name__ == "__main__":
main()