Skip to content

Commit 11d17bb

Browse files
committed
pedmf bcs; updraft fluxes at the bottom boundary are computed based on surface-interior tracer contrasts and grid mean fluxes
1 parent eb298da commit 11d17bb

File tree

6 files changed

+160
-118
lines changed

6 files changed

+160
-118
lines changed

reproducibility_tests/ref_counter.jl

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

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

2121

2222
#=
23+
275
24+
- Change boundary conditions for prognostic EDMF; updraft surface fluxes are
25+
computed based on surface-interior tracer contrasts and grid-mean fluxes
26+
2327
274
2428
- Remove unused calculation of TKE exchange in mixing length
2529

src/cache/prognostic_edmf_precomputed_quantities.jl

Lines changed: 4 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -129,132 +129,19 @@ NVTX.@annotate function set_prognostic_edmf_precomputed_quantities_bottom_bc!(
129129

130130
FT = Spaces.undertype(axes(Y.c))
131131
n = n_mass_flux_subdomains(turbconv_model)
132-
thermo_params = CAP.thermodynamics_params(p.params)
133132
turbconv_params = CAP.turbconv_params(p.params)
133+
ᶜaʲ_int_val = p.scratch.temp_data_level
134134

135-
(; ᶜΦ,) = p.core
136-
(; ᶜp, ᶜK, ᶜtsʲs, ᶜρʲs, ᶜts) = p.precomputed
137-
(; ustar, obukhov_length, buoyancy_flux) = p.precomputed.sfc_conditions
135+
(; ᶜρʲs) = p.precomputed
138136

139137
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
250138
sgsʲs_ρ_int_val = Fields.field_values(Fields.level(ᶜρʲs.:($j), 1))
251139
sgsʲs_ρa_int_val =
252140
Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).ρa, 1))
141+
@. ᶜaʲ_int_val = draft_area(sgsʲs_ρa_int_val, sgsʲs_ρ_int_val)
253142

254-
@. sgsʲs_ρ_int_val = TD.air_density(thermo_params, ᶜtsʲ_int_val)
255143
@. sgsʲs_ρa_int_val =
256-
$(FT(turbconv_params.surface_area)) *
257-
TD.air_density(thermo_params, ᶜtsʲ_int_val)
144+
$(FT(turbconv_params.surface_area)) * sgsʲs_ρ_int_val
258145
end
259146
return nothing
260147
end

src/prognostic_equations/edmfx_boundary_condition.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,54 @@
22
##### EDMFX SGS boundary condition
33
#####
44

5+
"""
6+
sgs_scalar_flux_bc(
7+
χ_sfc, ᶜχ_int, ᶜχʲ_int, ᶜaʲ_int, ρ_flux_χ
8+
)
9+
10+
Computes the surface scalar flux for an EDMF updraft subdomain by scaling the
11+
grid-mean surface flux according to the relative surface–interior scalar
12+
contrasts in the updraft and the grid mean.
13+
14+
In this simplified formulation, the updraft scalar value at the surface is
15+
assumed to equal the grid-mean surface value (`χ_sfc`). The updraft flux is
16+
obtained by multiplying the grid-mean scalar flux (`ρ_flux_χ`) by the ratio
17+
(χ_sfc - ᶜχʲ_int)/(χ_sfc - ᶜχ_int), with appropriate limiting for numerical
18+
stability. If the surface–interior contrast in the grid mean is negligible,
19+
the grid-mean flux is returned to avoid division by zero.
20+
21+
# Arguments
22+
- `χ_sfc`: Scalar value at the surface (same for grid-mean and updraft).
23+
- `ᶜχ_int`: Grid-mean interior scalar at the first model level.
24+
- `ᶜχʲ_int`: Updraft interior scalar at the first model level.
25+
- `ᶜaʲ_int`: Updraft fractional area at the first model level.
26+
- `ρ_flux_χ`: Grid-mean surface scalar flux (mass-flux form).
27+
28+
# Returns
29+
- Updraft surface scalar flux for `χ` (same units as `ρ_flux_χ`), scaled by the
30+
limited ratio of updraft to grid-mean scalar contrasts.
31+
"""
32+
33+
function sgs_scalar_flux_bc(
34+
χ_sfc::FT,
35+
ᶜχ_int,
36+
ᶜχʲ_int,
37+
ᶜaʲ_int,
38+
ρ_flux_χ,
39+
) where {FT}
40+
41+
# when surface-interior difference on the grid mean is zero (negligible),
42+
# return grid mean flux (which is zero or negligible) to avoid division by zero
43+
if abs(χ_sfc - ᶜχ_int) < sqrt(floatmin(FT))
44+
return ρ_flux_χ
45+
end
46+
47+
# we limit the ratio of sgs to gs scalar flux for numerical stability; physically we don't need a limit
48+
limit = max(0, 1 / ᶜaʲ_int)
49+
sgs_to_gs_flux_ratio = max(-limit, min(limit, (χ_sfc - ᶜχʲ_int) / (χ_sfc - ᶜχ_int)))
50+
return sgs_to_gs_flux_ratio * ρ_flux_χ
51+
end
52+
553
"""
654
sgs_scalar_first_interior_bc(
755
ᶜz_int::FT,

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
@@ -279,6 +279,7 @@ NVTX.@annotate function additional_tendency!(Yₜ, Y, p, t)
279279
end
280280

281281
surface_flux_tendency!(Yₜ, Y, p, t)
282+
edmfx_surface_flux_tendency!(Yₜ, Y, p, t, p.atmos.turbconv_model)
282283

283284
radiation_tendency!(Yₜ, Y, p, t, p.atmos.radiation_mode)
284285
if p.atmos.sgs_entr_detr_mode == Explicit()

src/prognostic_equations/surface_flux.jl

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ function surface_flux_tendency!(Yₜ, Y, p, t)
123123
)
124124
btt = boundary_tendency_scalar(ᶜh_tot, sfc_conditions.ρ_flux_h_tot)
125125
@. Yₜ.c.ρe_tot -= btt
126+
126127
ρ_flux_χ = p.scratch.sfc_temp_C3
127128
foreach_gs_tracer(Yₜ, Y) do ᶜρχₜ, ᶜρχ, ρχ_name
128129
ᶜχ = @. lazy(specific(ᶜρχ, Y.c.ρ))
@@ -138,3 +139,101 @@ function surface_flux_tendency!(Yₜ, Y, p, t)
138139
end
139140
end
140141
end
142+
143+
"""
144+
edmfx_surface_flux_tendency!(Yₜ, Y, p, t, turbconv_model::PrognosticEDMFX)
145+
146+
Applies surface–flux tendencies to EDMF updraft prognostic variables when the
147+
turbulent convection scheme is the `PrognosticEDMFX` model.
148+
149+
This function computes and adds contributions from surface fluxes of moist
150+
static energy (`mse`) and total specific humidity (`q_tot`) to the corresponding
151+
updraft subdomain tendencies in `Yₜ`. For each EDMF subdomain, it evaluates
152+
subgrid scalar fluxes using `sgs_scalar_flux_bc` and converts these fluxes into
153+
tendencies localized to the surface-adjacent grid cell via
154+
`boundary_tendency_scalar`.
155+
156+
# Arguments
157+
- `Yₜ`: Tendency state vector, modified in place.
158+
- `Y`: Current state vector.
159+
- `p`: Cache containing parameters, thermodynamic settings, precomputed fields,
160+
and EDMF configuration information.
161+
- `t`: Current simulation time.
162+
- `turbconv_model::PrognosticEDMFX`: Dispatch selector specifying the EDMF scheme.
163+
"""
164+
edmfx_surface_flux_tendency!(Yₜ, Y, p, t, turbconv_model) = nothing
165+
function edmfx_surface_flux_tendency!(Yₜ, Y, p, t, turbconv_model::PrognosticEDMFX)
166+
p.atmos.disable_surface_flux_tendency && return
167+
168+
(; params) = p
169+
(; ᶜρʲs, ᶜK, ᶜts) = p.precomputed
170+
thermo_params = CAP.thermodynamics_params(params)
171+
n = n_mass_flux_subdomains(p.atmos.turbconv_model)
172+
173+
(; ρ_flux_h_tot, ts) =
174+
p.precomputed.sfc_conditions
175+
176+
ᶜaʲ = p.scratch.ᶜtemp_scalar
177+
ρ_flux_χʲ = p.scratch.sfc_temp_C3
178+
# We need field_values everywhere because we are mixing
179+
# information from surface and first interior inside the
180+
# sgs_scalar_flux_bc call.
181+
ρ_flux_χʲ_val = Fields.field_values(ρ_flux_χʲ)
182+
183+
# Based on boundary conditions for updrafts we compute
184+
# the tendency due to the surface flux for EDMFX ᶜmseʲ...
185+
ρ_flux_h_tot_val = Fields.field_values(ρ_flux_h_tot)
186+
h_tot_sfc = @. lazy(TD.specific_enthalpy(thermo_params, ts))
187+
h_tot_sfc_val = Fields.field_values(h_tot_sfc)
188+
ᶜh_tot = @. lazy(
189+
TD.total_specific_enthalpy(
190+
thermo_params,
191+
ᶜts,
192+
specific(Y.c.ρe_tot, Y.c.ρ),
193+
),
194+
)
195+
ᶜh_tot_int_val = Fields.field_values(Fields.level(ᶜh_tot, 1))
196+
ᶜK_int_val = Fields.field_values(Fields.level(ᶜK, 1))
197+
198+
for j in 1:n
199+
@. ᶜaʲ = draft_area(Y.c.sgsʲs.:($$j).ρa, ᶜρʲs.:($$j))
200+
ᶜaʲ_int_val = Fields.field_values(Fields.level(ᶜaʲ, 1))
201+
ᶜmseʲ_int_val = Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).mse, 1))
202+
203+
@. ρ_flux_χʲ_val = sgs_scalar_flux_bc(
204+
h_tot_sfc_val,
205+
ᶜh_tot_int_val - ᶜK_int_val,
206+
ᶜmseʲ_int_val,
207+
ᶜaʲ_int_val,
208+
ρ_flux_h_tot_val,
209+
)
210+
btt = boundary_tendency_scalar(Y.c.sgsʲs.:(1).mse, ρ_flux_χʲ)
211+
@. Yₜ.c.sgsʲs.:($$j).mse -= btt / p.precomputed.ᶜρʲs.:($$j)
212+
end
213+
214+
# ... and the tendency for EDMFX ᶜq_totʲ.
215+
if !(p.atmos.moisture_model isa DryModel)
216+
ρ_flux_q_tot_val = Fields.field_values(p.precomputed.sfc_conditions.ρ_flux_q_tot)
217+
q_tot_sfc = @. lazy(TD.total_specific_humidity(thermo_params, ts))
218+
q_tot_sfc_val = Fields.field_values(q_tot_sfc)
219+
ᶜq_tot = @. lazy(specific(Y.c.ρq_tot, Y.c.ρ))
220+
ᶜq_tot_int_val = Fields.field_values(Fields.level(ᶜq_tot, 1))
221+
222+
for j in 1:n
223+
@. ᶜaʲ = draft_area(Y.c.sgsʲs.:($$j).ρa, ᶜρʲs.:($$j))
224+
ᶜaʲ_int_val = Fields.field_values(Fields.level(ᶜaʲ, 1))
225+
ᶜq_totʲ_int_val = Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).q_tot, 1))
226+
227+
@. ρ_flux_χʲ_val = sgs_scalar_flux_bc(
228+
q_tot_sfc_val,
229+
ᶜq_tot_int_val,
230+
ᶜq_totʲ_int_val,
231+
ᶜaʲ_int_val,
232+
ρ_flux_q_tot_val,
233+
)
234+
btt = boundary_tendency_scalar(Y.c.sgsʲs.:(1).q_tot, ρ_flux_χʲ)
235+
@. Yₜ.c.sgsʲs.:($$j).q_tot -= btt / p.precomputed.ᶜρʲs.:($$j)
236+
end
237+
end
238+
239+
end

0 commit comments

Comments
 (0)