import matplotlib.pyplot as plt
import numpy as np
# --- Set up plot styling ---
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.size'] = 12
def find_turning_points(R, V, E_v):
"""
Find inner and outer classical turning points where V(R) = E_v
Returns (R_min, R_max) or (None, None) if not found
"""
diff = V - E_v
# Find where sign changes (crossing points)
sign_changes = np.where(np.diff(np.sign(diff)))[0]
if len(sign_changes) >= 2:
# Inner turning point (first crossing)
R_min = R[sign_changes[0]]
# Outer turning point (last crossing)
R_max = R[sign_changes[-1]]
return R_min, R_max
else:
R_min = R[sign_changes[0]]
return R_min, 5
# Set up the figure
fig, ax = plt.subplots(1, 1, figsize=(7, 5))
# Parameters for the potentials
R_e = 1.0 # equilibrium distance (arbitrary units)
E_B = 5.0 # binding energy (eV)
k = 50.0 # force constant
mu = 120.0 # reduced mass (arbitrary units)
hbar = 1.0 # set to 1 for simplicity
# Calculate derived parameters
a = np.sqrt(k / (2 * E_B))
omega = np.sqrt(k / mu)
chi_e = (hbar * omega) / (4 * E_B)
# R values for plotting
R = np.linspace(0.5, 3.0, 500)
# Morse potential
V_morse = E_B * (1 - np.exp(-a * (R - R_e)))**2
# Harmonic potential
V_harmonic = 0.5 * k * (R - R_e)**2
# Plot potentials
ax.plot(R, V_morse, 'b-', linewidth=2.5, label='Morse potential', zorder=2)
ax.plot(R, V_harmonic, 'r--', linewidth=2, label='Harmonic approximation', zorder=2)
# Plot vibrational levels for Morse potential
n_levels_morse = int(1 / (2 * chi_e)) # maximum number of levels
v_values_morse = np.arange(0, n_levels_morse) # min(n_levels_morse, 15)) # limit to 15 for clarity
for v in v_values_morse:
E_v = hbar * omega * (v + 0.5) - chi_e * hbar * omega * (v + 0.5)**2
if E_v < E_B: # only plot bound states
R_min, R_max = find_turning_points(R, V_morse, E_v)
if R_min is not None and R_max is not None:
ax.hlines(E_v, R_min, R_max, colors='blue', linewidth=1.5,
alpha=0.6, zorder=3)
# Add quantum number label for first few levels
if v < 8:
ax.text(R_max + 0.05, E_v, f'$v={v}$', fontsize=10,
va='center', color='blue')
# Plot vibrational levels for harmonic potential
n_levels_harmonic = 18 # show fewer levels since they extend beyond binding energy
for v in range(n_levels_harmonic):
E_v = hbar * omega * (v + 0.5)
if E_v < E_B+1.1:
R_min, R_max = find_turning_points(R, V_harmonic, E_v)
if R_min is not None and R_max is not None:
ax.hlines(E_v, R_min, R_max, colors='red', linewidth=1.5,
alpha=0.5, linestyle='--', zorder=3)
# Mark equilibrium distance
ax.axvline(R_e, color='gray', linestyle=':', linewidth=1, alpha=0.5, zorder=1)
ax.text(R_e, -0.3, r'$R_e$', fontsize=12, ha='center', va='top')
# Mark binding energy
ax.axhline(E_B, color='gray', linestyle=':', linewidth=1, alpha=0.5, zorder=1)
# Mark dissociation limit
ax.axhline(E_B, xmin=0.7, xmax=0.95, color='green', linewidth=2,
alpha=0.7, zorder=1)
ax.text(2.7, E_B + 0.15, 'Dissociation limit', fontsize=11,
ha='right', va='bottom', color='green', style='italic')
# Mark ground state and D_0
E_0_morse = hbar * omega * 0.5 - chi_e * hbar * omega * 0.25
ax.annotate('', xy=(0.6, E_B), xytext=(0.6, E_0_morse),
arrowprops=dict(arrowstyle='<->', color='green', lw=2))
ax.text(0.5, (E_B + E_0_morse)/2, r'$D_0$', fontsize=12,
ha='left', va='center', color='green', fontweight='bold')
ax.hlines(E_0_morse,0,1, color='k', linestyle=':', linewidth=1, alpha=0.5, zorder=1)
ax.hlines(0,1,1.6, color='k', linestyle=':', linewidth=1, alpha=0.5, zorder=1)
ax.text(1.52, 0.1, r'$E_B$', fontsize=12, ha='left', va='bottom')
# Labels and formatting
ax.set_xlabel('Internuclear distance $R$ (arbitrary units)', fontsize=13)
ax.set_ylabel('Potential energy (arb. units)', fontsize=13)
#ax.set_title('Molecular Potential Energy Curves and Vibrational Levels',
# fontsize=14, fontweight='bold', pad=15)
ax.legend(loc='upper right', fontsize=11, framealpha=0.9)
ax.set_xlim(0.5, 3.0)
ax.set_ylim(-0.5, 6.5)
ax.grid(True, alpha=0.2, linestyle='-', linewidth=0.5)
# Get current y-axis tick locations
yticks = ax.get_yticks()
ax.set_yticks(yticks)
# Set new labels (shifted)
ax.set_yticklabels([f'{y - 5:.1f}' for y in yticks])
# Add text annotation explaining anharmonicity
ax.text(2.3, 3.5, 'Anharmonic spacing\n(levels converge)',
fontsize=10, ha='center', va='top',
bbox=dict(boxstyle='round,pad=0.5', facecolor='lightyellow',
edgecolor='orange', alpha=0.8))
# Add arrow pointing to level convergence
ax.annotate('', xy=(2.35, 3.8), xytext=(2.35, 4.5),
arrowprops=dict(arrowstyle='->', color='orange', lw=1.5))
plt.tight_layout()
#plt.savefig('molecular_potentials.png', dpi=300, bbox_inches='tight',
# facecolor='white')
plt.show()