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 CairoMakie

A 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.5

Reference 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 materialized

Per-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: Nothing

Initial 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)fig

Julia 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.