Cloudy thermal bubble
This example sets up, runs, and visualizes simulations of "thermal bubbles" (just circular regions of warm air) rising through a neutral background. We run a dry simulation and two "cloudy" simulations, both with and without precipitation. In the cloudy cases, we simulate a pocket of warm air rising in a saturated, condensate-laden environment.
using Breezeusing Oceananigans: Oceananigansusing Oceananigans.Unitsusing Statisticsusing Printfusing CairoMakieDry thermal bubble
We first set up a dry thermal bubble simulation without moisture processes. This serves as a baseline for comparison with the moist case.
grid = RectilinearGrid(CPU(); size = (128, 128), halo = (5, 5), x = (-10e3, 10e3), z = (0, 10e3), topology = (Bounded, Flat, Bounded))thermodynamic_constants = ThermodynamicConstants()reference_state = ReferenceState(grid, thermodynamic_constants, base_pressure=1e5, potential_temperature=300)dynamics = AnelasticDynamics(reference_state)advection = WENO(order=9)model = AtmosphereModel(grid; dynamics, thermodynamic_constants, advection)AtmosphereModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 128×1×128 RectilinearGrid{Float64, Bounded, Flat, Bounded} on CPU with 5×0×5 halo
├── dynamics: AnelasticDynamics(p₀=100000.0, θ₀=300.0)
├── formulation: LiquidIcePotentialTemperatureFormulation
├── thermodynamic_constants: ThermodynamicConstants{Float64}
├── timestepper: SSPRungeKutta3
├── advection scheme:
│ ├── momentum: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
│ ├── ρθ: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
│ └── ρqᵛ: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
├── forcing: @NamedTuple{ρu::Returns{Float64}, ρv::Returns{Float64}, ρw::Returns{Float64}, ρθ::Returns{Float64}, ρqᵛ::Returns{Float64}, ρE::Returns{Float64}}
├── tracers: ()
├── coriolis: Nothing
└── microphysics: NothingPotential temperature perturbation
We add a localized potential temperature perturbation for the dry bubble. In the dry case, this perturbation directly affects buoyancy without any moisture-related effects.
r₀ = 2e3z₀ = 2e3Δθ = 2 # Kθ₀ = model.dynamics.reference_state.potential_temperatureg = model.thermodynamic_constants.gravitational_accelerationfunction θᵢ(x, z) r = sqrt((x / r₀)^2 + ((z - z₀) / r₀)^2) return θ₀ + Δθ * cos(π * min(1, r) / 2)^2endset!(model, θ=θᵢ)Initial dry bubble visualization
Plot the initial potential temperature to visualize the dry thermal bubble.
θ = liquid_ice_potential_temperature(model)E = total_energy(model)∫E = Integral(E) |> Fieldfig = Figure()ax = Axis(fig[1, 1], aspect=2, xlabel="x (m)", ylabel="z (m)", title="Initial potential temperature θ (K)")hm = heatmap!(ax, θ)Colorbar(fig[1, 2], hm, label = "θ (K)")figSimulation rising
simulation = Simulation(model; Δt=2, stop_time=1000)conjure_time_step_wizard!(simulation, cfl=0.7)θ = liquid_ice_potential_temperature(model)Oceananigans.Diagnostics.erroring_NaNChecker!(simulation)function progress(sim) u, v, w = sim.model.velocities msg = @sprintf("Iter: % 4d, t: % 14s, Δt: % 14s, ⟨E⟩: %.8e J, extrema(θ): (%.2f, %.2f) K, max|w|: %.2f m/s", iteration(sim), prettytime(sim), prettytime(sim.Δt), mean(E), extrema(θ)..., maximum(abs, w)) @info msg return nothingendadd_callback!(simulation, progress, TimeInterval(100))u, v, w = model.velocitiesoutputs = (; θ, w)filename = "dry_thermal_bubble.jld2"writer = JLD2Writer(model, outputs; filename, schedule = TimeInterval(10seconds), overwrite_files = true)simulation.output_writers[:jld2] = writerrun!(simulation)fig = Figure()axθ = Axis(fig[1, 1], aspect=2, xlabel="x (m)", ylabel="z (m)")axw = Axis(fig[2, 1], aspect=2, xlabel="x (m)", ylabel="z (m)")hmθ = heatmap!(axθ, θ)hmw = heatmap!(axw, w)Colorbar(fig[1, 2], hmθ, label = "θ (K) at t = $(prettytime(simulation.model.clock.time))")Colorbar(fig[2, 2], hmw, label = "w (m/s) at t = $(prettytime(simulation.model.clock.time))")figJust running to t=1000 is pretty boring, Let's run the simulation for a longer time, just for fun!
simulation.stop_time = 30minutes run!(simulation)
Visualization
Visualize the potential temperature and the vertical velocity through time and create an animation.
θt = FieldTimeSeries(filename, "θ")wt = FieldTimeSeries(filename, "w")times = θt.timesfig = Figure(size = (800, 800), fontsize = 12)axθ = Axis(fig[1, 1], aspect=2, xlabel="x (m)", ylabel="z (m)")axw = Axis(fig[2, 1], aspect=2, xlabel="x (m)", ylabel="z (m)")n = Observable(length(θt))θn = @lift θt[$n]wn = @lift wt[$n]title = @lift "Dry thermal bubble evolution — t = $(prettytime(times[$n]))"fig[0, :] = Label(fig, title, fontsize = 16, tellwidth = false)θ_range = (minimum(θt), maximum(θt))w_range = maximum(abs, wt)hmθ = heatmap!(axθ, θn, colorrange = θ_range, colormap = :thermal)hmw = heatmap!(axw, wn, colorrange = (-w_range, w_range), colormap = :balance)Colorbar(fig[1, 2], hmθ, label = "θ (K)", vertical = true)Colorbar(fig[2, 2], hmw, label = "w (m/s)", vertical = true)CairoMakie.record(fig, "dry_thermal_bubble.mp4", 1:length(θt); framerate = 12, compression = 23) do nn n[] = nnendMoist thermal bubble with warm-phase saturation adjustment
Now we set up a moist thermal bubble simulation with warm-phase saturation adjustment, following the methodology described by Bryan and Fritsch (2002). This simulation includes moisture processes, where excess water vapor condenses to liquid water, releasing latent heat that enhances the buoyancy of the rising bubble.
For pedagogical purposes, we build a new model with warm-phase saturation adjustment microphysics. (We could have also used this model for the dry simulation):
microphysics = SaturationAdjustment(equilibrium=WarmPhaseEquilibrium())moist_model = AtmosphereModel(grid; dynamics, thermodynamic_constants, advection, microphysics)AtmosphereModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 128×1×128 RectilinearGrid{Float64, Bounded, Flat, Bounded} on CPU with 5×0×5 halo
├── dynamics: AnelasticDynamics(p₀=100000.0, θ₀=300.0)
├── formulation: LiquidIcePotentialTemperatureFormulation
├── thermodynamic_constants: ThermodynamicConstants{Float64}
├── timestepper: SSPRungeKutta3
├── advection scheme:
│ ├── momentum: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
│ ├── ρθ: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
│ └── ρqᵉ: WENO{5, Float64, Oceananigans.Utils.BackendOptimizedDivision}(order=9)
├── forcing: @NamedTuple{ρu::Returns{Float64}, ρv::Returns{Float64}, ρw::Returns{Float64}, ρθ::Returns{Float64}, ρqᵉ::Returns{Float64}, ρE::Returns{Float64}}
├── tracers: ()
├── coriolis: Nothing
└── microphysics: SaturationAdjustmentMoist thermal bubble initial conditions
For the moist bubble, we initialize both temperature and moisture perturbations. The bubble is warm and moist, leading to condensation and latent heat release as it rises and cools. First, we set the potential temperature to match the dry case, then we use the diagnostic saturation specific humidity field to set the moisture.
Set potential temperature to match the dry bubble initially
set!(moist_model, θ=θᵢ, qᵗ=0.025)Compute saturation specific humidity using the diagnostic field, and adjust the buoyancy to match the dry bubble Note, this isn't quite right and needs to be fixed.
using Breeze.Thermodynamics: dry_air_gas_constant, vapor_gas_constantqᵛ⁺ = SaturationSpecificHumidityField(moist_model, :equilibrium)θᵈ = liquid_ice_potential_temperature(moist_model) # note, current state is dryRᵈ = dry_air_gas_constant(thermodynamic_constants)Rᵛ = vapor_gas_constant(thermodynamic_constants)Rᵐ = Rᵈ * (1 - qᵛ⁺) + Rᵛ * qᵛ⁺θᵐ = θᵈ * Rᵈ / Rᵐset!(moist_model, θ=θᵐ)Simulation
moist_simulation = Simulation(moist_model; Δt=2, stop_time=30minutes)conjure_time_step_wizard!(moist_simulation, cfl=0.7)Oceananigans.Diagnostics.erroring_NaNChecker!(moist_simulation)E = total_energy(moist_model)θ = liquid_ice_potential_temperature(moist_model)function progress_moist(sim) ρqᵉ = sim.model.moisture_density u, v, w = sim.model.velocities msg = @sprintf("Iter: % 4d, t: % 14s, Δt: % 14s, ⟨E⟩: %.8e J, extrema(θ): (%.2f, %.2f) K \n", iteration(sim), prettytime(sim), prettytime(sim.Δt), mean(E), extrema(θ)...) msg *= @sprintf(" extrema(ρqᵉ): (%.2e, %.2e), max(qˡ): %.2e, max|w|: %.2f m/s, mean(ρqᵉ): %.2e", extrema(ρqᵉ)..., maximum(qˡ), maximum(abs, w), mean(ρqᵉ)) @info msg return nothingendadd_callback!(moist_simulation, progress_moist, TimeInterval(3minutes))θ = liquid_ice_potential_temperature(moist_model)u, v, w = moist_model.velocitiesqᵛ = specific_humidity(moist_model)qˡ = moist_model.microphysical_fields.qˡqˡ′ = qˡ - Field(Average(qˡ, dims=1))moist_outputs = (; θ, w, qˡ′)moist_filename = "cloudy_thermal_bubble.jld2"moist_writer = JLD2Writer(moist_model, moist_outputs; filename=moist_filename, schedule = TimeInterval(10seconds), overwrite_files = true)moist_simulation.output_writers[:jld2] = moist_writerrun!(moist_simulation)[ Info: Initializing simulation...
┌ Info: Iter: 0, t: 0 seconds, Δt: 2.200 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.64, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 0.00 m/s, mean(ρqᵉ): 1.91e-02
[ Info: ... simulation initialization complete (4.608 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (1.799 seconds).
┌ Info: Iter: 70, t: 3 minutes, Δt: 4.287 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.64, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 1.80 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 113, t: 6 minutes, Δt: 6.277 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.63, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.37 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 149, t: 9 minutes, Δt: 8.354 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.63, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 4.34 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 173, t: 12 minutes, Δt: 11.120 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.63, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 4.52 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 191, t: 15 minutes, Δt: 13.173 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.62, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 4.03 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 209, t: 18 minutes, Δt: 13.782 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.62, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.68 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 227, t: 21 minutes, Δt: 13.735 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.62, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.62 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 245, t: 24 minutes, Δt: 13.246 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.62, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.53 m/s, mean(ρqᵉ): 1.91e-02
┌ Info: Iter: 263, t: 27 minutes, Δt: 13.282 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.63, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.49 m/s, mean(ρqᵉ): 1.91e-02
[ Info: Simulation is stopping after running for 34.701 seconds.
[ Info: Simulation time 30 minutes equals or exceeds stop time 30 minutes.
┌ Info: Iter: 281, t: 30 minutes, Δt: 12.107 seconds, ⟨E⟩: 3.14202261e+05 J, extrema(θ): (295.63, 299.21) K
└ extrema(ρqᵉ): (1.09e-02, 2.89e-02), max(qˡ): 2.08e-02, max|w|: 3.89 m/s, mean(ρqᵉ): 1.91e-02
Visualization of moist thermal bubble
θt = FieldTimeSeries(moist_filename, "θ")wt = FieldTimeSeries(moist_filename, "w")qˡ′t = FieldTimeSeries(moist_filename, "qˡ′")times = θt.timesfig = Figure(size = (1800, 800), fontsize = 12)axθ = Axis(fig[1, 2], aspect=2, xlabel="x (m)", ylabel="z (m)")axw = Axis(fig[1, 3], aspect=2, xlabel="x (m)", ylabel="z (m)")axl = Axis(fig[2, 2:3], aspect=2, xlabel="x (m)", ylabel="z (m)")θ_range = (minimum(θt), maximum(θt))w_range = maximum(abs, wt)qˡ′_range = (minimum(qˡ′t), maximum(qˡ′t))n = Observable(length(θt))θn = @lift θt[$n]wn = @lift wt[$n]qˡ′n = @lift qˡ′t[$n]hmθ = heatmap!(axθ, θn, colorrange = θ_range, colormap = :thermal)hmw = heatmap!(axw, wn, colorrange = (-w_range, w_range), colormap = :balance)hml = heatmap!(axl, qˡ′n, colorrange = qˡ′_range, colormap = :balance)Colorbar(fig[1, 1], hmθ, label = "θ (K)", vertical = true)Colorbar(fig[1, 4], hmw, label = "w (m/s)", vertical = true)Colorbar(fig[2, 4], hml, label = "qˡ (kg/kg)", vertical = true)CairoMakie.record(fig, "cloudy_thermal_bubble.mp4", 1:length(θt); framerate = 24, compression = 23) do nn n[] = nnendMoist thermal bubble with precipitating one-moment microphysics
Next, we extend the moist thermal bubble example to a precipitating case using OneMomentCloudMicrophysics, which adds prognostic rain via autoconversion (cloud droplets coalescing to form rain) and accretion (rain collecting cloud droplets). This follows the CM1 benchmark configuration (iinit=4, isnd=4).
Note: The one-moment microphysics requires the CloudMicrophysics.jl package to be loaded, which activates the BreezeCloudMicrophysicsExt extension.
using CloudMicrophysicsBreezeCloudMicrophysicsExt = Base.get_extension(Breeze, :BreezeCloudMicrophysicsExt)using .BreezeCloudMicrophysicsExt: OneMomentCloudMicrophysicsBuild a new model with one-moment microphysics. We use saturation adjustment for cloud formation, but now rain is a prognostic variable that evolves via microphysical processes. We also use the same initial conditions as the moist case, but with slightly lower total water (qᵗ = 0.020) following the CM1 benchmark.
precip_cloud_formation = SaturationAdjustment(equilibrium=WarmPhaseEquilibrium())precip_microphysics = OneMomentCloudMicrophysics(; cloud_formation=precip_cloud_formation)precip_model = AtmosphereModel(grid; dynamics, thermodynamic_constants, advection, microphysics=precip_microphysics)qᵗ_precip = 0.020 # CM1 qt_mb value for saturated neutrally-stable soundingset!(precip_model, θ=θᵢ, qᵗ=qᵗ_precip)Simulation
We run the simulation for 60 minutes to allow precipitation to develop. The one-moment scheme requires time for cloud liquid to accumulate and autoconversion to produce rain.
precip_simulation = Simulation(precip_model; Δt=2, stop_time=60minutes)conjure_time_step_wizard!(precip_simulation, cfl=0.7)Oceananigans.Diagnostics.erroring_NaNChecker!(precip_simulation)θ_precip = liquid_ice_potential_temperature(precip_model)u_p, v_p, w_precip = precip_model.velocitiesqˡ_precip = precip_model.microphysical_fields.qˡ # Total liquid (cloud + rain)qᶜˡ_precip = precip_model.microphysical_fields.qᶜˡ # Cloud liquid onlyqʳ_precip = precip_model.microphysical_fields.qʳ # Rain mixing ratiofunction progress_precip(sim) qᶜˡmax = maximum(qᶜˡ_precip) qʳmax = maximum(qʳ_precip) wmax = maximum(abs, w_precip) msg = @sprintf("Iter: %4d, t: %14s, Δt: %14s, max|w|: %.2f m/s", iteration(sim), prettytime(sim), prettytime(sim.Δt), wmax) msg *= @sprintf(", max(qᶜˡ): %.2e, max(qʳ): %.2e", qᶜˡmax, qʳmax) @info msg return nothingendadd_callback!(precip_simulation, progress_precip, TimeInterval(5minutes))precip_outputs = (; θ=θ_precip, w=w_precip, qᶜˡ=qᶜˡ_precip, qʳ=qʳ_precip)precip_filename = "precipitating_thermal_bubble.jld2"precip_writer = JLD2Writer(precip_model, precip_outputs; filename=precip_filename, schedule = TimeInterval(30seconds), overwrite_files = true)precip_simulation.output_writers[:jld2] = precip_writerrun!(precip_simulation)[ Info: Initializing simulation...
[ Info: Iter: 0, t: 0 seconds, Δt: 2.200 seconds, max|w|: 0.00 m/s, max(qᶜˡ): 1.77e-02, max(qʳ): 0.00e+00
[ Info: ... simulation initialization complete (11.680 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (1.778 seconds).
[ Info: Iter: 94, t: 5 minutes, Δt: 5.187 seconds, max|w|: 5.85 m/s, max(qᶜˡ): 1.24e-02, max(qʳ): 8.75e-03
[ Info: Iter: 184, t: 10 minutes, Δt: 1.801 seconds, max|w|: 31.04 m/s, max(qᶜˡ): 8.55e-03, max(qʳ): 9.53e-03
[ Info: Iter: 545, t: 15 minutes, Δt: 655.046 ms, max|w|: 82.41 m/s, max(qᶜˡ): 1.16e-02, max(qʳ): 1.15e-02
[ Info: Iter: 1111, t: 20 minutes, Δt: 399.423 ms, max|w|: 135.29 m/s, max(qᶜˡ): 7.06e-03, max(qʳ): 1.18e-02
[ Info: Iter: 1852, t: 25 minutes, Δt: 281.617 ms, max|w|: 164.62 m/s, max(qᶜˡ): 1.26e-02, max(qʳ): 1.47e-02
[ Info: Iter: 2771, t: 30 minutes, Δt: 354.093 ms, max|w|: 137.22 m/s, max(qᶜˡ): 1.05e-02, max(qʳ): 1.16e-02
[ Info: Iter: 3610, t: 35 minutes, Δt: 305.581 ms, max|w|: 140.96 m/s, max(qᶜˡ): 6.58e-03, max(qʳ): 7.46e-03
[ Info: Iter: 4572, t: 40 minutes, Δt: 301.890 ms, max|w|: 149.49 m/s, max(qᶜˡ): 5.91e-03, max(qʳ): 7.57e-03
[ Info: Iter: 5565, t: 45 minutes, Δt: 367.020 ms, max|w|: 146.46 m/s, max(qᶜˡ): 5.67e-03, max(qʳ): 7.24e-03
[ Info: Iter: 6487, t: 50 minutes, Δt: 299.188 ms, max|w|: 181.13 m/s, max(qᶜˡ): 6.18e-03, max(qʳ): 6.46e-03
[ Info: Iter: 7403, t: 55 minutes, Δt: 333.493 ms, max|w|: 163.95 m/s, max(qᶜˡ): 5.32e-03, max(qʳ): 6.01e-03
[ Info: Simulation is stopping after running for 16.292 minutes.
[ Info: Simulation time 1 hour equals or exceeds stop time 1 hour.
[ Info: Iter: 8285, t: 1 hour, Δt: 305.464 ms, max|w|: 177.08 m/s, max(qᶜˡ): 5.19e-03, max(qʳ): 5.05e-03
Visualization of a precipitating thermal bubble
θts = FieldTimeSeries(precip_filename, "θ")wts = FieldTimeSeries(precip_filename, "w")qᶜˡts = FieldTimeSeries(precip_filename, "qᶜˡ")qʳts = FieldTimeSeries(precip_filename, "qʳ")times_precip = θts.timesNt = length(times_precip)θ_range_p = (minimum(θts), maximum(θts))w_range_p = maximum(abs, wts)qᶜˡ_range = (0, max(1e-6, maximum(qᶜˡts)))qʳ_range = (0, max(1e-6, maximum(qʳts)))fig = Figure(size=(1400, 700), fontsize=11)axθ = Axis(fig[1, 2], aspect=2, xlabel="x (m)", ylabel="z (m)", title="θ (K)")axw = Axis(fig[1, 3], aspect=2, xlabel="x (m)", ylabel="z (m)", title="w (m/s)")axqᶜˡ = Axis(fig[2, 2], aspect=2, xlabel="x (m)", ylabel="z (m)", title="Cloud liquid qᶜˡ (kg/kg)")axqʳ = Axis(fig[2, 3], aspect=2, xlabel="x (m)", ylabel="z (m)", title="Rain qʳ (kg/kg)")n = Observable(1)θn = @lift θts[$n]wn = @lift wts[$n]qᶜˡn = @lift qᶜˡts[$n]qʳn = @lift qʳts[$n]hmθ = heatmap!(axθ, θn, colorrange=θ_range_p, colormap=:thermal)hmw = heatmap!(axw, wn, colorrange=(-w_range_p, w_range_p), colormap=:balance)hmqᶜˡ = heatmap!(axqᶜˡ, qᶜˡn, colorrange=qᶜˡ_range, colormap=:dense)hmqʳ = heatmap!(axqʳ, qʳn, colorrange=qʳ_range, colormap=:amp)Colorbar(fig[1, 1], hmθ, label="θ (K)", vertical=true, width=15)Colorbar(fig[1, 4], hmw, label="w (m/s)", vertical=true, width=15)Colorbar(fig[2, 1], hmqᶜˡ, label="qᶜˡ (kg/kg)", vertical=true, width=15)Colorbar(fig[2, 4], hmqʳ, label="qʳ (kg/kg)", vertical=true, width=15)colgap!(fig.layout, 10)rowgap!(fig.layout, 10)CairoMakie.record(fig, "precipitating_thermal_bubble.mp4", 1:Nt; framerate = 12, compression = 23) do nn n[] = nnendJulia version and environment information
This example was executed with the following version of Julia:
using InteractiveUtils: versioninfoversioninfo()Julia Version 1.13.0
Commit d1c37793dd2 (2026-09-09 19:00 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-20.1.8 (ORCJIT, znver3)
GC: Built with stock GC
Threads: 1 default, 1 interactive, 5 GC (on 8 virtual cores)
Environment:
JULIA_NUM_GC_THREADS = 4,1
JULIA_GPG = 64B779A570972FFF7BFC2B54EAD471E1A1F2C10A
JULIA_LOAD_PATH = :@breeze
JULIA_PKG_SERVER_REGISTRY_PREFERENCE = eager
JULIA_VERSION = 1.13.0
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.10
[660aa2fb] Breeze v0.11.3 `.`
⌃ [052768ef] CUDA v6.2.2
[13f3f980] CairoMakie v0.15.15
[6a9e3e04] CloudMicrophysics v0.41.0
[e30172f5] Documenter v1.19.0
[daee34ce] DocumenterCitations v1.5.0
[b6400b83] DocumenterCodeBlocks v1.5.1
[1c52b33b] DocumenterLandingPage v0.2.2
[7da242da] Enzyme v0.13.206
⌅ [46192b85] GPUArraysCore v0.2.0
[63c18a36] KernelAbstractions v0.9.43
[98b081ad] Literate v2.21.0
[85f8d34a] NCDatasets v0.14.15
[9e8cae18] Oceananigans v0.113.4
[a01a1ee8] RRTMGP v1.0.1
[3c362404] Reactant v0.2.289
[276daf66] SpecialFunctions v2.9.0
[b77e0a4c] InteractiveUtils v1.11.0
[44cfe95a] Pkg v1.13.0
[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.