Per-name Implementation
This page walks through the implementation of a simple microphysics scheme to illustrate the developer interface. We implement a scheme that represents droplet and ice particle nucleation with constant-rate relaxation of specific humidity to saturation.
Defining the Scheme
First, we define a struct to hold the scheme parameters:
using Breeze
using Breeze.AtmosphereModels: AtmosphereModels
struct ExplicitMicrophysics{FT}
vapor_to_liquid :: FT
vapor_to_ice :: FT
endPrognostic Field Names
This scheme is fully prognostic, meaning we carry vapor, liquid, and ice density as prognostic variables:
AtmosphereModels.prognostic_field_names(::ExplicitMicrophysics) = (:ρqᵛ, :ρqˡ, :ρqⁱ)The names of prognostic fields defined by prognostic_field_names are crucial to the user interface, because users can interact with them and set! their initial conditions. Names should be concise, mathematical forms consistent with Breeze conventions (see the notation appendix).
Materializing Fields
When we materialize the microphysics fields, we must include all prognostic fields plus any diagnostic fields needed:
using Oceananigans: CenterField
function AtmosphereModels.materialize_microphysical_fields(::ExplicitMicrophysics, grid, boundary_conditions)
ρqᵛ = CenterField(grid; boundary_conditions=boundary_conditions.ρqᵛ)
ρqˡ = CenterField(grid; boundary_conditions=boundary_conditions.ρqˡ)
ρqⁱ = CenterField(grid; boundary_conditions=boundary_conditions.ρqⁱ)
qᵛ = CenterField(grid)
return (; ρqᵛ, ρqˡ, ρqⁱ, qᵛ)
endNote we include the diagnostic field qᵛ (vapor mass fraction) in addition to the three prognostic fields.
Building the Microphysical State
The microphysical state encapsulates local values for tendency computation:
using Breeze.AtmosphereModels: AbstractMicrophysicalState
struct ExplicitMicrophysicsState{FT} <: AbstractMicrophysicalState{FT}
qᵛ :: FT
qˡ :: FT
qⁱ :: FT
end
function AtmosphereModels.microphysical_state(::ExplicitMicrophysics, ρ, μ::NamedTuple, 𝒰, velocities)
# `velocities` is part of the interface for schemes that need aerosol activation; unused here
qᵛ = μ.ρqᵛ / ρ
qˡ = μ.ρqˡ / ρ
qⁱ = μ.ρqⁱ / ρ
return ExplicitMicrophysicsState(qᵛ, qˡ, qⁱ)
endComputing Tendencies
Tendencies are computed from the microphysical state. Each prognostic variable needs a tendency method:
using Breeze.Thermodynamics: temperature, saturation_specific_humidity,
PlanarLiquidSurface, PlanarIceSurface
# Relaxation toward liquid saturation
@inline function AtmosphereModels.microphysical_tendency(em::ExplicitMicrophysics, ::Val{:ρqˡ}, ρ, ℳ, 𝒰, constants)
T = temperature(𝒰, constants)
q⁺ˡ = saturation_specific_humidity(T, ρ, constants, PlanarLiquidSurface())
τᵛˡ = em.vapor_to_liquid
return ρ * (ℳ.qᵛ - q⁺ˡ) / τᵛˡ
end
# Relaxation toward ice saturation
@inline function AtmosphereModels.microphysical_tendency(em::ExplicitMicrophysics, ::Val{:ρqⁱ}, ρ, ℳ, 𝒰, constants)
T = temperature(𝒰, constants)
q⁺ⁱ = saturation_specific_humidity(T, ρ, constants, PlanarIceSurface())
τᵛⁱ = em.vapor_to_ice
return ρ * (ℳ.qᵛ - q⁺ⁱ) / τᵛⁱ
end
# Vapor closes by conservation
@inline function AtmosphereModels.microphysical_tendency(em::ExplicitMicrophysics, ::Val{:ρqᵛ}, ρ, ℳ, 𝒰, constants)
Sˡ = AtmosphereModels.microphysical_tendency(em, Val(:ρqˡ), ρ, ℳ, 𝒰, constants)
Sⁱ = AtmosphereModels.microphysical_tendency(em, Val(:ρqⁱ), ρ, ℳ, 𝒰, constants)
return -Sˡ - Sⁱ
endUpdating Auxiliary Fields
The update_microphysical_auxiliaries! function writes diagnostic fields:
@inline function AtmosphereModels.update_microphysical_auxiliaries!(μ, i, j, k, grid,
::ExplicitMicrophysics,
ℳ::ExplicitMicrophysicsState,
ρ, 𝒰, constants)
@inbounds μ.qᵛ[i, j, k] = 𝒰.moisture_mass_fractions.vapor
return nothing
endComputing Moisture Fractions
The moisture_fractions function partitions the scheme-dependent specific moisture $qᵛᵉ$ (see Microphysics Interface Overview) into vapor / liquid / ice components:
using Breeze.Thermodynamics: MoistureMassFractions
@inline function AtmosphereModels.moisture_fractions(::ExplicitMicrophysics, ℳ::ExplicitMicrophysicsState, qᵛᵉ)
return MoistureMassFractions(ℳ.qᵛ, ℳ.qˡ, ℳ.qⁱ)
endThermodynamic Adjustment
This is a fully prognostic scheme with no saturation adjustment, so we simply return the state unchanged:
@inline AtmosphereModels.maybe_adjust_thermodynamic_state(𝒰, ::ExplicitMicrophysics, qᵛᵉ, constants) = 𝒰Summary
To implement a new microphysics scheme, we need to:
- Define a struct to hold scheme parameters
- Implement
prognostic_field_namesto list density-weighted prognostic variables - Implement
materialize_microphysical_fieldsto create prognostic and diagnostic fields - Define a state type inheriting from
AbstractMicrophysicalState - Implement
microphysical_stateto build state from prognostic scalars - Implement
microphysical_tendencyfor each prognostic variable - Implement
update_microphysical_auxiliaries!to update diagnostic fields - Implement
moisture_fractionsto partition moisture - Implement
maybe_adjust_thermodynamic_state(trivial for non-equilibrium schemes)
For schemes with sedimentation, you would also implement velocity fields and the microphysical_velocities function. For bundle schemes where many process rates feed multiple prognostic tendencies, see Fused-kernel Microphysics Implementation.