2.4 Adiabatic Processes

Adiabatic processes involve no heat exchange with the environment. As air parcels rise and expand (or sink and compress), their temperature changes predictably.

Dry Adiabatic Lapse Rate

$$\Gamma_d = -\frac{dT}{dz} = \frac{g}{c_p} = 9.8 \text{ K/km}$$

Rate of temperature decrease for unsaturated rising air

Derived from the first law of thermodynamics with δQ = 0 and hydrostatic balance. Approximately 10°C per 1000m of ascent.

Moist Adiabatic Lapse Rate

$$\Gamma_m = \Gamma_d \frac{1 + \frac{L_v r_s}{R_d T}}{1 + \frac{L_v^2 r_s}{c_p R_v T^2}}$$

Varies from ~4-9 K/km depending on temperature and moisture

Warm, Moist Air

Γm ≈ 4-5 K/km

More latent heat release

Cold, Dry Air

Γm ≈ 8-9 K/km

Approaches dry adiabatic

Potential Temperature

$$\theta = T \left(\frac{p_0}{p}\right)^{R_d/c_p} = T \left(\frac{p_0}{p}\right)^{0.286}$$

Temperature a parcel would have if brought adiabatically to 1000 hPa

Key Properties

  • • Conserved during dry adiabatic processes
  • • Constant on isentropic surfaces
  • • ∂θ/∂z > 0 indicates stable stratification
  • • Used to compare air masses at different altitudes

Python: Adiabatic Processes

#!/usr/bin/env python3
"""
adiabatic_processes.py - Adiabatic lapse rates and potential temperature

Run: python3 adiabatic_processes.py
Requires: pip install numpy matplotlib
"""
import numpy as np
import matplotlib.pyplot as plt

# Constants
g = 9.81          # m/s²
R_d = 287.0       # J/(kg·K)
R_v = 461.5       # J/(kg·K)
c_p = 1005.0      # J/(kg·K)
c_v = 718.0       # J/(kg·K)
L_v = 2.5e6       # J/kg
p0 = 100000       # Pa (reference pressure for θ)
epsilon = R_d / R_v

# Dry adiabatic lapse rate
Gamma_d = g / c_p * 1000  # K/km

def saturation_mixing_ratio(T, p):
    """Saturation mixing ratio (kg/kg)"""
    e_s = 6.11 * np.exp(17.27 * (T - 273.15) / (T - 35.85)) * 100  # Pa
    return epsilon * e_s / (p - e_s)

def moist_adiabatic_lapse_rate(T, p):
    """Moist adiabatic lapse rate (K/km)"""
    r_s = saturation_mixing_ratio(T, p)
    numerator = 1 + L_v * r_s / (R_d * T)
    denominator = 1 + L_v**2 * r_s / (c_p * R_v * T**2)
    return Gamma_d * numerator / denominator

def potential_temperature(T, p):
    """Potential temperature (K)"""
    return T * (p0 / p) ** (R_d / c_p)

# Altitude and pressure profile
z = np.linspace(0, 15000, 100)  # m
p = p0 * np.exp(-z / 8500)       # hydrostatic approximation

# Parcel ascent: dry then moist
T_sfc = 300  # K
T_dry = np.zeros_like(z)
T_moist = np.zeros_like(z)
theta = np.zeros_like(z)

T_dry[0] = T_sfc
T_moist[0] = T_sfc
theta[0] = potential_temperature(T_sfc, p[0])

# Find LCL (assume initial RH = 80%)
RH_initial = 0.80
r_initial = RH_initial * saturation_mixing_ratio(T_sfc, p[0])
# Rough LCL calculation
lcl_idx = 20  # approximately

for i in range(1, len(z)):
    dz = z[i] - z[i-1]

    # Dry adiabatic
    T_dry[i] = T_dry[i-1] - Gamma_d * dz / 1000
    theta[i] = potential_temperature(T_dry[i], p[i])

    # Moist adiabatic (after LCL)
    if i < lcl_idx:
        T_moist[i] = T_moist[i-1] - Gamma_d * dz / 1000
    else:
        Gamma_m = moist_adiabatic_lapse_rate(T_moist[i-1], p[i-1])
        T_moist[i] = T_moist[i-1] - Gamma_m * dz / 1000

# Plot
fig, axes = plt.subplots(1, 3, figsize=(15, 8))

# Temperature profiles
ax1 = axes[0]
ax1.plot(T_dry - 273.15, z/1000, 'r-', linewidth=2, label='Dry Adiabatic')
ax1.plot(T_moist - 273.15, z/1000, 'b-', linewidth=2, label='Moist Adiabatic')
ax1.axhline(y=z[lcl_idx]/1000, color='green', linestyle='--', label=f'LCL ≈ {z[lcl_idx]/1000:.1f} km')
ax1.set_xlabel('Temperature (°C)', fontsize=12)
ax1.set_ylabel('Altitude (km)', fontsize=12)
ax1.set_title('Adiabatic Temperature Profiles', fontsize=14)
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_xlim(-80, 30)

# Potential temperature
ax2 = axes[1]
ax2.plot(theta, z/1000, 'purple', linewidth=2)
ax2.axhline(y=z[lcl_idx]/1000, color='green', linestyle='--')
ax2.set_xlabel('Potential Temperature θ (K)', fontsize=12)
ax2.set_ylabel('Altitude (km)', fontsize=12)
ax2.set_title('Potential Temperature Profile', fontsize=14)
ax2.grid(True, alpha=0.3)
# θ is constant for dry adiabatic
ax2.annotate('θ = const (dry adiabatic)', xy=(theta[10], z[10]/1000),
             xytext=(320, 5), fontsize=10, color='purple',
             arrowprops=dict(arrowstyle='->', color='purple'))

# Moist adiabatic lapse rate vs temperature
ax3 = axes[2]
T_range = np.linspace(250, 310, 100)
Gamma_m_values = [moist_adiabatic_lapse_rate(T, 85000) for T in T_range]
ax3.plot(T_range - 273.15, Gamma_m_values, 'b-', linewidth=2, label='Γ_m at 850 hPa')
ax3.axhline(y=Gamma_d, color='r', linestyle='--', linewidth=2, label=f'Γ_d = {Gamma_d:.1f} K/km')
ax3.set_xlabel('Temperature (°C)', fontsize=12)
ax3.set_ylabel('Lapse Rate (K/km)', fontsize=12)
ax3.set_title('Moist Adiabatic Lapse Rate', fontsize=14)
ax3.legend()
ax3.grid(True, alpha=0.3)
ax3.set_ylim(3, 11)

plt.tight_layout()
plt.savefig('adiabatic_processes.png', dpi=150, bbox_inches='tight')
plt.show()

print("Adiabatic Lapse Rates:")
print(f"  Dry adiabatic: Γ_d = {Gamma_d:.2f} K/km")
print(f"  Moist adiabatic at 0°C: {moist_adiabatic_lapse_rate(273.15, 85000):.2f} K/km")
print(f"  Moist adiabatic at 20°C: {moist_adiabatic_lapse_rate(293.15, 85000):.2f} K/km")
print(f"\nPotential Temperature:")
print(f"  θ(300 K, 1000 hPa) = {potential_temperature(300, 100000):.1f} K")
print(f"  θ(280 K, 700 hPa) = {potential_temperature(280, 70000):.1f} K")