Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
64faae6
change thermodynamics to thermodynamic_constants
glwagner Dec 1, 2025
e2dcd69
fix tests
glwagner Dec 1, 2025
09411da
Refactor formulations
glwagner Dec 1, 2025
a540880
convert "moist static" to just "static"
glwagner Dec 1, 2025
6f67813
make accessor functions for energy_density
glwagner Dec 1, 2025
fd02adc
implement PotentialTemperatureThermodynamics
glwagner Dec 1, 2025
0d3e48d
improvements
glwagner Dec 1, 2025
e458b01
generalize
glwagner Dec 1, 2025
f8d0209
Merge branch 'main' into glw/potential-temperature
glwagner Dec 1, 2025
a7da5b3
fix many bugs
glwagner Dec 2, 2025
8c92b6e
reorganize functionality
glwagner Dec 2, 2025
d4c3468
time-step test
glwagner Dec 2, 2025
f8db090
Merge remote-tracking branch 'origin/main' into glw/potential-tempera…
glwagner Dec 4, 2025
b0717cd
update kernel signature
glwagner Dec 4, 2025
410ae49
bugfixes
glwagner Dec 4, 2025
e1c6591
groom cloudy lkelvlin helmholtz
glwagner Dec 4, 2025
48b1251
add moist thermal bubble example
glwagner Dec 4, 2025
abae30d
clean up
glwagner Dec 4, 2025
6f073dd
add example
glwagner Dec 4, 2025
106f549
update test
glwagner Dec 4, 2025
2b6c909
derfin potential_temperature for static energy formulation
glwagner Dec 4, 2025
8fdd72e
updateg
glwagner Dec 4, 2025
d4b9257
Update test/formulations.jl
glwagner Dec 4, 2025
6b0f7f9
change name to LiquidIcePotentialTemperature
glwagner Dec 4, 2025
f8b00ee
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 4, 2025
73fd949
increase moist bubble resolution
glwagner Dec 4, 2025
cb0e46b
another rename
glwagner Dec 5, 2025
d3e21da
change adjustment specific humidity to equilibrium specific humidity
glwagner Dec 5, 2025
c4bc8a4
fix name mangling
glwagner Dec 5, 2025
329f24c
fix messed up names
glwagner Dec 5, 2025
39978ac
fixes
glwagner Dec 5, 2025
1e808e0
fix saturation specific humidity field constructor
glwagner Dec 5, 2025
87bb7f8
Apply suggestion
giordano Dec 5, 2025
5137306
spelling error
glwagner Dec 5, 2025
095aa14
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 5, 2025
5698bea
thermodyanmics -> thermodynamic_constants
glwagner Dec 5, 2025
c1debf2
thermodynamics -> thermodynamic_constants
glwagner Dec 5, 2025
886b80e
Update examples/moist_thermal_bubble.jl
glwagner Dec 5, 2025
13fa7e1
Update examples/cloudy_kelvin_helmholtz.jl
glwagner Dec 5, 2025
269342f
update bubble
glwagner Dec 5, 2025
8834ddd
fix some issues with the microphysics diagnostifcs
glwagner Dec 5, 2025
a453a31
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 5, 2025
8a64f6d
dont worry about docstring
glwagner Dec 5, 2025
7143ad0
update moist bubble
glwagner Dec 5, 2025
7981ec7
Merge branch 'main' into glw/potential-temperature
glwagner Dec 5, 2025
d19c69b
update plotting
glwagner Dec 5, 2025
ee20018
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 5, 2025
b72e966
define StaticEnergyField
glwagner Dec 5, 2025
780ef81
bugfix in StaticEnergyField
glwagner Dec 5, 2025
97670fc
which b ack to GPU
glwagner Dec 5, 2025
4e25aee
clean up energy/pt interface
glwagner Dec 5, 2025
b154727
fix
glwagner Dec 5, 2025
3cb6f73
fix
glwagner Dec 5, 2025
3d82653
fixes
glwagner Dec 5, 2025
98f2c8d
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 5, 2025
f3d49d7
bugfixes
glwagner Dec 5, 2025
da54a8b
change interface function to liquid_ice_potential_temperature
glwagner Dec 5, 2025
926fb12
fix boussinesq model
glwagner Dec 5, 2025
c3268c2
stupid fix for kelvin helmholtz
glwagner Dec 5, 2025
ea44f4e
align comments
navidcy Dec 6, 2025
27cc252
clarify
navidcy Dec 6, 2025
bcb1fc7
some space
navidcy Dec 6, 2025
7d0e33c
fix SaturationSpecificHumidityField + convert doctests to examples
navidcy Dec 6, 2025
68f40c4
fix doctest
navidcy Dec 6, 2025
11f0f9a
it's not potential_temperature anymore
navidcy Dec 6, 2025
655dfd6
Update src/AtmosphereModels/atmosphere_model.jl
navidcy Dec 6, 2025
1b7a4af
bump patch release
navidcy Dec 6, 2025
235feb4
Move up `const` definition
giordano Dec 6, 2025
f20e3fb
fix ssh constructor
glwagner Dec 6, 2025
b2d4b79
fix exports
glwagner Dec 6, 2025
f6f051b
Revert "Move up `const` definition"
giordano Dec 6, 2025
0b24cce
add doctests
glwagner Dec 6, 2025
64f96af
Merge branch 'glw/potential-temperature' of https://github.com/Numeri…
glwagner Dec 6, 2025
4b651b5
Update src/Microphysics/microphysics_diagnostics.jl
glwagner Dec 6, 2025
575be02
add tests
glwagner Dec 6, 2025
2bdcb0b
bugfixes
glwagner Dec 6, 2025
f6591f6
fix version and bug
glwagner Dec 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mkpath(literated_dir)

example_scripts = [
"thermal_bubble.jl",
"moist_thermal_bubble.jl",
"cloudy_kelvin_helmholtz.jl",
# "prescribed_sst.jl", # this is a WIP
]
Expand All @@ -31,6 +32,7 @@ end

example_pages = Any[
"Thermal bubble" => "literated/thermal_bubble.md",
"Moist and dry thermal bubbles" => "literated/moist_thermal_bubble.md",
"Cloudy Kelvin-Helmholtz instability" => "literated/cloudy_kelvin_helmholtz.md",
# "Prescribed SST" => "literated/prescribed_sst.md",
]
Expand Down
2 changes: 1 addition & 1 deletion docs/src/appendix/notation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A few notes about the following table:
a "reference state", which is an adiabatic, hydrostatic solution to the equations of motion. But there is also an
"energy reference temperature" and "reference latent heat", which are thermodynamic constants required to define
the internal energy of moist atmospheric constituents.
* Mapping to AM fields: `ρe` corresponds to `model.energy_density`, `ρqᵗ` to `model.moisture_density`, and `qᵗ` to `model.specific_moisture`.
* Mapping to AM fields: `ρe` corresponds to `energy_density(model)`, `ρqᵗ` to `model.moisture_density`, and `qᵗ` to `model.specific_moisture`.

The following table also uses a few conventions that suffuse the source code and which are internalized by wise developers:

Expand Down
4 changes: 2 additions & 2 deletions docs/src/microphysics/mixed_phase_saturation_adjustment.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ We can compute the saturation specific humidity across the entire range from hom
ice nucleation up to freezing using the equilibrium model above:

```@example mixed_phase
using Breeze.Microphysics: adjustment_saturation_specific_humidity
using Breeze.Microphysics: equilibrium_saturation_specific_humidity
p = 101325.0
qᵗ = 0.012
Expand All @@ -56,7 +56,7 @@ Tᶠ = eq.freezing_temperature
Tʰ = eq.homogeneous_ice_nucleation_temperature
T = range(Tʰ - 10, Tᶠ + 10; length=81) # slightly beyond the mixed-phase range
qᵛ⁺ = [adjustment_saturation_specific_humidity(Tʲ, p, qᵗ, thermo, eq) for Tʲ in T]
qᵛ⁺ = [equilibrium_saturation_specific_humidity(Tʲ, p, qᵗ, thermo, eq) for Tʲ in T]
```

Optionally, we can visualize how `qᵛ⁺` varies with temperature:
Expand Down
12 changes: 6 additions & 6 deletions docs/src/microphysics/warm_phase_saturation_adjustment.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ Next, we compute the saturation specific humidity for moist air with
a carefully chosen moist air mass fraction,

```@example microphysics
using Breeze.Microphysics: adjustment_saturation_specific_humidity, WarmPhaseEquilibrium
using Breeze.Microphysics: equilibrium_saturation_specific_humidity, WarmPhaseEquilibrium

qᵗ = 0.012 # [kg kg⁻¹] total specific humidity
qᵛ⁺ = Breeze.Microphysics.adjustment_saturation_specific_humidity(T, p, qᵗ, thermo, WarmPhaseEquilibrium())
qᵛ⁺ = Breeze.Microphysics.equilibrium_saturation_specific_humidity(T, p, qᵗ, thermo, WarmPhaseEquilibrium())
```

There are two facts of note. First is that we have identified a situation in which ``qᵗ > qᵛ⁺``,
Expand Down Expand Up @@ -164,7 +164,7 @@ To generate a second guess for the secant solver, we start by estimating
the liquid mass fraction using the guess ``T = T₁``,

```@example microphysics
qᵛ⁺₂ = adjustment_saturation_specific_humidity(T₁, p, qᵗ, thermo, WarmPhaseEquilibrium())
qᵛ⁺₂ = equilibrium_saturation_specific_humidity(T₁, p, qᵗ, thermo, WarmPhaseEquilibrium())
qˡ₁ = qᵗ - qᵛ⁺₂
```

Expand All @@ -189,7 +189,7 @@ using CairoMakie
equilibrium = WarmPhaseEquilibrium()
T = 230:0.5:320
r = [saturation_adjustment_residual(Tʲ, 𝒰, thermo, equilibrium) for Tʲ in T]
qᵛ⁺ = [adjustment_saturation_specific_humidity(Tʲ, p, qᵗ, thermo, equilibrium) for Tʲ in T]
qᵛ⁺ = [equilibrium_saturation_specific_humidity(Tʲ, p, qᵗ, thermo, equilibrium) for Tʲ in T]

fig = Figure()
axr = Axis(fig[1, 1], xlabel="Temperature (K)", ylabel="Saturation adjustment residual (K)")
Expand Down Expand Up @@ -228,7 +228,7 @@ for (i, qᵗⁱ) in enumerate(qᵗ)
q = MoistureMassFractions(qᵗⁱ)
𝒰 = StaticEnergyState(e₀, q, z, p)
T[i] = compute_temperature(𝒰, microphysics, thermo)
qᵛ⁺ = Breeze.Microphysics.adjustment_saturation_specific_humidity(T[i], p, qᵗⁱ, thermo, WarmPhaseEquilibrium())
qᵛ⁺ = Breeze.Microphysics.equilibrium_saturation_specific_humidity(T[i], p, qᵗⁱ, thermo, WarmPhaseEquilibrium())
qˡ[i] = max(0, qᵗⁱ - qᵛ⁺)
end

Expand Down Expand Up @@ -295,7 +295,7 @@ for k = 1:grid.Nz
T[k] = compute_temperature(𝒰, microphysics, thermo)

# Saturation specific humidity via adjustment formula using T[k], pᵣ, and qᵗ
qᵛ⁺[k] = Breeze.Microphysics.adjustment_saturation_specific_humidity(T[k], pᵣ, qᵗ, thermo, WarmPhaseEquilibrium())
qᵛ⁺[k] = Breeze.Microphysics.equilibrium_saturation_specific_humidity(T[k], pᵣ, qᵗ, thermo, WarmPhaseEquilibrium())
qˡ[k] = max(0, qᵗ - qᵛ⁺[k])
rh[k] = 100 * min(qᵗ, qᵛ⁺[k]) / qᵛ⁺[k]
end
Expand Down
59 changes: 43 additions & 16 deletions examples/anelastic_bomex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ u_bomex = AtmosphericProfilesLibrary.Bomex_u(FT)
p₀, θ₀ = 101500, 299.1
constants = ThermodynamicConstants()
reference_state = ReferenceState(grid, constants, base_pressure=p₀, potential_temperature=θ₀)
formulation = AnelasticFormulation(reference_state)
formulation = AnelasticFormulation(reference_state, thermodynamics=:LiquidIcePotentialTemperature)
# formulation = AnelasticFormulation(reference_state, thermodynamics=:StaticEnergy)

q₀ = Breeze.Thermodynamics.MoistureMassFractions{eltype(grid)} |> zero
ρ₀ = Breeze.Thermodynamics.density(p₀, θ₀, q₀, constants)
Expand All @@ -41,7 +42,9 @@ Lˡ = constants.liquid.reference_latent_heat
w′T′, w′q′ = 8e-3, 5.2e-5
Q = ρ₀ * cᵖᵈ * w′T′
F = ρ₀ * w′q′

ρe_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(Q))
ρθ_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(ρ₀ * w′T′))
ρqᵗ_bcs = FieldBoundaryConditions(bottom=FluxBoundaryCondition(F))

u★ = 0.28 # m/s
Expand Down Expand Up @@ -69,6 +72,11 @@ end
return @inbounds - p.ρᵣ[i, j, k] * w_dz_E
end

@inline function Fρθ_subsidence(i, j, k, grid, clock, fields, p)
w_dz_T = ℑzᵃᵃᶜ(i, j, k, grid, w_dz_ϕ, p.wˢ, p.θ_avg)
return @inbounds - p.ρᵣ[i, j, k] * w_dz_T
end

@inline function Fρqᵗ_subsidence(i, j, k, grid, clock, fields, p)
w_dz_Qᵗ = ℑzᵃᵃᶜ(i, j, k, grid, w_dz_ϕ, p.wˢ, p.qᵗ_avg)
return @inbounds - p.ρᵣ[i, j, k] * w_dz_Qᵗ
Expand All @@ -78,6 +86,7 @@ end
u_avg_f = Field{Nothing, Nothing, Center}(grid)
v_avg_f = Field{Nothing, Nothing, Center}(grid)
e_avg_f = Field{Nothing, Nothing, Center}(grid)
θ_avg_f = Field{Nothing, Nothing, Center}(grid)
qᵗ_avg_f = Field{Nothing, Nothing, Center}(grid)

wˢ = Field{Nothing, Nothing, Face}(grid)
Expand All @@ -88,17 +97,24 @@ set!(wˢ, z -> w_bomex(z))
ρu_subsidence_forcing = Forcing(Fρu_subsidence, discrete_form=true, parameters=(; u_avg=u_avg_f, wˢ, ρᵣ))
ρv_subsidence_forcing = Forcing(Fρv_subsidence, discrete_form=true, parameters=(; v_avg=v_avg_f, wˢ, ρᵣ))
ρe_subsidence_forcing = Forcing(Fρe_subsidence, discrete_form=true, parameters=(; e_avg=e_avg_f, wˢ, ρᵣ))
ρθ_subsidence_forcing = Forcing(Fρθ_subsidence, discrete_form=true, parameters=(; θ_avg=θ_avg_f, wˢ, ρᵣ))
ρqᵗ_subsidence_forcing = Forcing(Fρqᵗ_subsidence, discrete_form=true, parameters=(; qᵗ_avg=qᵗ_avg_f, wˢ, ρᵣ))

coriolis = FPlane(f=3.76e-5)
ρuᵍ = Field{Nothing, Nothing, Center}(grid)
ρvᵍ = Field{Nothing, Nothing, Center}(grid)
# ρuᵍ = Field{Nothing, Nothing, Center}(grid)
# ρvᵍ = Field{Nothing, Nothing, Center}(grid)
uᵍ = Field{Nothing, Nothing, Center}(grid)
vᵍ = Field{Nothing, Nothing, Center}(grid)
uᵍ_bomex = AtmosphericProfilesLibrary.Bomex_geostrophic_u(FT)
vᵍ_bomex = AtmosphericProfilesLibrary.Bomex_geostrophic_v(FT)
set!(ρuᵍ, z -> uᵍ_bomex(z))
set!(ρvᵍ, z -> vᵍ_bomex(z))
set!(ρuᵍ, ρᵣ * ρuᵍ)
set!(ρvᵍ, ρᵣ * ρvᵍ)
set!(uᵍ, z -> uᵍ_bomex(z))
set!(vᵍ, z -> vᵍ_bomex(z))

ρuᵍ = Field(ρᵣ * uᵍ)
ρvᵍ = Field(ρᵣ * vᵍ)

# set!(ρuᵍ, ρᵣ * ρuᵍ)
# set!(ρvᵍ, ρᵣ * ρvᵍ)

@inline Fρu_geostrophic(i, j, k, grid, clock, fields, p) = @inbounds - p.f * p.ρvᵍ[i, j, k]
@inline Fρv_geostrophic(i, j, k, grid, clock, fields, p) = @inbounds p.f * p.ρuᵍ[i, j, k]
Expand All @@ -117,13 +133,19 @@ set!(drying, ρᵣ * drying)
ρqᵗ_forcing = (ρqᵗ_drying_forcing, ρqᵗ_subsidence_forcing)

Fρe_field = Field{Nothing, Nothing, Center}(grid)
Fρθ_field = Field{Nothing, Nothing, Center}(grid)
dTdt_bomex = AtmosphericProfilesLibrary.Bomex_dTdt(FT)
set!(Fρe_field, z -> dTdt_bomex(1, z))
set!(Fρe_field, ρᵣ * cᵖᵈ * Fρe_field)
set!(Fρθ_field, z -> dTdt_bomex(1, z))
set!(Fρθ_field, ρᵣ * Fρe_field)

ρe_radiation_forcing = Forcing(Fρe_field)
ρe_forcing = (ρe_radiation_forcing, ρe_subsidence_forcing)

ρθ_radiation_forcing = Forcing(Fρθ_field)
ρθ_forcing = (ρθ_radiation_forcing, ρθ_subsidence_forcing)

fig = Figure()
axe = Axis(fig[1, 1], xlabel="z (m)", ylabel="Fρe (K/s)")
axq = Axis(fig[1, 2], xlabel="z (m)", ylabel="Fρqᵗ (1/s)")
Expand All @@ -144,8 +166,10 @@ closure = AnisotropicMinimumDissipation()
model = AtmosphereModel(grid; formulation, coriolis, microphysics, closure,
scalar_advection = Centered(order=2),
momentum_advection = WENO(order=9),
forcing = (ρqᵗ=ρqᵗ_forcing, ρu=ρu_forcing, ρv=ρv_forcing, ρe=ρe_forcing),
boundary_conditions = (ρe=ρe_bcs, ρqᵗ=ρqᵗ_bcs, ρu=ρu_bcs, ρv=ρv_bcs))
forcing = (ρqᵗ=ρqᵗ_forcing, ρu=ρu_forcing, ρv=ρv_forcing, ρθ=ρθ_forcing),
boundary_conditions = (ρθ=ρθ_bcs, ρqᵗ=ρqᵗ_bcs, ρu=ρu_bcs, ρv=ρv_bcs))
# forcing = (ρqᵗ=ρqᵗ_forcing, ρu=ρu_forcing, ρv=ρv_forcing, ρe=ρe_forcing),
# boundary_conditions = (ρe=ρe_bcs, ρqᵗ=ρqᵗ_bcs, ρu=ρu_bcs, ρv=ρv_bcs))

# Values for the initial perturbations can be found in Appendix B
# of Siebesma et al 2003, 3rd paragraph
Expand All @@ -159,32 +183,34 @@ simulation = Simulation(model; Δt=10, stop_time)
conjure_time_step_wizard!(simulation, cfl=0.7)

# Write a callback to compute *_avg_f
ρe = energy_density(model)
e = static_energy(model)
θ = liquid_ice_potential_temperature(model)
u_avg = Field(Average(model.velocities.u, dims=(1, 2)))
v_avg = Field(Average(model.velocities.v, dims=(1, 2)))
e_avg = Field(Average(ρe / ρᵣ, dims=(1, 2)))
e_avg = Field(Average(e, dims=(1, 2)))
θ_avg = Field(Average(θ, dims=(1, 2)))
qᵗ_avg = Field(Average(model.specific_moisture, dims=(1, 2)))

function compute_averages!(sim)
compute!(u_avg)
compute!(v_avg)
compute!(e_avg)
compute!(θ_avg)
compute!(qᵗ_avg)
parent(u_avg_f) .= parent(u_avg)
parent(v_avg_f) .= parent(v_avg)
parent(e_avg_f) .= parent(e_avg)
parent(θ_avg_f) .= parent(θ_avg)
parent(qᵗ_avg_f) .= parent(qᵗ_avg)
return nothing
end

add_callback!(simulation, compute_averages!)

θ = Breeze.AtmosphereModels.PotentialTemperatureField(model)
qˡ = model.microphysical_fields.qˡ
qᵛ = model.microphysical_fields.qᵛ
qᵗ = model.specific_moisture
qᵛ⁺ = Breeze.AtmosphereModels.SaturationSpecificHumidityField(model)
θ_avg = Average(θ, dims=(1, 2)) |> Field
qᵛ⁺ = SaturationSpecificHumidityField(model)
qˡ_avg = Average(qˡ, dims=(1, 2)) |> Field

fig = Figure()
Expand Down Expand Up @@ -212,7 +238,8 @@ ax_ρe = Axis(fig_lower[1, 1], xlabel="Energy density", ylabel="z (m)")
ax_e = Axis(fig_lower[1, 2], xlabel="Specific energy", ylabel="z (m)")
ax_θ = Axis(fig_lower[1, 3], xlabel="Potential temperature (K)", ylabel="z (m)")

e = specific_energy(model)
e = static_energy(model)
ρe = ρᵣ * e
ρe_avg = Average(ρe, dims=(1, 2)) |> Field
e_avg = Average(e, dims=(1, 2)) |> Field

Expand Down Expand Up @@ -247,7 +274,7 @@ function progress(sim)
qᵗ = sim.model.specific_moisture
qᵗmax = maximum(qᵗ)

ρe = energy_density(sim.model)
ρe = static_energy_density(sim.model)
ρemin = minimum(ρe)
ρemax = maximum(ρe)

Expand Down
2 changes: 1 addition & 1 deletion examples/atmos_convection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ set!(model, θ=θᵢ, u=Ξᵢ, v=Ξᵢ)

simulation = Simulation(model, Δt=10, stop_time=20minutes)
conjure_time_step_wizard!(simulation, cfl=0.7)
ρe = energy_density(model)
ρe = static_energy_density(model)
∫ρe = Field(Integral(ρe))

function progress(sim)
Expand Down
73 changes: 34 additions & 39 deletions examples/cloudy_kelvin_helmholtz.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@ using Printf
# Grid resolution is modest but enough to clearly resolve the Kelvin-Helmholtz billows and
# rolled-up moisture filament.

Nx = 384 # horizontal resolution
Nz = 128 # vertical resolution

Lx = 10e3 # domain length
Lz = 3e3 # domain height
Nx, Nz = 384, 128 # resolution
Lx, Lz = 10e3, 3e3 # domain extent

grid = RectilinearGrid(; size = (Nx, Nz), x = (0, Lx), z = (0, Lz),
topology = (Periodic, Flat, Bounded))
Expand All @@ -53,14 +50,14 @@ model = AtmosphereModel(grid; advection=WENO(order=5), microphysics)
# N² = \frac{g}{θ₀} \frac{∂θ}{∂z} ,
# ```
#
# We initialize the potential temperature that gives constant Brunt–Väisälä frequency,
# We initialize with a potential temperature that gives constant Brunt–Väisälä frequency,
# representative of mid-tropospheric stability. The (dry) Brunt–Väisälä frequency is
#
# ```math
# N² = \frac{g}{θ} \frac{∂θ}{∂z}
# ```
#
# and thus, for constant ``N²`` the above implies ``θ = θ₀ \exp{(N² z / g)}``.
# and thus, for constant ``N²``, the above implies that ``θ = θ₀ \exp{(N² z / g)}``.

thermo = ThermodynamicConstants()
g = thermo.gravitational_acceleration
Expand All @@ -81,17 +78,29 @@ N = 0.01 # target dry Brunt–Väisälä frequency (s⁻¹)

# First, we set up the shear layer using a ``\tanh`` profile:

z₀ = 1e3 # center of shear & moist layer (m)
Δzᶸ = 150 # shear layer half-thickness (m)
U_top = 25 # upper-layer wind (m/s)
U_bot = 5 # lower-layer wind (m/s)
uᵇ(z) = U_bot + (U_top - U_bot) * (1 + tanh((z - z₀) / Δzᶸ)) / 2
z₀ = 1e3 # center of shear & moist layer (m)
Δzᵘ = 150 # shear layer half-thickness (m)
U₀ = 5 # base wind speed (m/s)
ΔU = 20 # upper-layer wind (m/s)
uᵇ(z) = U₀ + ΔU * (1 + tanh((z - z₀) / Δzᵘ)) / 2

# For the moisture layer, we use a Gaussian in ``z`` centered at ``z₀``:

q_max = 0.012 # peak specific humidity (kg/kg)
Δz_q = 200 # moist layer half-width (m)
qᵇ(z) = q_max * exp(-(z - z₀)^2 / 2Δz_q^2)
qᵗ₀ = 0.012 # peak specific humidity (kg/kg)
Δzᵗ = 200 # moist layer half-width (m)
qᵇ(z) = qᵗ₀ * exp(-(z - z₀)^2 / 2Δzᵗ^2)

# We initialize the model via Oceananigans `set!`, adding also a bit of random noise.

δθ = 0.01
δu = 1e-3
δq = 0.05 * qᵗ₀

θᵢ(x, z) = θᵇ(z) + δθ * rand()
qᵗᵢ(x, z) = qᵇ(z) + δq * rand()
uᵢ(x, z) = uᵇ(z) + δu * rand()
Comment on lines +99 to +101
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use randn to have perturbations in both directions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

randn is dangerous because it is unbounded. We could use the same function we used for the docs intro, ie rand() - 1/2


set!(model; u=uᵢ, qᵗ=qᵗᵢ, θ=θᵢ)

# ## The Kelvin-Helmholtz instability
#
Expand All @@ -107,12 +116,11 @@ qᵇ(z) = q_max * exp(-(z - z₀)^2 / 2Δz_q^2)
#
# Let's plot the initial state as well as the Richardson number.

z = znodes(grid, Center())

dudz = @. (U_top - U_bot) * sech((z - z₀) / Δzᶸ)^2 / 2Δzᶸ
Ri = N^2 ./ dudz.^2
U = Field(Average(model.velocities.u, dims=(1, 2)))
Ri = N^2 / ∂z(U)^2

using CairoMakie
Qᵗ = Field(Average(model.specific_moisture, dims=1))
θ = Field(Average(liquid_ice_potential_temperature(model), dims=1))

fig = Figure(size=(800, 500))

Expand All @@ -121,11 +129,12 @@ axq = Axis(fig[1, 2], xlabel = "qᵇ (kg/kg)", title="Total liquid")
axθ = Axis(fig[1, 3], xlabel = "θᵇ (K)", title="Potential temperature")
axR = Axis(fig[1, 4], xlabel = "Ri", ylabel="z (m)", title="Richardson number")

lines!(axu, uᵇ.(z), z)
lines!(axq, qᵇ.(z), z)
lines!(axθ, θᵇ.(z), z)
lines!(axR, Ri, z)
lines!(axu, U)
lines!(axq, Qᵗ)
lines!(axθ, θ)
lines!(axR, Ri)
lines!(axR, [1/4, 1/4], [0, Lz], linestyle = :dash, color = :black)

xlims!(axR, 0, 0.8)
axR.xticks = 0:0.25:1

Expand All @@ -137,20 +146,6 @@ end

fig

# ## Define initial conditions
#
# We initialize the model via Oceananigans `set!`, adding also a bit of random noise.

δθ = 0.01
δu = 1e-3
δq = 0.05 * q_max

θᵢ(x, z) = θᵇ(z) + δθ * rand()
qᵗᵢ(x, z) = qᵇ(z) + δq * rand()
uᵢ(x, z) = uᵇ(z) + δu * rand()

set!(model; u=uᵢ, qᵗ=qᵗᵢ, θ=θᵢ)

# ## Set up and run the simulation
#
# We construct a simulation and use the time-step wizard to keep the CFL number under control.
Expand All @@ -176,7 +171,7 @@ add_callback!(simulation, progress, TimeInterval(1minute))
# the potential temperatures and the specific humidities (vapour, liquid, ice).
u, v, w = model.velocities
ξ = ∂z(u) - ∂x(w)
θ = PotentialTemperatureField(model)
θ = liquid_ice_potential_temperature(model)
outputs = merge(model.velocities, model.microphysical_fields, (; ξ, θ))

filename = "wave_clouds.jld2"
Expand Down
Loading