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)end
Invalid 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:

formulationthermodynamic prognostic
:LiquidIcePotentialTemperature (default)$ρθ$
:StaticEnergy$ρs$

and the moisture variable follows the microphysics:

cloud_formation / schememoisture 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:

KeyInputSpecific 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 mixture heat capacity $cᵖᵐ$ and by the Exner function $Π$, because it is applied to a potential temperature rather than a temperature. A flux and a forcing take the same conversion: $T = Π θ$ at fixed moisture gives $δT = Π δθ$, so with $𝒬 = ρ cᵖᵐ \overline{w'T'}$ the flux that reaches $ρθ$ is $ρ \overline{w'T'} / Π$. 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.

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)))

As with boundary conditions, an unrecognized forcing key is an error, and only one key per variable may be supplied.