# Requirements:
# - Python 3.9+
# - matplotlib>=3.7.0
# - numpy>=1.24.0, <2.0.0
import numpy as np
import matplotlib.pyplot as plt
k_B = 1.380649e-23 # J/K
h = 6.62607015e-34 # J·s
hbar = h / (2 * np.pi)
m_N2 = 4.65e-26 # Mass of N₂ molecule (kg)
# N₂ molecule parameters
B_N2 = 2.88 # K (rotational constant)
theta_vib_N2 = 3374 # K (vibrational temperature)
def translational_partition_per_molecule(V, T, m, h, k_B):
"""Translational partition function (per molecule)"""
lambda_T = h / np.sqrt(2 * np.pi * m * k_B * T) # Thermal de Broglie wavelength
return V / lambda_T**3
def rotational_partition(T, B):
"""Rotational partition function (high-temperature approximation)"""
return T / B
def vibrational_partition(T, theta_vib):
"""Vibrational partition function"""
return 1 / (1 - np.exp(-theta_vib / T))
def total_partition_per_molecule(V, T, m, B, theta_vib, h, k_B):
"""Total partition function = z_trans × z_rot × z_vib"""
z_trans = translational_partition_per_molecule(V, T, m, h, k_B)
z_rot = rotational_partition(T, B)
z_vib = vibrational_partition(T, theta_vib)
return z_trans * z_rot * z_vib
# Standard state
T_std = 298.15 # K
V_molar = 0.0224 # m³/mol
N_A = 6.022e23
V_per_molecule = V_molar / N_A
# Temperature range
T_range = np.linspace(100, 1000, 200)
# Calculate each contribution
z_trans_list = [translational_partition_per_molecule(V_per_molecule, T, m_N2, h, k_B)
for T in T_range]
z_rot_list = [rotational_partition(T, B_N2) for T in T_range]
z_vib_list = [vibrational_partition(T, theta_vib_N2) for T in T_range]
z_total_list = [total_partition_per_molecule(V_per_molecule, T, m_N2, B_N2,
theta_vib_N2, h, k_B) for T in T_range]
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Temperature dependence of each contribution
ax1 = axes[0, 0]
ax1.semilogy(T_range, z_trans_list, 'b-', linewidth=2, label='Translational')
ax1.semilogy(T_range, z_rot_list, 'g-', linewidth=2, label='Rotational')
ax1.semilogy(T_range, z_vib_list, 'r-', linewidth=2, label='Vibrational')
ax1.set_xlabel('Temperature (K)')
ax1.set_ylabel('Partition function (per molecule)')
ax1.set_title('Partition Function of Each Degree of Freedom')
ax1.legend()
ax1.grid(True, alpha=0.3, which='both')
# Total partition function
ax2 = axes[0, 1]
ax2.semilogy(T_range, z_total_list, 'k-', linewidth=2)
ax2.set_xlabel('Temperature (K)')
ax2.set_ylabel('Total partition function')
ax2.set_title('Total Partition Function (z_trans × z_rot × z_vib)')
ax2.grid(True, alpha=0.3, which='both')
# Contribution ratio
ax3 = axes[1, 0]
log_z_trans = np.log(z_trans_list)
log_z_rot = np.log(z_rot_list)
log_z_vib = np.log(z_vib_list)
log_z_total = np.log(z_total_list)
fraction_trans = log_z_trans / log_z_total
fraction_rot = log_z_rot / log_z_total
fraction_vib = log_z_vib / log_z_total
ax3.plot(T_range, fraction_trans, 'b-', linewidth=2, label='Translational')
ax3.plot(T_range, fraction_rot, 'g-', linewidth=2, label='Rotational')
ax3.plot(T_range, fraction_vib, 'r-', linewidth=2, label='Vibrational')
ax3.set_xlabel('Temperature (K)')
ax3.set_ylabel('Fraction of ln(z)')
ax3.set_title('Contribution Ratio')
ax3.legend()
ax3.grid(True, alpha=0.3)
# Contribution to heat capacity
ax4 = axes[1, 1]
C_trans = 1.5 * np.ones_like(T_range) # (3/2) k_B
C_rot = 1.0 * np.ones_like(T_range) # k_B (high temperature)
C_vib_list = []
for T in T_range:
x = theta_vib_N2 / T
exp_x = np.exp(x)
C_vib = x**2 * exp_x / (exp_x - 1)**2
C_vib_list.append(C_vib)
C_total = C_trans + C_rot + np.array(C_vib_list)
ax4.plot(T_range, C_trans, 'b--', linewidth=2, label='Translational')
ax4.plot(T_range, C_rot, 'g--', linewidth=2, label='Rotational')
ax4.plot(T_range, C_vib_list, 'r--', linewidth=2, label='Vibrational')
ax4.plot(T_range, C_total, 'k-', linewidth=2, label='Total')
ax4.axhline(3.5, color='gray', linestyle=':', linewidth=1.5, label='(7/2)k_B')
ax4.set_xlabel('Temperature (K)')
ax4.set_ylabel('C_V / k_B')
ax4.set_title('Contribution to Heat Capacity (N₂)')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('stat_mech_ideal_gas_partition.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results
print("=== Partition Function of N₂ Ideal Gas (Standard State)===")
print(f"T = {T_std} K, V = {V_molar} m³/mol\n")
z_trans_std = translational_partition_per_molecule(V_per_molecule, T_std, m_N2, h, k_B)
z_rot_std = rotational_partition(T_std, B_N2)
z_vib_std = vibrational_partition(T_std, theta_vib_N2)
z_total_std = z_trans_std * z_rot_std * z_vib_std
print(f"Translation: z_trans = {z_trans_std:.6e}")
print(f"Rotation: z_rot = {z_rot_std:.2f}")
print(f"Vibration: z_vib = {z_vib_std:.6f}")
print(f"Total: z_total = {z_total_std:.6e}\n")
print("Heat capacity (300 K):")
C_trans_val = 1.5
C_rot_val = 1.0
T_300 = 300
x_300 = theta_vib_N2 / T_300
C_vib_val = x_300**2 * np.exp(x_300) / (np.exp(x_300) - 1)**2
C_total_val = C_trans_val + C_rot_val + C_vib_val
print(f" C_trans = {C_trans_val:.2f} k_B")
print(f" C_rot = {C_rot_val:.2f} k_B")
print(f" C_vib = {C_vib_val:.4f} k_B (vibration nearly frozen)")
print(f" C_total ≈ {C_total_val:.2f} k_B")
print(f" Experimental value: C_V ≈ 2.5 k_B (translation + rotation only)")