Cloud microphysics in a stationary parcel model
This example demonstrates non-equilibrium cloud microphysics in a stationary parcel framework. We explore how vapor, cloud liquid, and rain evolve under different initial conditions, illustrating the key microphysical processes:
- Condensation: Supersaturated vapor → cloud liquid (timescale τ ≈ 10 s)
- Autoconversion: Cloud liquid → rain (timescale τ ≈ 1000 s)
- Rain evaporation: Subsaturated rain → vapor
We compare one-moment (mass only) and two-moment (mass + number) microphysics schemes. For two-moment, we use the Seifert and Beheng (2006) scheme, which derives process rates from the evolving particle size distribution. Tracking droplet number concentration enables realistic representation of aerosol-cloud interactions.
Stationary parcel models are classic tools in cloud physics, isolating microphysics from dynamics; see Rogers and Yau (1989).
using Breezeusing CloudMicrophysicsusing CairoMakieusing Oceananigans: OceananigansModel setup
A Lagrangian parcel is a closed system - rain doesn't "fall out" because the parcel moves with the hydrometeors. We use an ImpenetrableBoundaryCondition() to ensure moisture is conserved within the parcel.
grid = RectilinearGrid(CPU(); size=(1, 1, 1), x=(0, 1), y=(0, 1), z=(0, 1), topology=(Periodic, Periodic, Bounded))constants = ThermodynamicConstants()reference_state = ReferenceState(grid, constants; surface_pressure=101325, potential_temperature=300)dynamics = AnelasticDynamics(reference_state)BreezeCloudMicrophysicsExt = Base.get_extension(Breeze, :BreezeCloudMicrophysicsExt)OneMomentCloudMicrophysics = BreezeCloudMicrophysicsExt.OneMomentCloudMicrophysicsTwoMomentCloudMicrophysics = BreezeCloudMicrophysicsExt.TwoMomentCloudMicrophysicsBreeze.Microphysics.BulkMicrophysics{<:Any, <:BreezeCloudMicrophysicsExt.TwoMomentCategories{<:CloudMicrophysics.Parameters.SB2006, <:CloudMicrophysics.Parameters.AirProperties, <:CloudMicrophysics.Parameters.StokesRegimeVelType}}Unified simulation helper
A single function runs parcel simulations with either microphysics scheme. The function dynamically tracks number concentrations when using 2M microphysics.
function run_parcel_simulation(; microphysics, θ = 300, stop_time = 2000, Δt = 1, qᵗ = 0.020, qᶜˡ = 0, qʳ = 0, nᶜˡ = 0, nʳ = 0) model = AtmosphereModel(grid; dynamics, thermodynamic_constants=constants, microphysics) is_two_moment = microphysics isa TwoMomentCloudMicrophysics if is_two_moment set!(model; θ, qᵗ, qᶜˡ, nᶜˡ, qʳ, nʳ) else set!(model; θ, qᵗ, qᶜˡ, qʳ) end simulation = Simulation(model; Δt, stop_time, verbose=false) Oceananigans.Diagnostics.erroring_NaNChecker!(simulation) # Time series storage t = Float64[] qᵛ, qᶜˡ, qʳ = Float64[], Float64[], Float64[] nᶜˡ, nʳ = Float64[], Float64[] T = Float64[] function record_time_series(sim) μ = sim.model.microphysical_fields push!(t, time(sim)) push!(qᵛ, first(μ.qᵛ)) push!(qᶜˡ, first(μ.qᶜˡ)) push!(qʳ, first(μ.qʳ)) push!(T, first(sim.model.temperature)) if is_two_moment push!(nᶜˡ, first(μ.nᶜˡ)) push!(nʳ, first(μ.nʳ)) end end add_callback!(simulation, record_time_series) run!(simulation) if is_two_moment return (; t, qᵛ, qᶜˡ, qʳ, nᶜˡ, nʳ, T) else return (; t, qᵛ, qᶜˡ, qʳ, T) endendComparing one-moment and two-moment microphysics
We run four cases to demonstrate the key differences between schemes:
- 1M cases: Show effect of varying condensation timescale
- 2M cases: Show effect of varying initial droplet number
These comparisons illustrate the fundamental difference: one-moment schemes use prescribed timescales, while two-moment schemes derive rates from the evolving size distribution.
Define microphysics schemes with different parameters
import CloudMicrophysics.Parameters as CMPone_moment_cloud_microphysics_categories = BreezeCloudMicrophysicsExt.one_moment_cloud_microphysics_categoriesprecipitation_boundary_condition = ImpenetrableBoundaryCondition()# First, a slow schemeparameters_slow = CMP.Microphysics1MParams( Float64; cloud_liquid_formation = CMP.CloudLiquidFormation(20.0),)categories_slow = one_moment_cloud_microphysics_categories(Float64; parameters = parameters_slow)microphysics_1m_slow = OneMomentCloudMicrophysics(; categories = categories_slow, precipitation_boundary_condition,)BulkMicrophysics:
├── cloud_formation: NonEquilibriumCloudFormation(liquid(τ=20.0), nothing)
├── options: CloudMicrophysics.Parameters.Microphysics1MOptions{CloudMicrophysics.Parameters.CloudLiquidFormation{Float64}, CloudMicrophysics.Parameters.ConstantTimescale{Float64}, CloudMicrophysics.Parameters.CloudIceMelt, CloudMicrophysics.Parameters.Kessler1M{CloudMicrophysics.Parameters.Acnv1M{Float64}}, CloudMicrophysics.Parameters.NoSupersaturation{CloudMicrophysics.Parameters.Acnv1M{Float64}}, CloudMicrophysics.Parameters.RainEvaporation, CloudMicrophysics.Parameters.DepositionAndSublimation, CloudMicrophysics.Parameters.SnowMelt, CloudMicrophysics.Parameters.CloudLiquidRainAccretion{Float64}, CloudMicrophysics.Parameters.CloudLiquidSnowAccretion{Float64}, CloudMicrophysics.Parameters.CloudIceRainAccretion{Float64}, CloudMicrophysics.Parameters.CloudIceSnowAccretion{Float64}, CloudMicrophysics.Parameters.RainSnowAccretion{Float64}}
├── cloud_liquid: CloudLiquid(ρw=1000.0, r_eff=1.4e-5, N_0=5.0e8)
├── cloud_ice: CloudIce(r_eff=2.5e-5, ρᵢ=500.0, N_0=5.0e8, mass=ParticleMass(r0=1.0e-5, m0=2.0944e-12, me=3.0, Δm=0.0, χm=1.0), pdf=ParticlePDFIceRain(n0=2.0e7))
├── rain: CloudMicrophysics.Parameters.Rain
│ ├── autoconversion: CloudMicrophysics.Parameters.Kessler1M{CloudMicrophysics.Parameters.Acnv1M{Float64}}
│ ├── area: ParticleArea(a0=3.14159e-6, ae=2.0, Δa=0.0, χa=1.0)
│ ├── vent: Ventilation(a=1.5, b=0.53)
│ └── pdf: ParticlePDFIceRain(n0=1.6e7)
├── snow: CloudMicrophysics.Parameters.Snow
│ ├── autoconversion: CloudMicrophysics.Parameters.NoSupersaturation{CloudMicrophysics.Parameters.Acnv1M{Float64}}
│ ├── area: ParticleArea(a0=9.42478e-7, ae=2.0, Δa=0.0, χa=1.0)
│ ├── mass: ParticleMass(r0=0.001, m0=1.0e-7, me=2.0, Δm=0.0, χm=1.0)
│ ├── ρᵢ: 100.0
│ └── aspr: SnowAspectRatio(ϕ=0.15, κ=0.333333)
├── freezing_temperature: 273.15
└── velocities: TerminalVelocityParams(...)Then a fast scheme
parameters_fast = CMP.Microphysics1MParams( Float64; cloud_liquid_formation = CMP.CloudLiquidFormation(2.0),)categories_fast = one_moment_cloud_microphysics_categories(Float64; parameters = parameters_fast)microphysics_1m_fast = OneMomentCloudMicrophysics(; categories = categories_fast, precipitation_boundary_condition,)BulkMicrophysics:
├── cloud_formation: NonEquilibriumCloudFormation(liquid(τ=2.0), nothing)
├── options: CloudMicrophysics.Parameters.Microphysics1MOptions{CloudMicrophysics.Parameters.CloudLiquidFormation{Float64}, CloudMicrophysics.Parameters.ConstantTimescale{Float64}, CloudMicrophysics.Parameters.CloudIceMelt, CloudMicrophysics.Parameters.Kessler1M{CloudMicrophysics.Parameters.Acnv1M{Float64}}, CloudMicrophysics.Parameters.NoSupersaturation{CloudMicrophysics.Parameters.Acnv1M{Float64}}, CloudMicrophysics.Parameters.RainEvaporation, CloudMicrophysics.Parameters.DepositionAndSublimation, CloudMicrophysics.Parameters.SnowMelt, CloudMicrophysics.Parameters.CloudLiquidRainAccretion{Float64}, CloudMicrophysics.Parameters.CloudLiquidSnowAccretion{Float64}, CloudMicrophysics.Parameters.CloudIceRainAccretion{Float64}, CloudMicrophysics.Parameters.CloudIceSnowAccretion{Float64}, CloudMicrophysics.Parameters.RainSnowAccretion{Float64}}
├── cloud_liquid: CloudLiquid(ρw=1000.0, r_eff=1.4e-5, N_0=5.0e8)
├── cloud_ice: CloudIce(r_eff=2.5e-5, ρᵢ=500.0, N_0=5.0e8, mass=ParticleMass(r0=1.0e-5, m0=2.0944e-12, me=3.0, Δm=0.0, χm=1.0), pdf=ParticlePDFIceRain(n0=2.0e7))
├── rain: CloudMicrophysics.Parameters.Rain
│ ├── autoconversion: CloudMicrophysics.Parameters.Kessler1M{CloudMicrophysics.Parameters.Acnv1M{Float64}}
│ ├── area: ParticleArea(a0=3.14159e-6, ae=2.0, Δa=0.0, χa=1.0)
│ ├── vent: Ventilation(a=1.5, b=0.53)
│ └── pdf: ParticlePDFIceRain(n0=1.6e7)
├── snow: CloudMicrophysics.Parameters.Snow
│ ├── autoconversion: CloudMicrophysics.Parameters.NoSupersaturation{CloudMicrophysics.Parameters.Acnv1M{Float64}}
│ ├── area: ParticleArea(a0=9.42478e-7, ae=2.0, Δa=0.0, χa=1.0)
│ ├── mass: ParticleMass(r0=0.001, m0=1.0e-7, me=2.0, Δm=0.0, χm=1.0)
│ ├── ρᵢ: 100.0
│ └── aspr: SnowAspectRatio(ϕ=0.15, κ=0.333333)
├── freezing_temperature: 273.15
└── velocities: TerminalVelocityParams(...)And now a default two-moment scheme using Seifert and Beheng (2006)
microphysics_2m = TwoMomentCloudMicrophysics(; precipitation_boundary_condition)TwoMomentCloudMicrophysics:
├── cloud_formation: NonEquilibriumCloudFormation(liquid(τ=10.0), nothing)
├── warm_processes: SB2006
├── air_properties: CloudMicrophysics.Parameters.AirProperties{Float64}
├── cloud_liquid_fall_velocity: StokesRegimeVelType
├── rain_fall_velocity: SB2006VelType
└── precipitation_boundary_condition: NormalFlowBoundaryCondition{Nothing}: NothingRun four comparison cases
All cases start with the same supersaturated conditions (qᵗ = 0.030).
# One-moment cases: varying condensation timescalestop_time = 1000case_1m_slow = run_parcel_simulation(; microphysics = microphysics_1m_slow, qᵗ = 0.030, stop_time)case_1m_fast = run_parcel_simulation(; microphysics = microphysics_1m_fast, qᵗ = 0.030, stop_time)# Two-moment cases: varying initial droplet numberstop_time = 4000case_2m_few = run_parcel_simulation(; microphysics = microphysics_2m, qᵗ = 0.030, nᶜˡ = 100e6, stop_time)case_2m_many = run_parcel_simulation(; microphysics = microphysics_2m, qᵗ = 0.030, nᶜˡ = 300e6, stop_time)Visualization
We compare all four cases side-by-side to highlight the key differences.
# Colorblind-friendly colorsc_cloud = :limegreenc_rain = :orangeredc_cloud_n = :purplec_rain_n = :goldfig = Figure(size=(1000, 700))set_theme!(linewidth=2.5, fontsize=16)# Row 1: One-moment comparisonLabel(fig[1, 1:2], "One-moment microphysics: effect of condensation timescale")ax1_q = Axis(fig[2, 1]; xlabel="t (s)", ylabel="q (kg/kg)", title="Mass mixing ratios")t = case_1m_slow.tlines!(ax1_q, t, case_1m_slow.qᶜˡ; color=c_cloud, label="qᶜˡ (τ = 10 s)")lines!(ax1_q, t, case_1m_slow.qʳ; color=c_rain, label="qʳ (τ = 10 s)")lines!(ax1_q, t, case_1m_fast.qᶜˡ; color=c_cloud, linestyle=:dash, label="qᶜˡ (τ = 2 s)")lines!(ax1_q, t, case_1m_fast.qʳ; color=c_rain, linestyle=:dash, label="qʳ (τ = 2 s)")axislegend(ax1_q; position=:rc, labelsize=11)ax1_T = Axis(fig[2, 2]; xlabel="t (s)", ylabel="T (K)", title="Temperature")lines!(ax1_T, t, case_1m_slow.T; color=:magenta, label="τ = 10 s")lines!(ax1_T, t, case_1m_fast.T; color=:magenta, linestyle=:dash, label="τ = 2 s")axislegend(ax1_T; position=:rb, labelsize=11)xlims!(ax1_T, 0, 200)# Row 2: Two-moment comparisonLabel(fig[3, 1:2], "Two-moment microphysics: effect of initial droplet number")ax2_q = Axis(fig[4, 1]; xlabel="t (s)", ylabel="q (kg/kg)", title="Mass mixing ratios")t = case_2m_few.tlines!(ax2_q, t, case_2m_few.qᶜˡ; color=c_cloud, label="qᶜˡ (nᶜˡ₀ = 100/mg)")lines!(ax2_q, t, case_2m_few.qʳ; color=c_rain, label="qʳ (nᶜˡ₀ = 100/mg)")lines!(ax2_q, t, case_2m_many.qᶜˡ; color=c_cloud, linestyle=:dash, label="qᶜˡ (nᶜˡ₀ = 300/mg)")lines!(ax2_q, t, case_2m_many.qʳ; color=c_rain, linestyle=:dash, label="qʳ (nᶜˡ₀ = 300/mg)")axislegend(ax2_q; position=:rc, labelsize=11)ax2_n = Axis(fig[4, 2]; xlabel="t (s)", ylabel="n (1/kg)", title="Number concentrations")lines!(ax2_n, t, case_2m_few.nᶜˡ; color=c_cloud_n, label="nᶜˡ (nᶜˡ₀ = 100/mg)")lines!(ax2_n, t, case_2m_few.nʳ .* 1e6; color=c_rain_n, label="nʳ × 10⁶ (nᶜˡ₀ = 100/mg)")lines!(ax2_n, t, case_2m_many.nᶜˡ; color=c_cloud_n, linestyle=:dash, label="nᶜˡ (nᶜˡ₀ = 300/mg)")lines!(ax2_n, t, case_2m_many.nʳ .* 1e6; color=c_rain_n, linestyle=:dash, label="nʳ × 10⁶ (nᶜˡ₀ = 300/mg)")axislegend(ax2_n; position=:rt, labelsize=11)rowsize!(fig.layout, 1, Relative(0.05))rowsize!(fig.layout, 3, Relative(0.05))figDiscussion
One-moment microphysics (top row)
The condensation timescale τ controls how quickly supersaturated vapor converts to cloud liquid. With τ = 2 s (dashed), condensation happens 5× faster than with τ = 10 s (solid). However, once equilibrium is reached, both cases produce identical cloud liquid amounts and rain evolution. This illustrates a key limitation: 1M schemes prescribe process rates rather than deriving them from the microphysical state.
Two-moment microphysics (bottom row)
Initial droplet number dramatically affects precipitation timing:
Fewer droplets (100/mg): The same cloud water is distributed among fewer, larger droplets. These large drops collide more efficiently, accelerating autoconversion → rain forms faster.
More droplets (300/mg): Cloud water is spread across many small droplets. Small drops have low collision efficiency → rain is suppressed. This is the "cloud lifetime effect" central to aerosol-cloud interactions.
The number concentration panel reveals additional physics:
- Self-collection reduces nᶜˡ as droplets merge
- Autoconversion creates new rain drops (nʳ increases)
- The ratio q/n determines mean particle size
This sensitivity to droplet number is why two-moment schemes are essential for studying aerosol effects on precipitation. More aerosols → more CCN → more cloud droplets → smaller drops → less rain (the Twomey effect).
Julia version and environment information
This example was executed with the following version of Julia:
using InteractiveUtils: versioninfoversioninfo()Julia Version 1.12.6
Commit 15346901f00 (2026-04-09 19:20 UTC)
Build Info:
Official https://julialang.org release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 8 × AMD EPYC 7R13 Processor
WORD_SIZE: 64
LLVM: libLLVM-18.1.7 (ORCJIT, znver3)
GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 8 virtual cores)
Environment:
JULIA_GPG = 3673DF529D9049477F76B37566E3C7DC03D6E495
JULIA_LOAD_PATH = :@breeze
JULIA_PKG_SERVER_REGISTRY_PREFERENCE = eager
JULIA_VERSION = 1.12.6
JULIA_DEPOT_PATH = /usr/local/share/julia:
JULIA_PATH = /usr/local/julia
JULIA_PROJECT = @breeze
These were the top-level packages installed in the environment:
import PkgPkg.status()Status `/__w/Breeze.jl/Breeze.jl/docs/Project.toml`
⌃ [86bc3604] AtmosphericProfilesLibrary v0.1.8
[660aa2fb] Breeze v0.8.0 `.`
[052768ef] CUDA v6.2.1
[13f3f980] CairoMakie v0.15.13
⌅ [6a9e3e04] CloudMicrophysics v0.37.1
[e30172f5] Documenter v1.17.0
[daee34ce] DocumenterCitations v1.4.1
[b6400b83] DocumenterCodeBlocks v1.1.0
[7da242da] Enzyme v0.13.198
[46192b85] GPUArraysCore v0.2.0
[63c18a36] KernelAbstractions v0.9.42
[98b081ad] Literate v2.21.0
[85f8d34a] NCDatasets v0.14.15
[9e8cae18] Oceananigans v0.110.14
⌅ [a01a1ee8] RRTMGP v0.21.9
[3c362404] Reactant v0.2.278
[b77e0a4c] InteractiveUtils v1.11.0
[44cfe95a] Pkg v1.12.1
[9a3f8284] Random v1.11.0
Info Packages marked with ⌃ and ⌅ have new versions available. Those with ⌃ may be upgradable, but those with ⌅ are restricted by compatibility constraints from upgrading. To see why use `status --outdated`
This page was generated using Literate.jl.