codexpeltier-cooling-current-balance.py
peltier-cooling-current-balance.py
"""Plot an explicitly stipulated constant-property thermoelectric cooler.
Run from _media; emits this script's basename.png to the working directory.
Tested: Python 3.14.4, Matplotlib 3.10.7, NumPy 2.3.5. No extra dependencies.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 11})
alpha, tc, th, resistance, conductance = .040, 280., 300., 2., .40
current = np.linspace(0, 12, 401)
peltier = alpha*tc*current
joule = -.5*resistance*current**2
leak = -conductance*(th-tc)
cooling = peltier+joule+leak
reverse = -peltier+joule+leak
optimum = alpha*tc/resistance
roots = (alpha*tc+np.array([-1, 1])*np.sqrt((alpha*tc)**2-2*resistance*conductance*(th-tc)))/resistance
fig, (a, b) = plt.subplots(1, 2, figsize=(11, 5.2), dpi=120)
fig.patch.set_facecolor('white')
for ax in (a, b):
ax.set_facecolor('white')
ax.axhline(0, color='#555555', lw=1)
ax.grid(alpha=.16)
ax.set(xlim=(0, 12), xlabel='Current magnitude I (A)', ylabel='Heat extraction rate (W)')
a.set_title('Net cooling has a maximum', fontsize=14, weight='bold')
a.plot(current, peltier, color='#177eab', label=r'Peltier: $\alpha T_c I$')
a.plot(current, joule+leak, color='#ba4c21', linestyle='--', label=r'Loss: $-I^2R/2-K\Delta T$')
a.plot(current, cooling, color='#176b3b', lw=2.7, label=r'Net $Q_c$')
a.fill_between(current, 0, cooling, where=cooling>0, color='#88c7a2', alpha=.3)
a.scatter([optimum], [alpha*tc*optimum-.5*resistance*optimum**2+leak], color='#176b3b', zorder=5)
a.annotate('23.36 W at 5.60 A', (optimum, 23.36), (6.3, 44),
arrowprops={'arrowstyle': '->', 'color': '#176b3b'}, fontsize=10)
a.scatter(roots, [0, 0], color='#176b3b', s=20, zorder=5)
a.text(5.5, -130, 'Cooling: 0.767 < I < 10.433 A', ha='center', fontsize=10)
a.set(ylim=(-160, 150))
a.legend(loc='upper left', fontsize=10, framealpha=1)
b.set_title('Reversal at fixed face temperatures', fontsize=14, weight='bold')
b.plot(current, (cooling-reverse)/2, color='#177eab', lw=2,
label=r'Odd: $[Q_c(I)-Q_c(-I)]/2$')
b.plot(current, (cooling+reverse)/2, color='#ba4c21', lw=2,
label=r'Even: $[Q_c(I)+Q_c(-I)]/2$')
b.plot(current, joule, color='#713f9a', linestyle='--',
label=r'Even minus $Q_c(0)$: $-I^2R/2$')
b.set(ylim=(-160, 150))
b.legend(loc='upper left', fontsize=9, framealpha=1)
b.text(5.4, 15, 'Odd = transported heat', color='#177eab', fontsize=10)
b.text(.5, -130, 'Even includes the −8 W leak', color='#ba4c21', fontsize=10)
fig.suptitle('Stipulated model • calculated curves, not measurements', fontsize=15, y=.985)
fig.text(.5, .025, 'α = 0.040 V/K • R = 2.0 Ω • K = 0.40 W/K • Tc = 280 K • Th = 300 K',
ha='center', fontsize=11)
fig.subplots_adjust(left=.07, right=.985, bottom=.16, top=.84, wspace=.23)
fig.savefig(Path(__file__).stem+'.png', facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()