Skip to content

Commit 0116312

Browse files
authored
Merge pull request #4165 from CliMA/sa/implicit_water_advection
implicit water advection
2 parents 56447c1 + 8d88e26 commit 0116312

File tree

5 files changed

+130
-64
lines changed

5 files changed

+130
-64
lines changed

.buildkite/ci_driver.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,8 @@ if config.parsed_args["check_conservation"]
184184
@info " Net water change / total water: $water_conservation"
185185

186186
@test energy_conservation 0 atol = 100 * eps(FT)
187-
@test mass_conservation 0 atol = 100 * eps(FT)
188-
@test water_conservation 0 atol = 100 * eps(FT)
187+
@test mass_conservation 0 atol = 250 * eps(FT)
188+
@test water_conservation 0 atol = 250 * eps(FT)
189189
end
190190

191191
# Visualize the solution

src/prognostic_equations/implicit/implicit_tendency.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,7 @@ function implicit_vertical_advection_tendency!(Yₜ, Y, p, t)
194194
@. Yₜ.c.ρb_rim -= ᶜprecipdivᵥ(ᶠρ * ᶠright_bias(- ᶜwᵢ * specific(ρb_rim, ρ)))
195195
end
196196

197-
# TODO - decide if this needs to be explicit or implicit
198-
#vertical_advection_of_water_tendency!(Yₜ, Y, p, t)
197+
vertical_advection_of_water_tendency!(Yₜ, Y, p, t)
199198

200199
# This is equivalent to grad_v(Φ) + grad_v(p) / ρ
201200
ᶜΦ_r = @. lazy(phi_r(thermo_params, ᶜts))

src/prognostic_equations/implicit/manual_sparse_jacobian.jl

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
196196
name -> (@name(c.ρe_tot), name) => similar(Y.c, TridiagonalRow),
197197
available_condensate_mass_names,
198198
)...,
199+
MatrixFields.unrolled_map(
200+
name -> (@name(c.ρq_tot), name) => similar(Y.c, TridiagonalRow),
201+
available_condensate_mass_names,
202+
)...,
199203
(@name(c.uₕ), @name(c.uₕ)) =>
200204
!isnothing(atmos.turbconv_model) ||
201205
!disable_momentum_vertical_diffusion(
@@ -213,6 +217,14 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
213217
name -> (name, name) => similar(Y.c, TridiagonalRow),
214218
diffused_scalar_names,
215219
)...,
220+
MatrixFields.unrolled_map(
221+
name -> (@name(c.ρe_tot), name) => similar(Y.c, TridiagonalRow),
222+
available_condensate_mass_names,
223+
)...,
224+
MatrixFields.unrolled_map(
225+
name -> (@name(c.ρq_tot), name) => similar(Y.c, TridiagonalRow),
226+
available_condensate_mass_names,
227+
)...,
216228
(@name(c.ρe_tot), @name(c.ρq_tot)) =>
217229
similar(Y.c, TridiagonalRow),
218230
MatrixFields.unrolled_map(
@@ -347,8 +359,10 @@ function jacobian_cache(alg::ManualSparseJacobian, Y, atmos)
347359
!(atmos.moisture_model isa DryModel)
348360
gs_scalar_subalg = if !(atmos.moisture_model isa DryModel)
349361
MatrixFields.BlockLowerTriangularSolve(
350-
@name(c.ρq_tot),
351362
available_condensate_mass_names...,
363+
alg₂ = MatrixFields.BlockLowerTriangularSolve(
364+
@name(c.ρq_tot),
365+
),
352366
)
353367
else
354368
MatrixFields.BlockDiagonalSolve()
@@ -588,51 +602,21 @@ function update_jacobian!(alg::ManualSparseJacobian, cache, Y, p, dtγ, t)
588602
(@name(c.ρq_rim), @name(ᶜwᵢ), FT(1)),
589603
(@name(c.ρb_rim), @name(ᶜwᵢ), FT(1)),
590604
)
605+
internal_energy_func(name) =
606+
(name == @name(c.ρq_liq) || name == @name(c.ρq_rai)) ? TD.internal_energy_liquid :
607+
(name == @name(c.ρq_ice) || name == @name(c.ρq_sno)) ? TD.internal_energy_ice :
608+
nothing
591609
if !(p.atmos.moisture_model isa DryModel) || use_derivative(diffusion_flag)
592610
∂ᶜρe_tot_err_∂ᶜρe_tot = matrix[@name(c.ρe_tot), @name(c.ρe_tot)]
593611
@. ∂ᶜρe_tot_err_∂ᶜρe_tot = zero(typeof(∂ᶜρe_tot_err_∂ᶜρe_tot)) - (I,)
594612
end
595613

596614
if !(p.atmos.moisture_model isa DryModel)
597-
#TODO: tetsing explicit vs implicit
598-
#@. ∂ᶜρe_tot_err_∂ᶜρe_tot +=
599-
# dtγ * -(ᶜprecipdivᵥ_matrix()) ⋅
600-
# DiagonalMatrixRow(ᶠinterp(ᶜρ * ᶜJ) / ᶠJ) ⋅ ᶠright_bias_matrix() ⋅
601-
# DiagonalMatrixRow(
602-
# -(1 + ᶜkappa_m) / ᶜρ * ifelse(
603-
# ᶜh_tot == 0,
604-
# (Geometry.WVector(FT(0)),),
605-
# p.precomputed.ᶜwₕhₜ / ᶜh_tot,
606-
# ),
607-
# )
608-
609615
∂ᶜρe_tot_err_∂ᶜρq_tot = matrix[@name(c.ρe_tot), @name(c.ρq_tot)]
610616
@. ∂ᶜρe_tot_err_∂ᶜρq_tot = zero(typeof(∂ᶜρe_tot_err_∂ᶜρq_tot))
611-
#TODO: tetsing explicit vs implicit
612-
#@. ∂ᶜρe_tot_err_∂ᶜρq_tot =
613-
# dtγ * -(ᶜprecipdivᵥ_matrix()) ⋅
614-
# DiagonalMatrixRow(ᶠinterp(ᶜρ * ᶜJ) / ᶠJ) ⋅ ᶠright_bias_matrix() ⋅
615-
# DiagonalMatrixRow(
616-
# -(ᶜkappa_m) * ∂e_int_∂q_tot / ᶜρ * ifelse(
617-
# ᶜh_tot == 0,
618-
# (Geometry.WVector(FT(0)),),
619-
# p.precomputed.ᶜwₕhₜ / ᶜh_tot,
620-
# ),
621-
# )
622617

623618
∂ᶜρq_tot_err_∂ᶜρq_tot = matrix[@name(c.ρq_tot), @name(c.ρq_tot)]
624619
@. ∂ᶜρq_tot_err_∂ᶜρq_tot = zero(typeof(∂ᶜρq_tot_err_∂ᶜρq_tot)) - (I,)
625-
#TODO: testing explicit vs implicit
626-
#@. ∂ᶜρq_tot_err_∂ᶜρq_tot =
627-
# dtγ * -(ᶜprecipdivᵥ_matrix()) ⋅
628-
# DiagonalMatrixRow(ᶠinterp(ᶜρ * ᶜJ) / ᶠJ) ⋅ ᶠright_bias_matrix() ⋅
629-
# DiagonalMatrixRow(
630-
# -1 / ᶜρ * ifelse(
631-
# ᶜq_tot == 0,
632-
# (Geometry.WVector(FT(0)),),
633-
# p.precomputed.ᶜwₜqₜ / ᶜq_tot,
634-
# ),
635-
# ) - (I,)
636620

637621
# This scratch variable computation could be skipped if no tracers are present
638622
@. p.scratch.ᶜbidiagonal_adjoint_matrix_c3 =
@@ -641,6 +625,7 @@ function update_jacobian!(alg::ManualSparseJacobian, cache, Y, p, dtγ, t)
641625

642626
MatrixFields.unrolled_foreach(tracer_info) do (ρχₚ_name, wₚ_name, _)
643627
MatrixFields.has_field(Y, ρχₚ_name) || return
628+
644629
∂ᶜρχₚ_err_∂ᶜρχₚ = matrix[ρχₚ_name, ρχₚ_name]
645630
ᶜwₚ = MatrixFields.get_field(p.precomputed, wₚ_name)
646631
# TODO: come up with read-able names for the intermediate computations...
@@ -650,6 +635,24 @@ function update_jacobian!(alg::ManualSparseJacobian, cache, Y, p, dtγ, t)
650635
@. ∂ᶜρχₚ_err_∂ᶜρχₚ =
651636
p.scratch.ᶜbidiagonal_adjoint_matrix_c3
652637
p.scratch.ᶠband_matrix_wvec - (I,)
638+
639+
if ρχₚ_name in
640+
(@name(c.ρq_liq), @name(c.ρq_ice), @name(c.ρq_rai), @name(c.ρq_sno))
641+
∂ᶜρq_tot_err_∂ᶜρq = matrix[@name(c.ρq_tot), ρχₚ_name]
642+
@. ∂ᶜρq_tot_err_∂ᶜρq =
643+
p.scratch.ᶜbidiagonal_adjoint_matrix_c3
644+
p.scratch.ᶠband_matrix_wvec
645+
646+
∂ᶜρe_tot_err_∂ᶜρq = matrix[@name(c.ρe_tot), ρχₚ_name]
647+
e_int_func = internal_energy_func(ρχₚ_name)
648+
@. ∂ᶜρe_tot_err_∂ᶜρq =
649+
p.scratch.ᶜbidiagonal_adjoint_matrix_c3
650+
p.scratch.ᶠband_matrix_wvec
651+
DiagonalMatrixRow(
652+
e_int_func(thermo_params, p.precomputed.ᶜts) + ᶜΦ +
653+
$(Kin(ᶜwₚ, p.precomputed.ᶜu)),
654+
)
655+
end
653656
end
654657

655658
end
@@ -719,7 +722,7 @@ function update_jacobian!(alg::ManualSparseJacobian, cache, Y, p, dtγ, t)
719722
for (q_name, e_int_q, ∂cv∂q) in microphysics_tracers
720723
MatrixFields.has_field(Y, q_name) || continue
721724
∂ᶜρe_tot_err_∂ᶜρq = matrix[@name(c.ρe_tot), q_name]
722-
@. ∂ᶜρe_tot_err_∂ᶜρq =
725+
@. ∂ᶜρe_tot_err_∂ᶜρq +=
723726
dtγ * ᶜdiffusion_h_matrix
724727
DiagonalMatrixRow((ᶜkappa_m * (e_int_q - ∂cv∂q * (T - T_0)) - R_v * T) / ᶜρ)
725728
end

src/prognostic_equations/remaining_tendency.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ NVTX.@annotate function remaining_tendency!(Yₜ, Yₜ_lim, Y, p, t)
6969
horizontal_dynamics_tendency!(Yₜ, Y, p, t)
7070
hyperdiffusion_tendency!(Yₜ, Yₜ_lim, Y, p, t)
7171
explicit_vertical_advection_tendency!(Yₜ, Y, p, t)
72-
vertical_advection_of_water_tendency!(Yₜ, Y, p, t)
7372
additional_tendency!(Yₜ, Y, p, t)
7473
return Yₜ
7574
end

src/prognostic_equations/water_advection.jl

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,9 @@ is not a `DryModel`). The tendencies are calculated in a conservative form,
1919
2020
Fluxes at cell faces are reconstructed using a first-order upwind scheme.
2121
Specifically, face-valued density (`ᶠρ`) is multiplied by a right-biased,
22-
negated cell-centered specific flux term (e.g., `ᶠright_bias(-ᶜwₜqₜ)` for total water,
23-
where `ᶜwₜqₜ` represents `w q_t` at cell centers). The resulting face flux
22+
negated cell-centered specific flux term. The resulting face flux
2423
is then diverged using the `ᶜprecipdivᵥ` operator.
2524
26-
The precomputed terms `p.precomputed.ᶜwₜqₜ` and `p.precomputed.ᶜwₕhₜ` represent
27-
cell-centered specific vertical fluxes (e.g., `w ⋅ q_t` and `w ⋅ h_t`,
28-
respectively, where `w` is a vertical velocity component).
29-
30-
Additionally, when using prognostic EDMF with non-equilibrium moisture and microphysics,
31-
this function also applies advective tendencies to the subdomain moist static energy
32-
(`mse`) and total specific humidity (`q_tot`) fields within each mass flux
33-
subdomain. These tendencies use sedimentation velocities computed from the precomputed
34-
fields `ᶜwₕʲs` and `ᶜwₜʲs` for each subdomain j.
35-
3625
Arguments:
3726
- `Yₜ`: The tendency state vector, modified in place.
3827
- `Y`: The current state vector.
@@ -42,22 +31,98 @@ Arguments:
4231
4332
Modifies:
4433
- `Yₜ.c.ρ`, `Yₜ.c.ρe_tot`, and `Yₜ.c.ρq_tot` (always when moisture is present)
45-
- `Yₜ.c.sgsʲs.:(j).mse` and `Yₜ.c.sgsʲs.:(j).q_tot` (when using prognostic EDMFX with
46-
non-equilibrium moisture and multi-moment microphysics)
4734
"""
4835
function vertical_advection_of_water_tendency!(Yₜ, Y, p, t)
4936

37+
(; params) = p
38+
(; ᶜΦ) = p.core
39+
(; ᶜu, ᶜts) = p.precomputed
40+
thp = CAP.thermodynamics_params(params)
41+
5042
ᶜJ = Fields.local_geometry_field(Y.c).J
5143
ᶠJ = Fields.local_geometry_field(Y.f).J
52-
(; ᶜwₜqₜ, ᶜwₕhₜ) = p.precomputed
53-
54-
if !(p.atmos.moisture_model isa DryModel)
55-
@. Yₜ.c.ρ -=
56-
ᶜprecipdivᵥ(ᶠinterp(Y.c.ρ * ᶜJ) / ᶠJ * ᶠright_bias(-(ᶜwₜqₜ)))
57-
@. Yₜ.c.ρe_tot -=
58-
ᶜprecipdivᵥ(ᶠinterp(Y.c.ρ * ᶜJ) / ᶠJ * ᶠright_bias(-(ᶜwₕhₜ)))
59-
@. Yₜ.c.ρq_tot -=
60-
ᶜprecipdivᵥ(ᶠinterp(Y.c.ρ * ᶜJ) / ᶠJ * ᶠright_bias(-(ᶜwₜqₜ)))
44+
45+
microphysics_tracers = (
46+
(@name(ρq_liq), @name(ᶜwₗ)),
47+
(@name(ρq_ice), @name(ᶜwᵢ)),
48+
(@name(ρq_rai), @name(ᶜwᵣ)),
49+
(@name(ρq_sno), @name(ᶜwₛ)),
50+
)
51+
internal_energy_func(name) =
52+
(name == @name(ρq_liq) || name == @name(ρq_rai)) ? TD.internal_energy_liquid :
53+
(name == @name(ρq_ice) || name == @name(ρq_sno)) ? TD.internal_energy_ice :
54+
nothing
55+
MatrixFields.unrolled_foreach(microphysics_tracers) do (ρq_name, w_name)
56+
MatrixFields.has_field(Y.c, ρq_name) || return
57+
58+
ᶜρq = MatrixFields.get_field(Y.c, ρq_name)
59+
ᶜw = MatrixFields.get_field(p.precomputed, w_name)
60+
vtt = @.lazy(
61+
-1 * ᶜprecipdivᵥ(
62+
ᶠinterp(Y.c.ρ * ᶜJ) / ᶠJ * ᶠright_bias(
63+
Geometry.WVector(-(ᶜw)) * specific(ᶜρq, Y.c.ρ),
64+
),
65+
),
66+
)
67+
@. Yₜ.c.ρ += vtt
68+
@. Yₜ.c.ρq_tot += vtt
69+
70+
e_int_func = internal_energy_func(ρq_name)
71+
@. Yₜ.c.ρe_tot -= ᶜprecipdivᵥ(
72+
ᶠinterp(Y.c.ρ * ᶜJ) / ᶠJ * ᶠright_bias(
73+
Geometry.WVector(-(ᶜw)) * specific(ᶜρq, Y.c.ρ) *
74+
(e_int_func(thp, ᶜts) + ᶜΦ + $(Kin(ᶜw, ᶜu))),
75+
),
76+
)
77+
end
78+
79+
# For prognostic edmf, augment the energy tendencies with the additional energy contributions
80+
# so that the total-grid energy flux remains consistent. Specifically, we enforce that the
81+
# grid-mean energy flux equals the sum of the subdomain (updraft/environment) energy fluxes
82+
# by accounting for the energy carried by sedimenting tracers.
83+
if p.atmos.turbconv_model isa PrognosticEDMFX
84+
(; ᶜρʲs, ᶜtsʲs, ᶜts⁰) = p.precomputed
85+
86+
ᶜρ⁰ = @. lazy(TD.air_density(thp, ᶜts⁰))
87+
88+
# TODO the following code works for only one updraft
89+
sgs_microphysics_tracers = (
90+
(@name(q_liq), @name(ᶜwₗʲs.:(1)), @name(ᶜwₗ)),
91+
(@name(q_ice), @name(ᶜwᵢʲs.:(1)), @name(ᶜwᵢ)),
92+
(@name(q_rai), @name(ᶜwᵣʲs.:(1)), @name(ᶜwᵣ)),
93+
(@name(q_sno), @name(ᶜwₛʲs.:(1)), @name(ᶜwₛ)),
94+
)
95+
MatrixFields.unrolled_foreach(sgs_microphysics_tracers) do (q_name, wʲ_name, w_name)
96+
MatrixFields.has_field(Y.c.sgsʲs.:(1), q_name) || return
97+
98+
ᶜqʲ = MatrixFields.get_field(Y.c.sgsʲs.:(1), q_name)
99+
ᶜwʲ = MatrixFields.get_field(p.precomputed, wʲ_name)
100+
ᶜρq = MatrixFields.get_field(Y.c, get_ρχ_name(q_name))
101+
ᶜw = MatrixFields.get_field(p.precomputed, w_name)
102+
103+
e_int_func = internal_energy_func(get_ρχ_name(q_name))
104+
# TODO do we need to add kinetic energy of subdomains?
105+
@. Yₜ.c.ρe_tot -=
106+
ᶜprecipdivᵥ(
107+
ᶠinterp(ᶜρʲs.:(1) * ᶜJ) / ᶠJ * ᶠright_bias(
108+
Geometry.WVector(-(ᶜwʲ)) *
109+
draft_area(Y.c.sgsʲs.:(1).ρa, ᶜρʲs.:(1)) * ᶜqʲ *
110+
(
111+
e_int_func(thp, ᶜtsʲs.:(1)) - e_int_func(thp, ᶜts) -
112+
$(Kin(ᶜw, ᶜu))
113+
),
114+
),
115+
)
116+
117+
ᶜwaq⁰ = @. lazy((ᶜρq * ᶜw - Y.c.sgsʲs.:(1).ρa * ᶜqʲ * ᶜwʲ) / ᶜρ⁰)
118+
@. Yₜ.c.ρe_tot -=
119+
-1 * ᶜprecipdivᵥ(
120+
ᶠinterp(ᶜρ⁰ * ᶜJ) / ᶠJ * ᶠright_bias(
121+
Geometry.WVector(-(ᶜwaq⁰)) *
122+
(e_int_func(thp, ᶜts⁰) - e_int_func(thp, ᶜts) - $(Kin(ᶜw, ᶜu))),
123+
),
124+
)
125+
end
61126
end
62127

63128
return nothing

0 commit comments

Comments
 (0)