Skip to content

Commit d7d24d5

Browse files
committed
implement bcs for pedmf; flux at the bottom boundary
1 parent a1a9948 commit d7d24d5

File tree

4 files changed

+183
-120
lines changed

4 files changed

+183
-120
lines changed

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: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,74 @@
22
##### EDMFX SGS boundary condition
33
#####
44

5+
"""
6+
sgs_scalar_flux_bc(
7+
z_sfc, ρ_sfc, χ_sfc, ᶜχ_int, ᶜaʲ_int, ᶜχʲ_int,
8+
ρ_flux_χ, ustar, obukhov_length, sfc_local_geometry,
9+
)
10+
11+
Computes the surface scalar flux for an EDMF updraft subdomain based on the
12+
grid-mean scalar flux and an estimated subgrid (SGS) scalar fluctuation at the
13+
surface.
14+
15+
This function takes the resolved (grid-mean) surface flux of a scalar `χ`
16+
(e.g., `mse` or `q_tot`) and returns the corresponding flux for an EDMF updraft.
17+
The updraft scalar value at the surface is approximated as
18+
χʲ_sfc = χ_sfc + C * σ,
19+
where `χ_sfc` is the grid-mean surface scalar, `σ` is the estimated SGS scalar
20+
variance at the surface (computed from Monin–Obukhov similarity), and `C` is a
21+
coefficient based on the updraft fractional area. The resulting updraft flux is
22+
obtained by scaling the grid-mean flux according to the ratio of surface–interior
23+
scalar contrasts in the updraft and grid-mean fields.
24+
25+
# Arguments
26+
- `z_sfc`: Surface height of the scalar exchange level.
27+
- `ρ_sfc`: Air density at the surface.
28+
- `χ_sfc`: Grid-mean scalar value at the surface.
29+
- `ᶜχ_int`: Grid-mean interior scalar value at the first model level.
30+
- `ᶜaʲ_int`: Updraft fractional area at the first model level.
31+
- `ᶜχʲ_int`: Updraft interior scalar value at the first model level.
32+
- `ρ_flux_χ`: Grid-mean surface flux of the scalar (mass flux form).
33+
- `ustar`: Friction velocity.
34+
- `obukhov_length`: Obukhov length for surface stability.
35+
- `sfc_local_geometry`: Local geometric factors for the surface exchange.
36+
37+
# Returns
38+
- Updraft surface flux for scalar `χ` (same units as `ρ_flux_χ`).
39+
"""
40+
function sgs_scalar_flux_bc(
41+
z_sfc::FT,
42+
ρ_sfc,
43+
χ_sfc,
44+
ᶜχ_int,
45+
ᶜaʲ_int,
46+
ᶜχʲ_int,
47+
ρ_flux_χ,
48+
ustar,
49+
obukhov_length,
50+
sfc_local_geometry,
51+
) where {FT}
52+
53+
kinematic_sfc_flux_χ = ρ_flux_χ / ρ_sfc # Convert to kinematic flux [K m/s]
54+
55+
χ_sfc_var = get_scalar_variance(
56+
kinematic_sfc_flux_χ,
57+
ustar,
58+
z_sfc,
59+
obukhov_length,
60+
sfc_local_geometry,
61+
)
62+
63+
# TODO: This assumes that there is only one updraft, or that ᶜaʲ_int
64+
# is the specific area fraction for the updraft being considered when
65+
# sampling from the tail of the combined subgrid + grid-mean distribution.
66+
# The percentile range [1 - ᶜaʲ_int, 1] samples the top ᶜaʲ_int fraction.
67+
χʲ_sfc_coeff = percentile_bounds_mean_norm(1 - ᶜaʲ_int, FT(1))
68+
χʲ_sfc = χ_sfc + χʲ_sfc_coeff * sqrt(χ_sfc_var)
69+
70+
return (χʲ_sfc - ᶜχʲ_int) / (χ_sfc - ᶜχ_int) * ρ_flux_χ
71+
end
72+
573
"""
674
sgs_scalar_first_interior_bc(
775
ᶜz_int::FT,
@@ -66,7 +134,7 @@ function sgs_scalar_first_interior_bc(
66134

67135
kinematic_sfc_flux_scalar = sfc_ρ_flux_scalar / ᶜρ_int # Convert to kinematic flux [K m/s]
68136

69-
scalar_var = get_first_interior_variance(
137+
scalar_var = get_scalar_variance(
70138
kinematic_sfc_flux_scalar,
71139
ustar,
72140
ᶜz_int,
@@ -86,7 +154,7 @@ function sgs_scalar_first_interior_bc(
86154
end
87155

88156
"""
89-
get_first_interior_variance(
157+
get_scalar_variance(
90158
kinematic_scalar_flux,
91159
ustar::FT,
92160
z,
@@ -115,7 +183,7 @@ Arguments:
115183
Returns:
116184
- The estimated variance of the scalar quantity [K² or (kg/kg)²].
117185
"""
118-
function get_first_interior_variance(
186+
function get_scalar_variance(
119187
kinematic_scalar_flux,
120188
ustar::FT,
121189
z,

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: 107 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,109 @@ 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_χʲ = p.scratch.sfc_temp_C3
174+
ᶜaʲ = p.scratch.ᶜtemp_scalar
175+
176+
(; ρ_flux_h_tot, ρ_flux_q_tot, ustar, obukhov_length, ts) =
177+
p.precomputed.sfc_conditions
178+
179+
ρ_flux_h_tot_val = Fields.field_values(ρ_flux_h_tot)
180+
ρ_flux_q_tot_val = Fields.field_values(ρ_flux_q_tot)
181+
ustar_val = Fields.field_values(ustar)
182+
obukhov_length_val = Fields.field_values(obukhov_length)
183+
sfc_local_geometry_val = Fields.field_values(
184+
Fields.local_geometry_field(Fields.level(Y.f, Fields.half)),
185+
)
186+
187+
ρ_sfc = @. lazy(TD.air_density(thermo_params, ts))
188+
h_sfc = @. lazy(TD.specific_enthalpy(thermo_params, ts))
189+
q_tot_sfc = @. lazy(TD.total_specific_humidity(thermo_params, ts))
190+
z_sfc_val =
191+
Fields.field_values(Fields.level(Fields.coordinate_field(Y.f).z, Fields.half))
192+
ρ_sfc_val = Fields.field_values(ρ_sfc)
193+
h_sfc_val = Fields.field_values(h_sfc)
194+
q_tot_sfc_val = Fields.field_values(q_tot_sfc)
195+
196+
ᶜh_tot = @. lazy(
197+
TD.total_specific_enthalpy(
198+
thermo_params,
199+
ᶜts,
200+
specific(Y.c.ρe_tot, Y.c.ρ),
201+
),
202+
)
203+
ᶜh_tot_int_val = Fields.field_values(Fields.level(ᶜh_tot, 1))
204+
ᶜK_int_val = Fields.field_values(Fields.level(ᶜK, 1))
205+
ᶜq_tot = @. lazy(specific(Y.c.ρq_tot, Y.c.ρ))
206+
ᶜq_tot_int_val = Fields.field_values(Fields.level(ᶜq_tot, 1))
207+
208+
ρ_flux_χʲ_val = Fields.field_values(ρ_flux_χʲ)
209+
210+
for j in 1:n
211+
@. ᶜaʲ = draft_area(Y.c.sgsʲs.:($$j).ρa, ᶜρʲs.:($$j))
212+
ᶜaʲ_int_val = Fields.field_values(Fields.level(ᶜaʲ, 1))
213+
214+
ᶜmseʲ_int_val = Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).mse, 1))
215+
@. ρ_flux_χʲ_val = sgs_scalar_flux_bc(
216+
z_sfc_val,
217+
ρ_sfc_val,
218+
h_sfc_val,
219+
ᶜh_tot_int_val - ᶜK_int_val,
220+
ᶜaʲ_int_val,
221+
ᶜmseʲ_int_val,
222+
ρ_flux_h_tot_val,
223+
ustar_val,
224+
obukhov_length_val,
225+
sfc_local_geometry_val,
226+
)
227+
btt = boundary_tendency_scalar(Y.c.sgsʲs.:(1).mse, ρ_flux_χʲ)
228+
@. Yₜ.c.sgsʲs.:($$j).mse -= btt / p.precomputed.ᶜρʲs.:($$j)
229+
230+
ᶜq_totʲ_int_val = Fields.field_values(Fields.level(Y.c.sgsʲs.:($j).q_tot, 1))
231+
@. ρ_flux_χʲ_val = sgs_scalar_flux_bc(
232+
z_sfc_val,
233+
ρ_sfc_val,
234+
q_tot_sfc_val,
235+
ᶜq_tot_int_val,
236+
ᶜaʲ_int_val,
237+
ᶜq_totʲ_int_val,
238+
ρ_flux_q_tot_val,
239+
ustar_val,
240+
obukhov_length_val,
241+
sfc_local_geometry_val,
242+
)
243+
btt = boundary_tendency_scalar(Y.c.sgsʲs.:(1).q_tot, ρ_flux_χʲ)
244+
@. Yₜ.c.sgsʲs.:($$j).q_tot -= btt / p.precomputed.ᶜρʲs.:($$j)
245+
end
246+
247+
end

0 commit comments

Comments
 (0)