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 Breezeusing Breeze.AtmosphereModels: AtmosphereModelsstruct ExplicitMicrophysics{FT}    vapor_to_liquid :: FT    vapor_to_ice :: FTend

Prognostic 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ⁱ)
Field naming conventions

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: CenterFieldfunction 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ᵛ)end

Note 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: AbstractMicrophysicalStatestruct ExplicitMicrophysicsState{FT} <: AbstractMicrophysicalState{FT}    qᵛ :: FT:: FT    qⁱ :: FTendfunction 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ⁱ / ρ    return ExplicitMicrophysicsState(qᵛ, qˡ, qⁱ)end

Computing 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)= AtmosphereModels.microphysical_tendency(em, Val(:ρqˡ), ρ, ℳ, 𝒰, constants)    Sⁱ = AtmosphereModels.microphysical_tendency(em, Val(:ρqⁱ), ρ, ℳ, 𝒰, constants)    return -- Sⁱend

Updating 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 nothingend

Computing 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ⁱ)end

Thermodynamic 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:

  1. Define a struct to hold scheme parameters
  2. Implement prognostic_field_names to list density-weighted prognostic variables
  3. Implement materialize_microphysical_fields to create prognostic and diagnostic fields
  4. Define a state type inheriting from AbstractMicrophysicalState
  5. Implement microphysical_state to build state from prognostic scalars
  6. Implement microphysical_tendency for each prognostic variable
  7. Implement update_microphysical_auxiliaries! to update diagnostic fields
  8. Implement moisture_fractions to partition moisture
  9. 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.