Single-column ensemble
Breeze can run an AtmosphereModel in "single column mode" on a grid with topology = (Flat, Flat, Bounded). All horizontal terms vanish, the anelastic pressure solve and vertical-velocity stepping are skipped (w ≡ 0), and vertical transport is carried entirely by the turbulence closure. When the horizontal dimensions are given a size greater than one — with ColumnEnsembleSize — the model becomes a forest of independent columns advanced concurrently in a single kernel launch, with no coupling between them. This is ideal for ensembles: parameter sweeps, closure calibration, and boundary-layer scheme development.
Here we build an ensemble of columns that differ only in their vertical diffusivity — the classic calibration knob — and watch each column mix a warm near-surface layer to a different depth.
using Breezeusing Oceananigansusing Oceananigans.Grids: ColumnEnsembleSizeusing Oceananigans.Unitsusing CairoMakieA forest of columns
ColumnEnsembleSize(Nz, ensemble=(N, 1), Hz) lays out N independent columns in the (Flat) x-direction. The horizontal halos are zero, so the columns never exchange information.
Nz = 48N = 5grid = RectilinearGrid(size = ColumnEnsembleSize(Nz=Nz, ensemble=(N, 1), Hz=3), z = (0, 3kilometers), topology = (Flat, Flat, Bounded))5×1×48 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── Flat x
├── Flat y
└── Bounded z ∈ [0.0, 3000.0] regularly spaced with Δz=62.5Reference state and dynamics
The columns share one anelastic reference state (a dry, neutrally stratified background).
constants = ThermodynamicConstants()reference_state = ReferenceState(grid, constants; base_pressure=101325, potential_temperature=290)dynamics = AnelasticDynamics(reference_state)AnelasticDynamics(p₀=101325.0, θ₀=290.0)
└── pressure_anomaly: not materializedPer-column vertical diffusivity
The closure is an array of closures — one per column. Each column mixes with its own constant vertical diffusivity, spanning two orders of magnitude.
κs = [1, 3, 10, 30, 100] # m² s⁻¹, one per columnclosures = [VerticalScalarDiffusivity(ν=κ, κ=κ) for κ in κs, j in 1:1]model = AtmosphereModel(grid; dynamics, closure=closures)AtmosphereModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 5×1×48 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── dynamics: AnelasticDynamics(p₀=101325.0, θ₀=290.0)
├── formulation: LiquidIcePotentialTemperatureFormulation
├── thermodynamic_constants: ThermodynamicConstants{Float64}
├── timestepper: SSPRungeKutta3
├── advection scheme:
│ ├── momentum: Centered(order=2)
│ ├── ρθ: Centered(order=2)
│ └── ρqᵛ: Centered(order=2)
├── forcing: @NamedTuple{ρu::Returns{Float64}, ρv::Returns{Float64}, ρw::Returns{Float64}, ρθ::Returns{Float64}, ρqᵛ::Returns{Float64}, ρE::Returns{Float64}}
├── tracers: ()
├── coriolis: Nothing
└── microphysics: NothingInitial condition
Every column starts from the same state: a stably stratified background with a warm near-surface layer that the turbulent mixing will erode.
θ★ = 290 # background potential temperature (K)Γ = 0.004 # background lapse rate (K m⁻¹)Δθ = 6 # surface warm anomaly (K)h = 400 # anomaly scale height (m)θᵢ(z) = θ★ + Γ * z + Δθ * exp(-z / h)set!(model, θ = θᵢ)Run the ensemble
All N columns step forward together.
simulation = Simulation(model, Δt=20, stop_time=6hours)run!(simulation)[ Info: Initializing simulation...
[ Info: ... simulation initialization complete (170.867 ms)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (2.131 seconds).
[ Info: Simulation is stopping after running for 3.046 seconds.
[ Info: Simulation time 6 hours equals or exceeds stop time 6 hours.
Visualize
We plot the potential temperature profile of each column. Columns with larger diffusivity mix the warm surface layer deeper, producing a taller, better-mixed layer — exactly the sensitivity a calibration would exploit.
θ = model.temperature # temperature ≈ θ for this dry, near-surface caseset_theme!(fontsize=14, linewidth=2.5)fig = Figure(size=(700, 600))ax = Axis(fig[1, 1]; xlabel="Temperature (K)", ylabel="Altitude (km)", title="A forest of single columns with different vertical mixing")z_ticks_km = 0:0.5:3ax.yticks = ((z_ticks_km .* 1000), string.(z_ticks_km))colors = cgrad(:viridis, N, categorical=true)for i in 1:N lines!(ax, view(θ, i, 1, :); color=colors[i], label="κ = $(κs[i]) m²/s")endaxislegend(ax; position=:rb, framevisible=false)figJulia 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.