Skip to content

Commit a2b15aa

Browse files
authored
Merge pull request #4106 from CliMA/sa/consistent_edmf_bcs
pedmf boundary conditions
2 parents 767ab99 + 8fcc7b8 commit a2b15aa

File tree

8 files changed

+179
-149
lines changed

8 files changed

+179
-149
lines changed

config/model_configs/prognostic_edmfx_trmm_column_0M.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ z_elem: 82
3030
z_stretch: false
3131
dz_bottom: 30
3232
dt: 150secs
33-
t_end: 6hours
33+
t_end: 12hours
3434
toml: [toml/prognostic_edmfx_implicit_scm_calibrated_5_cases_shallow_deep_v1.toml]
3535
netcdf_interpolation_num_points: [8, 8, 82]
3636
diagnostics:

reproducibility_tests/ref_counter.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
275
1+
276
22

33
# **README**
44
#
@@ -20,6 +20,10 @@
2020

2121

2222
#=
23+
276
24+
- Update prognostic EDMF boundary conditions: apply equal surface fluxes to the
25+
updraft and grid mean, and enable entrainment of buoyant air in the first cell.
26+
2327
275
2428
- Change order of GPU calculations for better performance, but it
2529
results in slightly different floating point rounding. Artifacts

src/cache/precomputed_quantities.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,7 @@ NVTX.@annotate function set_explicit_precomputed_quantities_part1!(Y, p, t)
556556
ᶜgradᵥ(ᶠinterp(TD.liquid_ice_pottemp(thermo_params, ᶜts)))
557557
end
558558

559-
if turbconv_model isa PrognosticEDMFX
560-
set_prognostic_edmf_precomputed_quantities_bottom_bc!(Y, p, t)
561-
elseif turbconv_model isa DiagnosticEDMFX
559+
if turbconv_model isa DiagnosticEDMFX
562560
set_diagnostic_edmf_precomputed_quantities_bottom_bc!(Y, p, t)
563561
elseif !(isnothing(turbconv_model))
564562
# Do nothing for other turbconv models for now

src/cache/prognostic_edmf_precomputed_quantities.jl

Lines changed: 22 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -115,150 +115,6 @@ NVTX.@annotate function set_prognostic_edmf_precomputed_quantities_draft!(
115115
return nothing
116116
end
117117

118-
"""
119-
set_prognostic_edmf_precomputed_quantities_bottom_bc!(Y, p, ᶠuₕ³, t)
120-
121-
Updates velocity and thermodynamics quantities at the surface in each SGS draft.
122-
"""
123-
NVTX.@annotate function set_prognostic_edmf_precomputed_quantities_bottom_bc!(
124-
Y,
125-
p,
126-
t,
127-
)
128-
(; moisture_model, turbconv_model, microphysics_model) = p.atmos
129-
130-
FT = Spaces.undertype(axes(Y.c))
131-
n = n_mass_flux_subdomains(turbconv_model)
132-
thermo_params = CAP.thermodynamics_params(p.params)
133-
turbconv_params = CAP.turbconv_params(p.params)
134-
135-
(; ᶜΦ,) = p.core
136-
(; ᶜp, ᶜK, ᶜtsʲs, ᶜρʲs, ᶜts) = p.precomputed
137-
(; ustar, obukhov_length, buoyancy_flux) = p.precomputed.sfc_conditions
138-
139-
for j in 1:n
140-
ᶜtsʲ = ᶜtsʲs.:($j)
141-
ᶜmseʲ = Y.c.sgsʲs.:($j).mse
142-
ᶜq_totʲ = Y.c.sgsʲs.:($j).q_tot
143-
if moisture_model isa NonEquilMoistModel && (
144-
microphysics_model isa Microphysics1Moment ||
145-
microphysics_model isa Microphysics2Moment
146-
)
147-
ᶜq_liqʲ = Y.c.sgsʲs.:($j).q_liq
148-
ᶜq_iceʲ = Y.c.sgsʲs.:($j).q_ice
149-
ᶜq_raiʲ = Y.c.sgsʲs.:($j).q_rai
150-
ᶜq_snoʲ = Y.c.sgsʲs.:($j).q_sno
151-
end
152-
153-
# We need field_values everywhere because we are mixing
154-
# information from surface and first interior inside the
155-
# sgs_scalar_first_interior_bc call.
156-
ᶜz_int_val =
157-
Fields.field_values(Fields.level(Fields.coordinate_field(Y.c).z, 1))
158-
z_sfc_val = Fields.field_values(
159-
Fields.level(Fields.coordinate_field(Y.f).z, Fields.half),
160-
)
161-
ᶜρ_int_val = Fields.field_values(Fields.level(Y.c.ρ, 1))
162-
ᶜp_int_val = Fields.field_values(Fields.level(ᶜp, 1))
163-
(; ρ_flux_h_tot, ρ_flux_q_tot, ustar, obukhov_length) =
164-
p.precomputed.sfc_conditions
165-
166-
buoyancy_flux_val = Fields.field_values(buoyancy_flux)
167-
ρ_flux_h_tot_val = Fields.field_values(ρ_flux_h_tot)
168-
ρ_flux_q_tot_val = Fields.field_values(ρ_flux_q_tot)
169-
170-
ustar_val = Fields.field_values(ustar)
171-
obukhov_length_val = Fields.field_values(obukhov_length)
172-
sfc_local_geometry_val = Fields.field_values(
173-
Fields.local_geometry_field(Fields.level(Y.f, Fields.half)),
174-
)
175-
176-
# Based on boundary conditions for updrafts we overwrite
177-
# the first interior point for EDMFX ᶜmseʲ...
178-
ᶜaʲ_int_val = p.scratch.temp_data_level
179-
# TODO: replace this with the actual surface area fraction when
180-
# using prognostic surface area
181-
@. ᶜaʲ_int_val = FT(turbconv_params.surface_area)
182-
ᶜh_tot = @. lazy(
183-
TD.total_specific_enthalpy(
184-
thermo_params,
185-
ᶜts,
186-
specific(Y.c.ρe_tot, Y.c.ρ),
187-
),
188-
)
189-
ᶜh_tot_int_val = Fields.field_values(Fields.level(ᶜh_tot, 1))
190-
ᶜK_int_val = Fields.field_values(Fields.level(ᶜK, 1))
191-
ᶜmseʲ_int_val = Fields.field_values(Fields.level(ᶜmseʲ, 1))
192-
@. ᶜmseʲ_int_val = sgs_scalar_first_interior_bc(
193-
ᶜz_int_val - z_sfc_val,
194-
ᶜρ_int_val,
195-
ᶜaʲ_int_val,
196-
ᶜh_tot_int_val - ᶜK_int_val,
197-
buoyancy_flux_val,
198-
ρ_flux_h_tot_val,
199-
ustar_val,
200-
obukhov_length_val,
201-
sfc_local_geometry_val,
202-
)
203-
204-
# ... and the first interior point for EDMFX ᶜq_totʲ.
205-
206-
ᶜq_tot = @. lazy(specific(Y.c.ρq_tot, Y.c.ρ))
207-
ᶜq_tot_int_val = Fields.field_values(Fields.level(ᶜq_tot, 1))
208-
ᶜq_totʲ_int_val = Fields.field_values(Fields.level(ᶜq_totʲ, 1))
209-
@. ᶜq_totʲ_int_val = sgs_scalar_first_interior_bc(
210-
ᶜz_int_val - z_sfc_val,
211-
ᶜρ_int_val,
212-
ᶜaʲ_int_val,
213-
ᶜq_tot_int_val,
214-
buoyancy_flux_val,
215-
ρ_flux_q_tot_val,
216-
ustar_val,
217-
obukhov_length_val,
218-
sfc_local_geometry_val,
219-
)
220-
221-
# Then overwrite the prognostic variables at first inetrior point.
222-
ᶜΦ_int_val = Fields.field_values(Fields.level(ᶜΦ, 1))
223-
ᶜtsʲ_int_val = Fields.field_values(Fields.level(ᶜtsʲ, 1))
224-
if moisture_model isa NonEquilMoistModel && (
225-
microphysics_model isa Microphysics1Moment ||
226-
microphysics_model isa Microphysics2Moment
227-
)
228-
ᶜq_liqʲ_int_val = Fields.field_values(Fields.level(ᶜq_liqʲ, 1))
229-
ᶜq_iceʲ_int_val = Fields.field_values(Fields.level(ᶜq_iceʲ, 1))
230-
ᶜq_raiʲ_int_val = Fields.field_values(Fields.level(ᶜq_raiʲ, 1))
231-
ᶜq_snoʲ_int_val = Fields.field_values(Fields.level(ᶜq_snoʲ, 1))
232-
@. ᶜtsʲ_int_val = TD.PhaseNonEquil_phq(
233-
thermo_params,
234-
ᶜp_int_val,
235-
ᶜmseʲ_int_val - ᶜΦ_int_val,
236-
TD.PhasePartition(
237-
ᶜq_totʲ_int_val,
238-
ᶜq_liqʲ_int_val + ᶜq_raiʲ_int_val,
239-
ᶜq_iceʲ_int_val + ᶜq_snoʲ_int_val,
240-
),
241-
)
242-
else
243-
@. ᶜtsʲ_int_val = TD.PhaseEquil_phq(
244-
thermo_params,
245-
ᶜp_int_val,
246-
ᶜmseʲ_int_val - ᶜΦ_int_val,
247-
ᶜq_totʲ_int_val,
248-
)
249-
end
250-
sgsʲs_ρ_int_val = Fields.field_values(Fields.level(ᶜρʲs.:($j), 1))
251-
sgsʲs_ρa_int_val =
252-
Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).ρa, 1))
253-
254-
@. sgsʲs_ρ_int_val = TD.air_density(thermo_params, ᶜtsʲ_int_val)
255-
@. sgsʲs_ρa_int_val =
256-
$(FT(turbconv_params.surface_area)) *
257-
TD.air_density(thermo_params, ᶜtsʲ_int_val)
258-
end
259-
return nothing
260-
end
261-
262118
"""
263119
set_prognostic_edmf_precomputed_quantities_implicit_closures!(Y, p, t)
264120
@@ -332,6 +188,7 @@ NVTX.@annotate function set_prognostic_edmf_precomputed_quantities_explicit_clos
332188
ᶠnh_pressure₃_buoyʲs,
333189
) = p.precomputed
334190
(; ustar, obukhov_length) = p.precomputed.sfc_conditions
191+
ᶜaʲ_int_val = p.scratch.temp_data_level
335192

336193
ᶜz = Fields.coordinate_field(Y.c).z
337194
z_sfc = Fields.level(Fields.coordinate_field(Y.f).z, Fields.half)
@@ -411,6 +268,27 @@ NVTX.@annotate function set_prognostic_edmf_precomputed_quantities_explicit_clos
411268
dt,
412269
)
413270

271+
# If the surface buoyancy flux is positive, increase entrainment in the first cell
272+
# so that the updraft area grows to at least `surface_area` within one timestep.
273+
# Otherwise (stable surface), leave entrainment unchanged.
274+
buoyancy_flux_val = Fields.field_values(p.precomputed.sfc_conditions.buoyancy_flux)
275+
sgsʲs_ρ_int_val = Fields.field_values(Fields.level(ᶜρʲs.:($j), 1))
276+
sgsʲs_ρa_int_val =
277+
Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).ρa, 1))
278+
@. ᶜaʲ_int_val = draft_area(sgsʲs_ρa_int_val, sgsʲs_ρ_int_val)
279+
entr_int_val = Fields.field_values(Fields.level(ᶜentrʲs.:($j), 1))
280+
detr_int_val = Fields.field_values(Fields.level(ᶜdetrʲs.:($j), 1))
281+
@. entr_int_val = limit_entrainment(
282+
ifelse(
283+
buoyancy_flux_val < 0 || ᶜaʲ_int_val >= $(FT(turbconv_params.surface_area)),
284+
entr_int_val,
285+
detr_int_val +
286+
($(FT(turbconv_params.surface_area)) / ᶜaʲ_int_val - 1) / FT(dt),
287+
),
288+
ᶜaʲ_int_val,
289+
dt,
290+
)
291+
414292
# The buoyancy term in the nonhydrostatic pressure closure is always applied
415293
# for prognostic edmf. The tendency is combined with the buoyancy term in the
416294
# updraft momentum equation in `edmfx_sgs_vertical_advection_tendency!`. This

src/prognostic_equations/edmfx_entr_detr.jl

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,139 @@ function edmfx_entr_detr_tendency!(Yₜ, Y, p, t, turbconv_model::PrognosticEDMF
580580
return nothing
581581
end
582582

583+
"""
584+
edmfx_first_interior_entr_tendency!(Yₜ, Y, p, t, turbconv_model::PrognosticEDMFX)
585+
586+
Apply first-interior–level entrainment tendencies for each EDMF updraft.
587+
588+
This routine (1) seeds a small positive updraft area fraction when surface
589+
buoyancy flux is positive—allowing the plume to grow from zero—and
590+
(2) adds entrainment tendencies for moist static energy (`mse`) and total
591+
humidity (`q_tot`) in the first model cell.
592+
The entrained tracer value is taken from `sgs_scalar_first_interior_bc`.
593+
"""
594+
edmfx_first_interior_entr_tendency!(Yₜ, Y, p, t, turbconv_model) = nothing
595+
function edmfx_first_interior_entr_tendency!(
596+
Yₜ,
597+
Y,
598+
p,
599+
t,
600+
turbconv_model::PrognosticEDMFX,
601+
)
602+
603+
(; params, dt) = p
604+
(; ᶜK, ᶜρʲs, ᶜentrʲs, ᶜts) = p.precomputed
605+
606+
FT = eltype(params)
607+
n = n_mass_flux_subdomains(p.atmos.turbconv_model)
608+
thermo_params = CAP.thermodynamics_params(params)
609+
turbconv_params = CAP.turbconv_params(params)
610+
ᶜaʲ_int_val = p.scratch.temp_data_level
611+
ᶜmse_buoyant_air_int_val = p.scratch.temp_data_level_2
612+
ᶜq_tot_buoyant_air_int_val = p.scratch.temp_data_level_3
613+
614+
(;
615+
ustar,
616+
obukhov_length,
617+
buoyancy_flux,
618+
ρ_flux_h_tot,
619+
ρ_flux_q_tot,
620+
ustar,
621+
obukhov_length,
622+
) =
623+
p.precomputed.sfc_conditions
624+
625+
ᶜz_int_val = Fields.field_values(Fields.level(Fields.coordinate_field(Y.c).z, 1))
626+
z_sfc_val =
627+
Fields.field_values(Fields.level(Fields.coordinate_field(Y.f).z, Fields.half))
628+
ᶜρ_int_val = Fields.field_values(Fields.level(Y.c.ρ, 1))
629+
630+
buoyancy_flux_val = Fields.field_values(buoyancy_flux)
631+
ρ_flux_h_tot_val = Fields.field_values(ρ_flux_h_tot)
632+
ρ_flux_q_tot_val = Fields.field_values(ρ_flux_q_tot)
633+
634+
ustar_val = Fields.field_values(ustar)
635+
obukhov_length_val = Fields.field_values(obukhov_length)
636+
sfc_local_geometry_val = Fields.field_values(
637+
Fields.local_geometry_field(Fields.level(Y.f, Fields.half)),
638+
)
639+
640+
ᶜh_tot = @. lazy(
641+
TD.total_specific_enthalpy(
642+
thermo_params,
643+
ᶜts,
644+
specific(Y.c.ρe_tot, Y.c.ρ),
645+
),
646+
)
647+
ᶜh_tot_int_val = Fields.field_values(Fields.level(ᶜh_tot, 1))
648+
ᶜK_int_val = Fields.field_values(Fields.level(ᶜK, 1))
649+
ᶜmse⁰ = ᶜspecific_env_mse(Y, p)
650+
env_mse_int_val = Fields.field_values(Fields.level(ᶜmse⁰, 1))
651+
652+
ᶜq_tot = @. lazy(specific(Y.c.ρq_tot, Y.c.ρ))
653+
ᶜq_tot_int_val = Fields.field_values(Fields.level(ᶜq_tot, 1))
654+
ᶜq_tot⁰ = ᶜspecific_env_value(@name(q_tot), Y, p)
655+
env_q_tot_int_val = Fields.field_values(Fields.level(ᶜq_tot⁰, 1))
656+
657+
for j in 1:n
658+
659+
# Seed a small positive updraft area fraction when surface buoyancy flux is positive.
660+
# This perturbation prevents the plume area from staying identically zero,
661+
# allowing entrainment to grow it to the prescribed surface area.
662+
sgsʲs_ρ_int_val = Fields.field_values(Fields.level(ᶜρʲs.:($j), 1))
663+
sgsʲs_ρa_int_val = Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).ρa, 1))
664+
sgsʲs_ρaₜ_int_val = Fields.field_values(Fields.level(Yₜ.c.sgsʲs.:($j).ρa, 1))
665+
@. sgsʲs_ρaₜ_int_val += ifelse(buoyancy_flux_val < 0,
666+
0,
667+
max(0, (sgsʲs_ρ_int_val * $(eps(FT)) - sgsʲs_ρa_int_val) / FT(dt)),
668+
)
669+
670+
# Apply entrainment tendencies in the first model cell for moist static energy (mse)
671+
# and total humidity (q_tot). The entrained fluid is assumed to have a scalar value
672+
# given by `sgs_scalar_first_interior_bc` (mean + SGS perturbation). Since
673+
# `edmfx_entr_detr_tendency!` computes entrainment based on the environment–updraft
674+
# contrast, we supply the high-value (entrained) tracer minus the environment value
675+
# here to form the correct tendency.
676+
entr_int_val = Fields.field_values(Fields.level(ᶜentrʲs.:($j), 1))
677+
@. ᶜaʲ_int_val = max(
678+
FT(turbconv_params.surface_area),
679+
draft_area(sgsʲs_ρa_int_val, sgsʲs_ρ_int_val),
680+
)
681+
682+
sgsʲs_mseₜ_int_val =
683+
Fields.field_values(Fields.level(Yₜ.c.sgsʲs.:($j).mse, 1))
684+
@. ᶜmse_buoyant_air_int_val = sgs_scalar_first_interior_bc(
685+
ᶜz_int_val - z_sfc_val,
686+
ᶜρ_int_val,
687+
ᶜaʲ_int_val,
688+
ᶜh_tot_int_val - ᶜK_int_val,
689+
buoyancy_flux_val,
690+
ρ_flux_h_tot_val,
691+
ustar_val,
692+
obukhov_length_val,
693+
sfc_local_geometry_val,
694+
)
695+
@. sgsʲs_mseₜ_int_val += entr_int_val * (ᶜmse_buoyant_air_int_val - env_mse_int_val)
696+
697+
sgsʲs_q_totₜ_int_val =
698+
Fields.field_values(Fields.level(Yₜ.c.sgsʲs.:($j).q_tot, 1))
699+
@. ᶜq_tot_buoyant_air_int_val = sgs_scalar_first_interior_bc(
700+
ᶜz_int_val - z_sfc_val,
701+
ᶜρ_int_val,
702+
ᶜaʲ_int_val,
703+
ᶜq_tot_int_val,
704+
buoyancy_flux_val,
705+
ρ_flux_q_tot_val,
706+
ustar_val,
707+
obukhov_length_val,
708+
sfc_local_geometry_val,
709+
)
710+
@. sgsʲs_q_totₜ_int_val +=
711+
entr_int_val * (ᶜq_tot_buoyant_air_int_val - env_q_tot_int_val)
712+
713+
end
714+
end
715+
583716
# limit entrainment and detrainment rates for prognostic EDMFX
584717
# limit rates approximately below the inverse timescale 1/dt
585718
limit_entrainment(entr::FT, a, dt) where {FT} = max(

src/prognostic_equations/mass_flux_closures.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ end
206206
Apply EDMF filters:
207207
- Relax u_3 to zero when it is negative
208208
- Relax ρa to zero when it is negative
209+
- Relax ρa to ρ when a is larger than one
209210
210211
This function modifies the tendency `Yₜ` in place based on the current state `Y`,
211212
parameters `p`, time `t`, and the turbulence convection model `turbconv_model`.
@@ -225,6 +226,8 @@ function edmfx_filter_tendency!(Yₜ, Y, p, t, turbconv_model::PrognosticEDMFX)
225226
@. Yₜ.f.sgsʲs.:($$j).u₃ -=
226227
C3(min(Y.f.sgsʲs.:($$j).u₃.components.data.:1, 0)) / FT(dt)
227228
@. Yₜ.c.sgsʲs.:($$j).ρa -= min(Y.c.sgsʲs.:($$j).ρa, 0) / FT(dt)
229+
@. Yₜ.c.sgsʲs.:($$j).ρa -=
230+
max(Y.c.sgsʲs.:($$j).ρa - p.precomputed.ᶜρʲs.:($$j), 0) / FT(dt)
228231
end
229232
end
230233
end

src/prognostic_equations/remaining_tendency.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ NVTX.@annotate function additional_tendency!(Yₜ, Y, p, t)
284284
if p.atmos.sgs_entr_detr_mode == Explicit()
285285
edmfx_entr_detr_tendency!(Yₜ, Y, p, t, p.atmos.turbconv_model)
286286
end
287+
edmfx_first_interior_entr_tendency!(Yₜ, Y, p, t, p.atmos.turbconv_model)
287288
if p.atmos.sgs_mf_mode == Explicit()
288289
edmfx_sgs_mass_flux_tendency!(Yₜ, Y, p, t, p.atmos.turbconv_model)
289290
end

0 commit comments

Comments
 (0)