Skip to content

Commit d2f4cc0

Browse files
committed
Add diffusion to microphysics tracers
1 parent 1164ae5 commit d2f4cc0

File tree

2 files changed

+56
-38
lines changed

2 files changed

+56
-38
lines changed

src/prognostic_equations/edmfx_sgs_flux.jl

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#####
2-
##### Tendencies applied to the grid-mean atmospheric state due to subgrid-scale (SGS)
2+
##### Tendencies applied to the grid-mean atmospheric state due to subgrid-scale (SGS)
33
##### fluxes computed by the EDMFX scheme
44
#####
55

@@ -12,7 +12,7 @@ divergence of subgrid-scale (SGS) mass fluxes from EDMFX updrafts and the enviro
1212
This involves terms of the form `- ∂(ρₖ aₖ w′ₖ ϕ′ₖ)/∂z`, where `k` denotes
1313
an SGS component (updraft `j` or environment `0`), `aₖ` is the area fraction,
1414
`w′ₖ` is the vertical velocity deviation from the grid mean, and `ϕ′ₖ` is the
15-
deviation of a conserved variable `ϕ` (such as total enthalpy or specific humidity)
15+
deviation of a conserved variable `ϕ` (such as total enthalpy or specific humidity)
1616
from its grid-mean value. These terms represent the redistribution of energy and tracers
1717
by the resolved SGS circulations relative to the grid mean flow.
1818
@@ -51,7 +51,7 @@ function edmfx_sgs_mass_flux_tendency!(
5151

5252
if p.atmos.edmfx_model.sgs_mass_flux isa Val{true}
5353
# Enthalpy fluxes. First sum up the draft fluxes
54-
# TODO: Isolate assembly of flux term pattern to a function and
54+
# TODO: Isolate assembly of flux term pattern to a function and
5555
# reuse (both in prognostic and diagnostic EDMFX)
5656
# [best after removal of precomputed quantities]
5757
ᶠu³_diff = p.scratch.ᶠtemp_CT3
@@ -434,7 +434,7 @@ function edmfx_sgs_diffusive_flux_tendency!(
434434
top = Operators.SetValue(C3(FT(0))),
435435
bottom = Operators.SetValue(ρatke_flux),
436436
)
437-
# Add flux divergence and dissipation term, relaxing TKE to zero
437+
# Add flux divergence and dissipation term, relaxing TKE to zero
438438
# in one time step if tke < 0
439439
@. Yₜ.c.sgs⁰.ρatke -=
440440
ᶜdivᵥ_ρatke(-(ᶠρaK_u * ᶠgradᵥ(ᶜtke⁰))) + ifelse(
@@ -510,7 +510,7 @@ function edmfx_sgs_diffusive_flux_tendency!(
510510
)
511511

512512
# Assumes envinronmental area fraction is 1 (so draft area fraction is negligible)
513-
# TODO: Relax this assumption and construct diagnostic EDMF fluxes in parallel to
513+
# TODO: Relax this assumption and construct diagnostic EDMF fluxes in parallel to
514514
# prognostic fluxes
515515
FT = Spaces.undertype(axes(Y.c))
516516
(; dt, params) = p
@@ -565,7 +565,7 @@ function edmfx_sgs_diffusive_flux_tendency!(
565565
top = Operators.SetValue(C3(FT(0))),
566566
bottom = Operators.SetValue(ρatke_flux),
567567
)
568-
# Add flux divergence and dissipation term, relaxing TKE to zero
568+
# Add flux divergence and dissipation term, relaxing TKE to zero
569569
# in one time step if tke < 0
570570
@. Yₜ.c.sgs⁰.ρatke -=
571571
ᶜdivᵥ_ρatke(-(ᶠρaK_u * ᶠgradᵥ(ᶜtke⁰))) + ifelse(
@@ -592,16 +592,48 @@ function edmfx_sgs_diffusive_flux_tendency!(
592592
@. Yₜ.c.ρq_tot -= ᶜρχₜ_diffusion
593593
@. Yₜ.c.ρ -= ᶜρχₜ_diffusion
594594
end
595+
if (
596+
p.atmos.moisture_model isa NonEquilMoistModel &&
597+
p.atmos.microphysics_model isa Microphysics1Moment
598+
)
599+
α = CAP.α_vert_diff_tracer(params)
595600

596-
# TODO: add liquid, ice, rain and snow specific humidity diffusion
601+
ᶜρχₜ_diffusion = p.scratch.ᶜtemp_scalar
602+
ᶜdivᵥ_ρq = Operators.DivergenceF2C(
603+
top = Operators.SetValue(C3(FT(0))),
604+
bottom = Operators.SetValue(C3(FT(0))),
605+
)
606+
microphysics_tracers = (
607+
(@name(c.ρq_liq), @name(q_liq)),
608+
(@name(c.ρq_ice), @name(q_ice)),
609+
(@name(c.ρq_rai), @name(q_rai)),
610+
(@name(c.ρq_sno), @name(q_sno)),
611+
)
612+
MatrixFields.unrolled_foreach(
613+
microphysics_tracers,
614+
) do (ρqʲ_name, name)
615+
MatrixFields.has_field(Y, ρqʲ_name) || return
616+
617+
ᶜρqʲ = MatrixFields.get_field(Y, ρqʲ_name)
618+
ᶜρqʲₜ = MatrixFields.get_field(Yₜ, ρqʲ_name)
619+
ᶜqʲ = (@. lazy(specific(ᶜρqʲ, Y.c.ρ)))
620+
621+
if name in (@name(q_liq), @name(q_ice))
622+
@. ᶜρχₜ_diffusion = ᶜdivᵥ_ρq(-(ᶠρaK_h * ᶠgradᵥ(ᶜqʲ)))
623+
elseif name in (@name(q_rai), @name(q_sno))
624+
@. ᶜρχₜ_diffusion = ᶜdivᵥ_ρq(-(ᶠρaK_h * α * ᶠgradᵥ(ᶜqʲ)))
625+
else
626+
error("Unsupported moisture tracer variable")
627+
end
628+
@. ᶜρqʲₜ -= ᶜρχₜ_diffusion
629+
end
630+
end
597631

598632
# Momentum diffusion
599633
ᶠstrain_rate = p.scratch.ᶠtemp_UVWxUVW
600634
ᶠstrain_rate .= compute_strain_rate_face(ᶜu)
601635
@. Yₜ.c.uₕ -= C12(ᶜdivᵥ(-(2 * ᶠρaK_u * ᶠstrain_rate)) / Y.c.ρ)
602636
end
603637

604-
# TODO: Add tracer fluxes
605-
606638
return nothing
607639
end

src/utils/variable_manipulations.jl

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ foreach_gs_tracer(f::F, Y_or_similar_values...) where {F} =
185185
Computes a smooth, monotonic weight function `w(a)` that ranges from 0 to 1.
186186
187187
This function is used as the interpolation weight in the regularized `specific`
188-
function. It ensures a numerically stable and smooth transition between a subgrid-scale
189-
(SGS) quantity and its grid-mean counterpart, especially when the SGS area fraction `a`
188+
function. It ensures a numerically stable and smooth transition between a subgrid-scale
189+
(SGS) quantity and its grid-mean counterpart, especially when the SGS area fraction `a`
190190
is small.
191191
192192
**Key Properties:**
@@ -195,7 +195,7 @@ is small.
195195
- `w(a_half) = 0.5`.
196196
- The function is continuously differentiable, with derivatives equal to zero at
197197
`a = 0` and `a = 1`, which ensures smooth blending.
198-
- The functions grows very rapidly near `a = a_half`, and grows very slowly at all other
198+
- The functions grows very rapidly near `a = a_half`, and grows very slowly at all other
199199
values of `a`.
200200
- For small `a_half`, the weight rapidly approaches 1 for values of `a` that are
201201
a few times larger than `a_half`.
@@ -210,7 +210,7 @@ constructed in two main steps to satisfy the key properties:
210210
2. **Midpoint Control**: To ensure the function passes through the control point
211211
`(a_half, 0.5)`, the input `a` is first transformed by a specially designed
212212
power function (`1 - (1 - a)^k`) before being passed to the bounded sigmoid.
213-
This transformation maps `a_half` to `0.5` while preserving differentiability
213+
This transformation maps `a_half` to `0.5` while preserving differentiability
214214
at the boundaries.
215215
216216
Arguments:
@@ -246,11 +246,11 @@ draft_sum(f, sgsʲs) = unrolled_sum(f, sgsʲs)
246246
"""
247247
ᶜenv_value(grid_scale_value, f_draft, gs, turbconv_model)
248248
249-
Computes the value of a quantity `ρaχ` in the environment subdomain by subtracting
249+
Computes the value of a quantity `ρaχ` in the environment subdomain by subtracting
250250
the sum of its values in all draft subdomains from the grid-scale value. Available
251251
for general variables in PrognosticEDMFX and environmental area (ᶜρa⁰) in DiagnosticEDMFX.
252252
253-
This is based on the domain decomposition principle for density-area weighted
253+
This is based on the domain decomposition principle for density-area weighted
254254
quantities: `GridMean(ρχ) = Env(ρaχ) + Sum(Drafts(ρaχ))`.
255255
256256
The function handles both PrognosticEDMFX and DiagnosticEDMFX models:
@@ -315,21 +315,7 @@ function ᶜspecific_env_value(::Val{χ_name}, Y, p) where {χ_name}
315315
ᶜρa⁰ = @. lazy(ρa⁰(Y.c.ρ, Y.c.sgsʲs, turbconv_model))
316316

317317
elseif turbconv_model isa DiagnosticEDMFX || turbconv_model isa EDOnlyEDMFX
318-
ᶜχʲs = getproperty(p.precomputed, Symbol(:ᶜ, χ_name, :ʲs))
319-
n = n_mass_flux_subdomains(turbconv_model)
320-
321-
# Σ ρaʲ * χʲ
322-
ᶜρaχʲs_sum = p.scratch.ᶜtemp_scalar_3
323-
@. ᶜρaχʲs_sum = 0
324-
for j in 1:n
325-
ᶜρaʲ = p.precomputed.ᶜρaʲs.:($j)
326-
ᶜχʲ = ᶜχʲs.:($j)
327-
@. ᶜρaχʲs_sum += ᶜρaʲ * ᶜχʲ
328-
end
329-
330-
ᶜρaχ⁰ = @. lazy(ᶜρχ - ᶜρaχʲs_sum)
331-
# Denominator: ρa⁰ = ρ - Σ ρaʲ, assume ᶜρa⁰ = ρ
332-
ᶜρa⁰ = Y.c.ρ
318+
error("Not implemented. You should use grid mean values.")
333319
end
334320

335321
return @. lazy(specific(
@@ -414,9 +400,9 @@ Computes the specific moist static energy (`mse`) in the environment (`mse⁰`).
414400
415401
This is a specialized helper function because `mse` is not a grid-scale prognostic
416402
variable. It first computes the grid-scale moist static energy density (`ρmse`)
417-
from other grid-scale quantities (`ρ`, total specific enthalpy `h_tot`, specific
418-
kinetic energy `K`). It then uses the `ᶜenv_value` helper to compute the environment's
419-
portion of `ρmse` and `ρa` via domain decomposition, and finally calculates the specific
403+
from other grid-scale quantities (`ρ`, total specific enthalpy `h_tot`, specific
404+
kinetic energy `K`). It then uses the `ᶜenv_value` helper to compute the environment's
405+
portion of `ρmse` and `ρa` via domain decomposition, and finally calculates the specific
420406
value using the regularized `specific` function.
421407
422408
Arguments:
@@ -473,9 +459,9 @@ end
473459
Computes the environment vertical velocity `u₃⁰`.
474460
475461
This function calculates the environment's total vertical momentum (`ρa⁰u₃⁰`) and
476-
its total area-weighted density (`ρa⁰`) using the domain decomposition principle
477-
(GridMean = Env + Sum(Drafts)). It then computes the final specific velocity `u₃⁰`
478-
using the regularized `specific` function to ensure numerical stability when the
462+
its total area-weighted density (`ρa⁰`) using the domain decomposition principle
463+
(GridMean = Env + Sum(Drafts)). It then computes the final specific velocity `u₃⁰`
464+
using the regularized `specific` function to ensure numerical stability when the
479465
environment area fraction `a⁰` is small.
480466
481467
Arguments:
@@ -499,7 +485,7 @@ u₃⁰(ρaʲs, u₃ʲs, ρ, u₃, turbconv_model) = specific(
499485
A wrapper for Julia's `mapreduce` function that automatically determines
500486
the initial value (`init`) for the reduction.
501487
502-
This is useful for iterators whose elements are custom structs or
488+
This is useful for iterators whose elements are custom structs or
503489
`ClimaCore.Geometry.AxisTensor`s, where the zero element cannot be inferred
504490
as a simple scalar. It uses `ClimaCore.RecursiveApply` tools (`rzero`,
505491
`rpromote_type`) to create a type-stable, correctly-structured zero element
@@ -523,7 +509,7 @@ Computes the dot product of two `Tuple`s (`a` and `b`) using a recursive,
523509
manually unrolled implementation.
524510
525511
This function is designed to be type-stable and efficient for CUDA kernels,
526-
where standard `mapreduce` implementations can otherwise suffer from type-inference
512+
where standard `mapreduce` implementations can otherwise suffer from type-inference
527513
failures.
528514
529515
It uses `ClimaCore.RecursiveApply` operators (`⊞` for addition, `⊠` for

0 commit comments

Comments
 (0)