Boundary conditions and forcing
AtmosphereModel accepts boundary_conditions and forcing as NamedTuples whose keys name the variable each applies to:
model = AtmosphereModel(grid; boundary_conditions = (; ρθ = ρθ_bcs), forcing = (; ρθ = ρθ_forcing))Both are validated when the model is built. A key that names nothing the model can apply it to raises an ArgumentError, rather than being accepted and then quietly ignored:
using Breezeusing Oceananigansgrid = RectilinearGrid(size=(8, 8), x=(0, 1e3), z=(0, 1e3), topology=(Periodic, Flat, Bounded))bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(100))try AtmosphereModel(grid; boundary_conditions=(; ρe=bcs))catch err println(err.msg)endInvalid boundary_conditions: (:ρe,) do not name anything that carries boundary conditions!
Boundary conditions may be set on (:ρu, :ρv, :ρw, :ρθ, :ρqᵛ, :ρE, :ρqᵗ).
An energy flux or forcing is supplied under ρE (or E); ρe names turbulent kinetic energy, so it is a key only under a prognostic-TKE closure.Prognostic names depend on the model
Two of Breeze's prognostic variables are named by the choices you make elsewhere in the model. The thermodynamic variable follows the formulation:
formulation | thermodynamic prognostic |
|---|---|
:LiquidIcePotentialTemperature (default) | $ρθ$ |
:StaticEnergy | $ρs$ |
and the moisture variable follows the microphysics:
cloud_formation / scheme | moisture prognostic |
|---|---|
SaturationAdjustment | $ρqᵉ$, equilibrium moisture |
NonEquilibriumCloudFormation, DCMIP2016KesslerMicrophysics, nothing | $ρqᵛ$, vapor |
Keying a surface flux by one of those names ties the setup to one formulation or one microphysics scheme. Changing cloud_formation would then silently change which key you are supposed to use — a detail of how condensation is parameterized, leaking into the specification of an evaporative flux.
Interface keys: ρE and ρqᵗ
To avoid that, an energy or water input is supplied under a key that names the physical input rather than the variable that carries it:
| Key | Input | Specific alias (forcings) | Applied to |
|---|---|---|---|
| $ρE$ | Energy: W m⁻² at a boundary, W m⁻³ in the interior | $E$ | the thermodynamic prognostic |
| $ρqᵗ$ | Water: kg m⁻² s⁻¹ at a boundary, kg m⁻³ s⁻¹ in the interior | $qᵗ$ | the moisture prognostic |
$ρE$ is a total energy density and $ρqᵗ$ a total moisture density (see the notation appendix). Neither is itself prognostic, so both are unambiguous, and a setup written with them runs unchanged under any formulation and any microphysics scheme:
Q = 100 # sensible heat flux, W m⁻²E = 1e-5 # evaporative mass flux, kg m⁻² s⁻¹ρE_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(Q))ρqᵗ_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(E))boundary_conditions = (; ρE=ρE_bcs, ρqᵗ=ρqᵗ_bcs)θ_model = AtmosphereModel(grid; boundary_conditions)s_model = AtmosphereModel(grid; formulation=:StaticEnergy, boundary_conditions)Breeze converts the input as the receiving variable requires. Static energy is itself an energy per unit mass, so an energy flux reaches $ρs$ unchanged; for $ρθ$ it is divided by the local vapor-equivalent heat capacity $\hat{c}ᵖ$ and Exner function $\hat{Π}$, because $\hat{c}ᵖ \hat{Π} θˡⁱ$ is the enthalpy $H$ (see Liquid-ice potential temperature), so that heating at fixed composition changes $θˡⁱ$ by $1 / (\hat{c}ᵖ \hat{Π})$ per joule. A flux and a forcing take the same conversion: at fixed moisture $cᵖᵐ δT = \hat{c}ᵖ \hat{Π} δθ$, so with $𝒬 = ρ cᵖᵐ \overline{w'T'}$ the flux that reaches $ρθ$ is $𝒬 / (\hat{c}ᵖ \hat{Π})$. Water needs no conversion under either scheme: water added to the prognostic moisture is water added to $qᵗ$.
The interface key is not a field, and does not appear in the model:
keys(θ_model.timestepper.Gⁿ)(:ρu, :ρv, :ρw, :ρθ, :ρqᵛ)ρE and ρqᵗ are the recommended keys. The specific names remain available where they are genuinely prognostic — ρs under formulation = :StaticEnergy, ρqᵉ under saturation adjustment — which is useful when you mean a flux of that variable specifically rather than an energy or water input. Supplying both an interface key and the variable it routes onto is an error, since the two would be summed into a single flux on the same field.
Surface fluxes from bulk formulae
A constant or spatially varying flux can be given directly, as above. Fluxes that depend on the evolving surface state are instead supplied as bulk formulae, which Breeze evaluates against the model state each time step:
Cᴰ = 1.2e-3 # drag coefficientCᵀ = 1.2e-3 # transfer coefficient for heat and moistureT₀ = 300 # sea surface temperature, KρE_bcs = FieldBoundaryConditions(bottom=BulkSensibleHeatFlux(coefficient=Cᵀ, surface_temperature=T₀))ρqᵗ_bcs = FieldBoundaryConditions(bottom=BulkVaporFlux(coefficient=Cᵀ, surface_temperature=T₀))ρu_bcs = FieldBoundaryConditions(bottom=Breeze.BulkDrag(coefficient=Cᴰ)) # `BulkDrag` is also an Oceananigans namemodel = AtmosphereModel(grid; boundary_conditions=(; ρE=ρE_bcs, ρqᵗ=ρqᵗ_bcs, ρu=ρu_bcs))BulkSensibleHeatFlux forms its surface difference in whichever thermodynamic variable the model evolves — $Δθ$ for a potential temperature model, $Δs$ for a static energy model — so the same specification is correct for both. See Wall fluxes for placement on any of the six boundaries, the forms the wall state may take, and stability-corrected transfer coefficients.
Surface fluxes from a time series
Archived fluxes — the sensible and latent heat fluxes of a single-column case, say — are a FieldTimeSeries of horizontal fields, one value per column, given to FluxBoundaryCondition. The flux is read at the model time, interpolated linearly between records inside the kernel and held at the last record beyond it (time_indexing = Clamp(), the default). With times a range the record lookup is arithmetic, so the same boundary condition also runs under Reactant, whose clock is traced:
times = 0:600:3600Q = FieldTimeSeries{Center, Center, Nothing}(grid, times)for (n, t) in enumerate(times) set!(Q[n], x -> 100 + 10 * x / 1e3 + 50 * t / 3600) # W m⁻², varying in x and in timeendρE_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(Q))model = AtmosphereModel(grid; boundary_conditions=(; ρE=ρE_bcs))Forcing
Interior sources are supplied under forcing, using the same keys. Each also accepts a specific alias — the name without its leading $ρ$ — which Breeze multiplies by the density at kernel time. A forcing keyed ρθ is a source of $ρθ$; one keyed θ is a source of $θ$:
∂t_θ = 1 / 86400 # radiative cooling rate, K s⁻¹model = AtmosphereModel(grid; forcing=(; θ=Returns(-∂t_θ)))The energy and water interface keys work the same way, with specific aliases $E$ and $qᵗ$:
∂t_E = -2.0 # radiative cooling, W m⁻³model = AtmosphereModel(grid; forcing=(; ρE=Returns(∂t_E)))A prescribed temperature tendency, the form in which large-scale and radiative heating are often archived, goes under E wrapped in TemperatureForcing. It is converted to an energy tendency with the local mixture heat capacity $cᵖᵐ$ at kernel time, so the temperature changes at the prescribed rate in moist and cloudy air as well as dry. Multiplying by the dry heat capacity $cᵖᵈ$ instead heats air with 20 g kg⁻¹ of vapor about 1.7% too little:
∂t_T = 2 / 86400 # large-scale heating, K s⁻¹model = AtmosphereModel(grid; forcing=(; E=TemperatureForcing(Returns(∂t_T))))As with boundary conditions, an unrecognized forcing key is an error, and only one key per variable may be supplied.