One-moment 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
Stationary parcel models are classic tools in cloud physics, isolating microphysics from dynamics; see Rogers and Yau (1989).
using Breeze
using CloudMicrophysics
using CairoMakieModel 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.OneMomentCloudMicrophysicsBreeze.Microphysics.BulkMicrophysics{<:Any, <:Breeze.Microphysics.FourCategories{<:CloudMicrophysics.Parameters.CloudLiquid, <:CloudMicrophysics.Parameters.CloudIce, <:CloudMicrophysics.Parameters.Rain, <:CloudMicrophysics.Parameters.Snow, <:CloudMicrophysics.Parameters.CollisionEff, <:CloudMicrophysics.Parameters.Blk1MVelType, <:CloudMicrophysics.Parameters.AirProperties}}ImpenetrableBoundaryCondition ensures rain collects in the parcel rather than exiting
microphysics = OneMomentCloudMicrophysics(precipitation_boundary_condition = ImpenetrableBoundaryCondition())
τ = microphysics.cloud_formation.liquid.τ_relax # Condensation timescale (~10 s)10.0Simulation helper
function run_parcel_simulation(; θ=300, qᵗ=0.020, qᶜˡ=0, qʳ=0, stop_time=65τ, Δt=1)
model = AtmosphereModel(grid; dynamics, thermodynamic_constants=constants, microphysics)
set!(model; θ, qᵗ, qᶜˡ, qʳ)
simulation = Simulation(model; Δt, stop_time, verbose=false)
t, qᵛ, qᶜˡ, qʳ, T = Float64[], Float64[], Float64[], Float64[], Float64[]
function record_time_series(sim)
push!(t, time(sim))
push!(qᵛ, first(sim.model.microphysical_fields.qᵛ))
push!(qᶜˡ, first(sim.model.microphysical_fields.qᶜˡ))
push!(qʳ, first(sim.model.microphysical_fields.qʳ))
push!(T, first(sim.model.temperature))
end
add_callback!(simulation, record_time_series)
run!(simulation)
return (; t, qᵛ, qᶜˡ, qʳ, T)
endrun_parcel_simulation (generic function with 1 method)Five cases illustrating different regimes
We run five simulations with different initial conditions to explore the full spectrum of microphysical behavior:
case1 = run_parcel_simulation(qᵗ=0.025) # Supersaturated
case2 = run_parcel_simulation(qᵗ=0.030) # Higher moisture
case3 = run_parcel_simulation(qᵗ=0.015, qʳ=0.002) # Subsaturated with rain
case4 = run_parcel_simulation(qᵗ=0.03, qᶜˡ=0.02, qʳ=0.005) # Supersaturated with rain
case5 = run_parcel_simulation(θ=280, qᵗ=0.040, qᶜˡ=0.010) # Autoconversion + evaporationVisualization
We plot the change in moisture mass fractions from initial conditions.
fig = Figure(size=(900, 900), fontsize=16)
norm(t) = t ./ τ # Normalize time by condensation timescalenorm (generic function with 1 method)We choose some, bright, colorblind-friendly colors (Wong palette + vibrant choices)
c_vapor = :dodgerblue # Bright blue
c_cloud = :limegreen # Vivid green
c_rain = :orangered # Bright orange-red
c_temp = :magenta # Vibrant magenta
Δ(x) = x .- x[1] # Deviation from initial value
function plot_case!(fig, row, case, description; show_xlabel=false, xlim=65)
Label(fig[row, 1:2], description; fontsize=17, halign=:center, padding=(10, 0, 0, 0))
xlims = (-0.05 * xlim, xlim)
ax_q = Axis(fig[row+1, 1]; ylabel="Δq", limits=(xlims, nothing),
xticklabelsvisible=show_xlabel, xlabel=show_xlabel ? "t / τ" : "")
lines!(ax_q, norm(case.t), Δ(case.qᵛ); color=c_vapor, linewidth=2.5, label="Δqᵛ")
lines!(ax_q, norm(case.t), Δ(case.qᶜˡ); color=c_cloud, linewidth=2.5, label="Δqᶜˡ")
lines!(ax_q, norm(case.t), Δ(case.qʳ); color=c_rain, linewidth=2.5, label="Δqʳ")
ax_T = Axis(fig[row+1, 2]; ylabel="T (K)", limits=(xlims, nothing),
xticklabelsvisible=show_xlabel, xlabel=show_xlabel ? "t / τ" : "")
lines!(ax_T, norm(case.t), case.T; color=c_temp, linewidth=2.5)
return ax_q, ax_T
endPlot all 5 cases
ax1, _ = plot_case!(fig, 1, case1, "(a) Supersaturation: vapor → cloud liquid")
plot_case!(fig, 3, case2, "(b) Strong supersaturation: vapor → cloud liquid → rain")
plot_case!(fig, 5, case3, "(c) Evaporating rain in a subsaturated environment")
plot_case!(fig, 7, case4, "(d) Transient autoconversion of rain, then evaporation", show_xlabel=true)
plot_case!(fig, 9, case5, "(e) Sustained autoconversion from cloud liquid → rain";
show_xlabel=true)(Axis (3 plots), Axis (1 plots))We add a legend outside the figure and adjust row heights for labels vs axes
Legend(fig[0, :], ax1; orientation=:horizontal, framevisible=false)
for i in 1:2:9
rowsize!(fig.layout, i, Relative(0.02))
end
figDiscussion
(a) Condensation: Supersaturated vapor condenses to cloud liquid, releasing latent heat and warming the parcel. Equilibrium is reached in ~5τ.
(b) High moisture: Higher initial moisture creates more cloud liquid. Autoconversion to rain is slow (τ_acnv ≈ 100τ) so rain remains negligible on these short timescales.
(c) Rain evaporation: Subsaturated air with pre-existing rain. Rain evaporates, cooling the parcel via latent heat absorption.
(d) Mixed: Supersaturated with rain. Cloud forms via condensation while rain simultaneously evaporates. The net temperature change depends on the balance between latent heat release (condensation) and absorption (evaporation).
(e) Autoconversion: Running 500× longer reveals rain formation via autoconversion. Cloud liquid slowly converts to rain on timescales of ~100τ. Temperature remains nearly constant since autoconversion involves no phase change (just redistribution of liquid water between cloud and rain categories).
Julia version and environment information
This example was executed with the following version of Julia:
using InteractiveUtils: versioninfo
versioninfo()Julia Version 1.12.2
Commit ca9b6662be4 (2025-11-20 16:25 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_VERSION = 1.12.2
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 Pkg
Pkg.status()Status `/__w/Breeze.jl/Breeze.jl/docs/Project.toml`
[86bc3604] AtmosphericProfilesLibrary v0.1.7
[660aa2fb] Breeze v0.2.1 `.`
[052768ef] CUDA v5.9.5
[13f3f980] CairoMakie v0.15.8
[6a9e3e04] CloudMicrophysics v0.29.1
[e30172f5] Documenter v1.16.1
[daee34ce] DocumenterCitations v1.4.1
[98b081ad] Literate v2.21.0
⌅ [9e8cae18] Oceananigans v0.102.5
[a01a1ee8] RRTMGP v0.21.6
[b77e0a4c] InteractiveUtils v1.11.0
[44cfe95a] Pkg v1.12.0
[9a3f8284] Random v1.11.0
Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated`
This page was generated using Literate.jl.