Longwave: Williams (2026) Simple Spectral Model
The AnalyticBandLongwave solver advances Schwarzschild's two-stream equations
\[\frac{dF^{\uparrow}}{d\tau} = F^{\uparrow} - \pi B(T), \qquad \frac{dF^{\downarrow}}{d\tau} = \pi B(T) - F^{\downarrow}\]
at each of nwavenumber = 41 evenly spaced wavenumbers between 10 and 2510 cm⁻¹ and integrates the resulting fluxes spectrally.
Absorption spectra
The scheme represents three analytic sources of clear-sky absorption:
using AnalyticBandRadiation
using CairoMakie
lw = AnalyticBandLongwave(Float64)
ν̃ = range(lw.wavenumber_min, lw.wavenumber_max, length = 500)
# Replace zeros with NaN so the log-y plot shows gaps where a band is inactive.
nan_zero(v) = [x == 0 ? NaN : x for x in v]
κ_h2o_line = nan_zero([h2o_line_kappa_ref(ν, lw) for ν in ν̃])
κ_h2o_cont = [h2o_cont_kappa_ref(ν, lw) for ν in ν̃]
κ_CO₂ = nan_zero([co2_kappa_ref(ν, lw) for ν in ν̃])
fig = Figure(size = (760, 440))
ax = Axis(fig[1, 1];
xlabel = "Wavenumber ν̃ [cm⁻¹]",
ylabel = "κ [m² kg⁻¹]",
yscale = log10,
title = "Williams (2026) reference absorption (T = 260 K, p = 500 hPa)")
lines!(ax, ν̃, κ_h2o_line; label = "H₂O line", linewidth = 2)
lines!(ax, ν̃, κ_h2o_cont; label = "H₂O continuum", linewidth = 2)
lines!(ax, ν̃, κ_CO₂; label = "CO₂ 15 μm", linewidth = 2, linestyle = :dash)
axislegend(ax; position = :rt)
All three curves are evaluated at the paper's reference state (T, p, RH) = (260 K, 500 hPa, 100 %). At runtime williams_delta_tau applies pressure broadening (κ ∝ p / p_ref), continuum temperature scaling (exp(σ_cont (T_ref − T)), Mlawer et al. 1997), and the two-stream diffusivity factor D = 1.5 (Armstrong 1968).
2 × CO₂ clear-sky forcing
A standard clear-sky CO₂-doubling benchmark. The column is a lapse-rate atmosphere from 220 K at the top to 295 K at the surface with constant specific humidity q = 5 g kg⁻¹ and surface pressure 1000 hPa.
using AnalyticBandRadiation
using CairoMakie
nlayers = 32
σ_half = collect(range(0.0, 1.0, length = nlayers + 1))
geom = ColumnGrid(σ_half)
surface = SurfaceState{Float64}(sea_surface_temperature = 295.0,
land_surface_temperature = 285.0,
land_fraction = 0.3)
constants = PhysicalConstants{Float64}()
lw = AnalyticBandLongwave(Float64)
# Sweep CO₂
co2s = [50.0, 100.0, 200.0, 280.0, 400.0, 560.0, 800.0, 1120.0]
olrs = Float64[]
for c in co2s
profile = AtmosphereProfile(
temperature = collect(range(220.0, 295.0, length = nlayers)),
humidity = fill(0.005, nlayers),
geopotential = zeros(nlayers),
surface_pressure = 100_000.0,
CO₂ = c,
)
dT = zeros(nlayers)
dg = LongwaveDiagnostics{Float64}()
solve_longwave!(dT, dg, lw, profile, geom, surface, constants)
push!(olrs, dg.outgoing_longwave)
end
fig = Figure(size = (820, 360))
ax1 = Axis(fig[1, 1];
xlabel = "CO₂ [ppmv]",
ylabel = "ℐꜛˡʷ at TOA [W m⁻²]",
xscale = log10,
title = "Clear-sky outgoing longwave vs CO₂")
lines!(ax1, co2s, olrs; color = :black, linestyle = :dash)
scatter!(ax1, co2s, olrs; markersize = 10, color = :black)
ax2 = Axis(fig[1, 2];
xlabel = "CO₂ [ppmv]",
ylabel = "ℐꜛˡʷ(280) − ℐꜛˡʷ(CO₂) [W m⁻²]",
xscale = log10,
title = "Clear-sky CO₂ radiative forcing")
lines!(ax2, co2s, olrs[4] .- olrs; color = :crimson, linewidth = 2)
scatter!(ax2, co2s, olrs[4] .- olrs; markersize = 10, color = :crimson)
vlines!(ax2, 560; color = :gray70, linestyle = :dot)
hlines!(ax2, 0; color = :gray70)
forcing_280_560 = olrs[4] - olrs[6]
Label(fig[2, 1:2], "2× CO₂ forcing (280 → 560 ppmv) = $(round(forcing_280_560, digits = 2)) W m⁻²";
tellwidth = false, fontsize = 12)
The forcing of OLR(280) − OLR(560) is in the physically plausible range for clear-sky 2×CO₂ (2–5 W m⁻², cf. IPCC AR6 WG1 Ch. 7).