Column solvers
The staged solvers compute interface fluxes from precomputed optical properties. They do not know where the optics came from: the same radiative_fluxes! methods accept optical depths produced by ecCKD tables, analytic bands, or a comparison model. All solvers write caller-owned RadiativeFluxes arrays and follow the package conventions: arrays are ordered top-to-bottom, with pressure increasing downward (index 1 = top of atmosphere); interface flux arrays have Nz + 1 entries; fluxes are in W m⁻².
Data flow
ColumnAtmosphere ── optical_properties! ──▶ LongwaveOptics
ShortwaveOptics
│
radiative_fluxes!
│
▼
RadiativeFluxes ── heating_rates! ──▶ K s⁻¹One radiation update is four staged calls on caller-owned state:
optical_properties!fills the g-point optics from the gas-optics model and the atmosphere;radiative_fluxes!withCloudlessLongwavesolves the longwave stream against its boundary conditions;radiative_fluxes!withCloudlessShortwavedoes the same for the shortwave stream;heating_rates!converts the flux convergence into the temperature tendencyṪ(K s⁻¹).
The complete, executed construction of the atmosphere, work arrays, and boundary conditions is the staged ecCKD column example; examples/ecckd_column.jl is the same workflow against reference model files.
Cloudless longwave
CloudlessLongwave is a plane-parallel clear-sky solver for LongwaveOptics. Optical depth and source arrays may be vectors of length Nz (broadband) or matrices shaped (Ng, Nz); source is the layer Planck source in flux units ($\pi B$, W m⁻²). The atmosphere argument is accepted for interface consistency and is not inspected.
The solver has two paths:
- No-scattering emission (default). Fluxes follow the Schwarzschild recurrence $F' = F\,t + S$ up and down the column. With optional
source_top/source_bottominterface Planck sources, the solver reproduces ecRad's no-scattering longwave: layer transmittance $t = e^{-D\tau}$ with diffusivity $D = 1.66$, and emission linear in the source between the layer's interfaces (with a small-$\tau$ limit below $\tau = 10^{-3}$). With only a layer-meansource, the stored optical depth is used as-is ($t = e^{-\tau}$), so callers on that path supply diffusivity-scaled optical depths. - Longwave scattering (opt-in). Supplying
single_scattering_albedoandscattering_asymmetry(both, and interface sources are then required) activates an ecRad-style two-stream adding path: per-layer reflectance and transmittance from $\gamma_1 = D - \tfrac{D}{2}\omega(1+\mathcal{G})$, $\gamma_2 = \tfrac{D}{2}\omega(1-\mathcal{G})$, a downward sweep accumulating the albedo and source of the stack below each interface, then a downward flux pass.
LongwaveBoundaryConditions carries the upwelling surface flux (scalar or per-g-point), the downwelling TOA flux (default zero), and a diffuse surface albedo (default zero, i.e. a blackbody surface).
Cloudless shortwave
CloudlessShortwave transports the direct solar beam through ShortwaveOptics. When atmosphere.geometry.cos_zenith is present, optical depths are scaled by the slant path $1/\mu_0$ (with $\mu_0$ clamped away from zero); otherwise the historical vertical-path convention applies. toa_shortwave_down in ShortwaveBoundaryConditions is the flux through a horizontal plane at TOA (so $S_0 \mu_0$ for solar constant $S_0$).
- Absorption only (all
rayleigh_optical_depthzero for a g-point): Beer–Lambert direct transmission down, reflection by the direct surface albedo, and upward transmission of the reflected beam through the same slant optical depths. - With scattering: an ecRad-compatible two-stream with $\gamma_1 = 2 - \omega(1.25 + 0.75 \mathcal{G})$, $\gamma_2 = \omega(0.75 - 0.75 \mathcal{G})$, and $\gamma_3 = 0.5 - 0.75\,\mu_0 \mathcal{G}$, separate direct and diffuse streams, and the same adding method as the longwave scattering path. The single-scattering albedo and asymmetry of each layer are formed from the absorption and scattering optical-depth channels, so cloud and aerosol scattering added to those channels (see Cloud and aerosol optics) is transported without solver changes.
Every layer is delta-Eddington scaled (Joseph, Wiscombe and Weinman 1976) before the two-stream coefficients are formed. A fraction $f = \mathcal{G}^2$ of the phase function is treated as an unscattered forward peak and removed,
\[\tau' = (1 - \omega f)\,\tau, \qquad \omega' = \frac{(1 - f)\,\omega}{1 - \omega f}, \qquad \mathcal{G}' = \frac{\mathcal{G} - f}{1 - f}.\]
This is not only an accuracy refinement. A two-stream solution resolves the phase function too coarsely to stay conservative at cloud-like asymmetries, so without the scaling a non-absorbing layer returns more energy than it received — by as much as 13 % of the incident beam at $\mathcal{G} = 0.95$. Rayleigh scattering has $\mathcal{G} = 0$, which makes $f = 0$ and leaves clear-sky results unchanged.
The scaling is applied to the combined gas, cloud, and aerosol optics of a layer, as RRTMGP does. ecRad instead defaults to scaling cloud and aerosol optics before they are added to the gas optics, so that a cloud's forward peak does not also thin the gas absorption; the two agree when scattering dominates the layer and differ slightly when gas absorption does.
The layer solution near the conservative limit
In the conservative limit $\omega \to 1$ the two-stream coefficients satisfy $\gamma_1 \to \gamma_2$, so the eigenvalue $\lambda = \sqrt{(\gamma_1 - \gamma_2)(\gamma_1 + \gamma_2)}$ tends to zero (the solver holds it at $\sqrt{10^{-12}}$) and every reflectance and transmittance of the layer is an $O(\lambda)$ result. Written as ecRad does, with $1 - e^{-2\lambda\tau}$ and $\lambda + \gamma_1 + (\lambda - \gamma_1) e^{-2\lambda\tau}$, each is the difference of $O(1)$ terms, and the relative rounding error $\epsilon / (2\lambda\tau)$ reaches $10^{-10}$ in Float64 and a few percent in Float32, breaking energy conservation of non-absorbing layers. The solver evaluates the same algebra in terms of the small differences
\[m_1 = 1 - e^{-\lambda\tau}, \qquad m_2 = 1 - e^{-2\lambda\tau}, \qquad d = 1 - e^{-\tau/\mu_0},\]
each computed with expm1, and of sums of like-signed terms. With $e = e^{-\lambda\tau}$, $\mathcal{D} = e^{-\tau/\mu_0}$, $\alpha_1 = \gamma_1\gamma_4 + \gamma_2\gamma_3$ and $\alpha_2 = \gamma_1\gamma_3 + \gamma_2\gamma_4$,
\[\begin{aligned} \lambda + \gamma_1 + (\lambda - \gamma_1) e^2 &= \lambda (1 + e^2) + \gamma_1 m_2, \\ (1 - \lambda\mu_0)(\alpha_2 + \lambda\gamma_3) - (1 + \lambda\mu_0)(\alpha_2 - \lambda\gamma_3) e^2 - 2\lambda e (\gamma_3 - \alpha_2\mu_0) \mathcal{D} &= (\alpha_2 - \lambda^2\mu_0\gamma_3) m_2 + \lambda(\gamma_3 - \mu_0\alpha_2)(m_1^2 + 2 e d), \\ 2\lambda e (\gamma_4 + \alpha_1\mu_0) - \mathcal{D}\left[(1 + \lambda\mu_0)(\alpha_1 + \lambda\gamma_4) - (1 - \lambda\mu_0)(\alpha_1 - \lambda\gamma_4) e^2\right] &= \lambda(\gamma_4 + \mu_0\alpha_1)\left(d\,(1 + e^2) - m_1^2\right) - \mathcal{D}(\alpha_1 + \lambda^2\mu_0\gamma_4) m_2, \end{aligned}\]
using $1 + e^2 - 2 e \mathcal{D} = m_1^2 + 2 e d$ and $2 e - \mathcal{D}(1 + e^2) = d\,(1 + e^2) - m_1^2$.
Surface albedos for diffuse and direct radiation are independent and may be broadband scalars or per-g-point vectors.
All-sky overlap solvers
The all-sky solvers operate on two-region optical properties: LongwaveCloudOverlapOptics and ShortwaveCloudOverlapOptics hold clear and cloudy optics with the same (Ng, Nz) shape, plus three layer fields that stay separate from the optical depths:
cloud_fraction— one value per layer; never used to weaken cloudy-region optical depth before transport;overlap_parameter— the ecRad/Hogan–Illingworth $\alpha$ between each pair of adjacent layers (Nz - 1values, default 1);fractional_standard_deviation— the fractional standard deviation of in-cloud condensate, used by the Tripleclouds split (default 1).
CloudOverlapShortwave supports six overlap modes, in increasing fidelity:
:maximum/:average— solve the clear and cloudy columns independently with the configuredclear_solver, then blend each interface flux by an interface cloud fraction (maximum or mean of the adjacent layers).:adding— blend clear/cloudy layer reflectance, transmittance, and direct terms by layer cloud fraction, then run one adding pass.:matrix_maximum/:matrix_alpha— carry separate clear-region and cloudy-region fluxes through the adding pass, redistributing them between layers with a 2×2 overlap matrix built from the pair cloud cover $C = \alpha \max(c_u, c_l) + (1 - \alpha)(c_u + c_l - c_u c_l)$; $\alpha = 1$ (maximum overlap) for:matrix_maximum, the supplied per-interfaceoverlap_parameterfor:matrix_alpha.:tripleclouds_alpha— additionally split the cloudy region into optically thin and thick regions. The thin-region area fraction ramps from 0.5 to 0.9 asfractional_standard_deviationgrows from 1.5 to 3.725, and the two regions scale the clear-to-cloudy optical-depth difference by ecRad's gamma-distribution factors (thin scaling $0.025 + 0.975\,e^{-f(1 + f/2(1 + f/2))}$ for fractional standard deviation $f$, thick scaling chosen to conserve the in-cloud mean). Overlap between the thin/thick sub-regions uses $\alpha^n$ with the solver'sinhomogeneity_overlap_exponent$n$.
CloudOverlapLongwave supports :adding (blend layer reflectance, transmittance, and source terms before a scalar adding pass) and :tripleclouds_alpha (the same three-region gamma split, with paired overlap matrices redistributing downward fluxes and upward sources).
These are deterministic diagnostic solvers — staged all-sky access points, not a bit-for-bit ecRad McICA implementation (the source says as much in the solver docstrings). Tight boundary-flux agreement with ecRad (≈10⁻⁵ W m⁻²) has been demonstrated only for the reference-optics configuration validated on the validation-platform branch, where the solver is fed ecRad's own saved optical properties; see Validation.
How ecCKD gas optics feed the solvers
Two runtime gas-optics models implement optical_properties!:
EcCKDGasOpticsModelholds fixed, already-interpolated(Ng, Ngases)coefficients — the path used by unit tests and teacher–student training.EcCKDTabulatedGasOpticsModelholds reference(Ng, Ngases, Npressures, Ntemperatures)look-up tables. Per layer it brackets pressure on a logarithmic grid, interpolates bilinearly in pressure and temperature (supporting ecCKD's pressure-dependent temperature grids), and accumulates $\tau_g = \sum_j \kappa_{g,j}(p, T)\, u_j$ over the gases with an unrolled, allocation-free sum. The ecCKD concentration conventions are applied at this point: thecompositebackground gas,relative-lineargases as $\kappa\,(u_j - r_j u_\mathrm{composite})$ with reference mole fraction $r_j$, and the H₂O look-up-table dimension interpolated per layer from the actualh2o/compositeamounts. Shortwave Rayleigh optical depth is $k_g\,\Delta p / (g M_\mathrm{air})$ from the per-g-point molar scattering table, and the longwave Planck source is interpolated from the file's source table at layer and interface temperatures.
The evaluation is streaming: the only spectral intermediates are the caller-owned (Ng, Nz) optical-depth and source arrays. Solvers then loop over g-points, carry running fluxes through the column, and accumulate weights[g] * flux directly into the broadband interface arrays — spectral fluxes are never stored with shape (Ng, Nz + 1), and there are no four-dimensional intermediates. Host models can fuse the same per-g-point recurrences into their own column kernels; the model types are Adapt.jl-aware so tables can be moved to GPU device memory.