Skip to content

Commit f438d40

Browse files
committed
implement ARG aerosol activation with prescribed aerosol densities for 2M microphysics
1 parent 5557f30 commit f438d40

File tree

6 files changed

+204
-88
lines changed

6 files changed

+204
-88
lines changed

.buildkite/Manifest-v1.11.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,10 @@ uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9"
461461
version = "0.1.13"
462462

463463
[[deps.CloudMicrophysics]]
464-
deps = ["ClimaParams", "DocStringExtensions", "ForwardDiff", "LazyArtifacts", "LogExpFunctions", "QuadGK", "RootSolvers", "SpecialFunctions", "Thermodynamics"]
465-
git-tree-sha1 = "cfc9decc96d616ab3b8d911f843eeb2aafd7d2ad"
464+
deps = ["ClimaParams", "DocStringExtensions", "ForwardDiff", "LazyArtifacts", "LogExpFunctions", "QuadGK", "RootSolvers", "SpecialFunctions", "StaticArrays", "Thermodynamics"]
465+
git-tree-sha1 = "8f7137d8bb3098d6c46b2c3f859fb873d5b28abd"
466466
uuid = "6a9e3e04-43cd-43ba-94b9-e8782df3c71b"
467-
version = "0.25.0"
467+
version = "0.26.1"
468468

469469
[deps.CloudMicrophysics.extensions]
470470
EmulatorModelsExt = ["DataFrames", "MLJ"]

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ ClimaInterpolations = "0.1.0"
4949
ClimaParams = "0.10.35"
5050
ClimaTimeSteppers = "0.8.2"
5151
ClimaUtilities = "0.1.22"
52-
CloudMicrophysics = "0.25.0"
52+
CloudMicrophysics = "0.26.1"
5353
Dates = "1"
5454
ForwardDiff = "0.10.38, 1"
5555
Insolation = "0.9.2"
@@ -64,10 +64,10 @@ NullBroadcasts = "0.1"
6464
RRTMGP = "0.21.3"
6565
Random = "1"
6666
SciMLBase = "2.12"
67-
StaticArrays = "1.7"
67+
StaticArrays = "1.9"
6868
Statistics = "1"
6969
SurfaceFluxes = "0.11, 0.12"
70-
Thermodynamics = "0.12.13"
70+
Thermodynamics = "0.12.14"
7171
UnrolledUtilities = "0.1.9"
7272
YAML = "0.4"
7373
julia = "1.9"

config/model_configs/aquaplanet_nonequil_allsky_gw_res_2M.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ orographic_gravity_wave: "gfdl_restart"
2020
surface_setup: "DefaultMoninObukhov"
2121
prescribe_ozone: true
2222
reproducibility_test: true
23-
prescribed_aerosols: ["SO4", "CB1", "OC1", "DST01", "SSLT01"]
23+
prescribed_aerosols: ["SO4", "CB1", "OC1", "DST01", "SSLT01", "SSLT02", "SSLT03", "SSLT04", "SSLT05"]
2424
diagnostics:
2525
- short_name: [edt, evu, mmrso4, mmrbcpo, mmrocpo, mmrdust, mmrss, loadss, o3, od550aer, odsc550aer]
2626
reduction_time: average

src/parameterized_tendencies/microphysics/cloud_condensate.jl

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,67 @@ function cloud_condensate_tendency!(
105105
dt,
106106
)
107107

108-
@. Yₜ.c.ρn_liq +=
109-
Y.c.ρ * aerosol_activation_sources(
110-
cmc.liquid,
111-
thp,
112-
Y.c.ρ,
113-
Tₐ,
114-
specific(Y.c.ρq_tot, Y.c.ρ),
115-
specific(Y.c.ρq_liq, Y.c.ρ),
116-
specific(Y.c.ρq_ice, Y.c.ρ),
117-
specific(Y.c.ρq_rai, Y.c.ρ),
118-
specific(Y.c.ρq_sno, Y.c.ρ),
119-
specific(Y.c.ρn_liq, Y.c.ρ),
120-
cmc.N_cloud_liquid_droplets,
121-
dt,
122-
)
108+
# Aerosol activation using prescribed aerosol (Sea salt and sulfate)
109+
if !(:prescribed_aerosols_field in propertynames(p.tracers))
110+
return
111+
end
112+
seasalt_num = p.scratch.ᶜtemp_scalar
113+
seasalt_mean_radius = p.scratch.ᶜtemp_scalar_2
114+
sulfate_num = p.scratch.ᶜtemp_scalar_3
115+
@. seasalt_num = 0
116+
@. seasalt_mean_radius = 0
117+
@. sulfate_num = 0
118+
# Get aerosol concentrations if available
119+
seasalt_names = [:SSLT01, :SSLT02, :SSLT03, :SSLT04, :SSLT05]
120+
sulfate_names = [:SO4]
121+
aerosol_field = p.tracers.prescribed_aerosols_field
122+
for aerosol_name in propertynames(aerosol_field)
123+
if aerosol_name in seasalt_names
124+
seasalt_particle_radius = getproperty(
125+
cmc.aerosol,
126+
Symbol(string(aerosol_name) * "_radius"),
127+
)
128+
seasalt_particle_mass =
129+
4 / 3 *
130+
FT(pi) *
131+
seasalt_particle_radius^3 *
132+
cmc.aerosol.seasalt_density
133+
seasalt_mass = getproperty(aerosol_field, aerosol_name)
134+
@. seasalt_num += seasalt_mass / seasalt_particle_mass
135+
@. seasalt_mean_radius +=
136+
seasalt_mass / seasalt_particle_mass *
137+
log(seasalt_particle_radius)
138+
elseif aerosol_name in sulfate_names
139+
sulfate_particle_mass =
140+
4 / 3 *
141+
FT(pi) *
142+
cmc.aerosol.sulfate_radius^3 *
143+
cmc.aerosol.sulfate_density
144+
sulfate_mass = getproperty(aerosol_field, aerosol_name)
145+
@. sulfate_num += sulfate_mass / sulfate_particle_mass
146+
end
147+
end
148+
@. seasalt_mean_radius = exp(seasalt_mean_radius / seasalt_num)
149+
150+
# Compute aerosol activation (ARG 2000)
151+
(; ᶜu) = p.precomputed
152+
ᶜw = p.scratch.ᶜtemp_scalar_4
153+
Snₗ = p.scratch.ᶜtemp_scalar_5
154+
@. ᶜw = max(0, w_component.(Geometry.WVector.(ᶜu)))
155+
@. Snₗ = aerosol_activation_sources(
156+
seasalt_num,
157+
seasalt_mean_radius,
158+
sulfate_num,
159+
specific(Y.c.ρq_tot, Y.c.ρ),
160+
specific(Y.c.ρq_liq, Y.c.ρ),
161+
specific(Y.c.ρq_ice, Y.c.ρ),
162+
specific(Y.c.ρn_liq, Y.c.ρ),
163+
Y.c.ρ,
164+
ᶜw,
165+
(cmc,),
166+
thp,
167+
ᶜts,
168+
dt,
169+
)
170+
@. Yₜ.c.ρn_liq += Y.c.ρ * Snₗ
123171
end

src/parameterized_tendencies/microphysics/microphysics_wrappers.jl

Lines changed: 113 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# A set of wrappers for using CloudMicrophysics.jl functions inside EDMFX loops
22

33
import Thermodynamics as TD
4+
import CloudMicrophysics.ThermodynamicsInterface as CMTDI
45
import CloudMicrophysics.Microphysics0M as CM0
56
import CloudMicrophysics.Microphysics1M as CM1
67
import CloudMicrophysics.Microphysics2M as CM2
78
import CloudMicrophysics.MicrophysicsNonEq as CMNe
9+
import CloudMicrophysics.AerosolModel as CMAM
10+
import CloudMicrophysics.AerosolActivation as CMAA
811
import CloudMicrophysics.Parameters as CMP
912

1013
# Define some aliases and functions to make the code more readable
1114
const Tₐ = TD.air_temperature
15+
const Pₐ = TD.air_pressure
1216
const PP = TD.PhasePartition
1317
const qᵥ = TD.vapor_specific_humidity
1418

@@ -25,33 +29,6 @@ function limit(q, dt, n::Int)
2529
return max(FT(0), q) / float(dt) / n
2630
end
2731

28-
"""
29-
ml_N_cloud_liquid_droplets(cmc, c_dust, c_seasalt, c_SO4, q_liq)
30-
31-
- cmc - a struct with cloud and aerosol parameters
32-
- c_dust, c_seasalt, c_SO4 - dust, seasalt and ammonium sulfate mass concentrations [kg/kg]
33-
- q_liq - liquid water specific humidity
34-
35-
Returns the liquid cloud droplet number concentration diagnosed based on the
36-
aerosol loading and cloud liquid water.
37-
"""
38-
function ml_N_cloud_liquid_droplets(cmc, c_dust, c_seasalt, c_SO4, q_liq)
39-
# We can also add w, T, RH, w' ...
40-
# Also consider lookind only at around cloud base height
41-
(; α_dust, α_seasalt, α_SO4, α_q_liq) = cmc.aml
42-
(; c₀_dust, c₀_seasalt, c₀_SO4, q₀_liq) = cmc.aml
43-
N₀ = cmc.N_cloud_liquid_droplets
44-
45-
FT = eltype(N₀)
46-
return N₀ * (
47-
FT(1) +
48-
α_dust * (log(max(c_dust, eps(FT))) - log(c₀_dust)) +
49-
α_seasalt * (log(max(c_seasalt, eps(FT))) - log(c₀_seasalt)) +
50-
α_SO4 * (log(max(c_SO4, eps(FT))) - log(c₀_SO4)) +
51-
α_q_liq * (log(max(q_liq, eps(FT))) - log(q₀_liq))
52-
)
53-
end
54-
5532
"""
5633
cloud_sources(cm_params, thp, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Tₐ, dt)
5734
@@ -386,60 +363,130 @@ end
386363
#####
387364

388365
"""
389-
aerosol_activation_sources(cm_params, thp, ρ, Tₐ, qₜ, qₗ, qᵢ, qᵣ, qₛ, n_dp, n_dp_prescribed, dt)
390-
391-
- cm_params - CloudMicrophysics parameters struct for cloud water or ice condensate
392-
- thp - Thermodynamics parameters struct
393-
- ρ - air density
394-
- Tₐ - air temperature
395-
- qₜ - total specific humidity
396-
- qₗ - liquid specific humidity
397-
- qᵢ - ice specific humidity
398-
- qᵣ - rain specific humidity
399-
- qₛ - snow specific humidity
400-
- n_dp - number concentration droplets (liquid or ice) per mass
401-
_ n_dp_prescribed - prescribed number concentration of droplets (liquid or ice) per mass
402-
- dt - model time step
366+
aerosol_activation_sources(
367+
seasalt_num,
368+
seasalt_mean_radius,
369+
sulfate_num,
370+
nₗ,
371+
ρ,
372+
w,
373+
air_params,
374+
aerosol_params,
375+
arg_params,
376+
thermo_params,
377+
ts,
378+
dt,
379+
)
403380
404-
Returns the activation rate. #TODO This function temporarily computes activation rate
405-
based on mass rates and a prescribed droplet mass (no activation parameterization yet).
381+
Computes the source term for cloud droplet number concentration per mass due to aerosol activation,
382+
based on the Abdul-Razzak and Ghan (2000) parameterization.
383+
384+
This function estimates the number of aerosols activated into cloud droplets per mass of air per second
385+
from a bi-modal aerosol distribution (sea salt and sulfate), given local supersaturation and vertical
386+
velocity. The result is returned as a tendency (per second) of liquid droplet number concentration.
387+
388+
# Arguments
389+
- `seasalt_num`: Number concentration per mass of sea salt aerosols [kg⁻¹].
390+
- `seasalt_mean_radius`: Mean dry radius of sea salt aerosol mode [m].
391+
- `sulfate_num`: Number concentration per mass of sulfate aerosols [kg⁻¹].
392+
- `qₜ`, `qₗ`, `qᵢ` - total water, cloud liquid and cloud ice specific humidity
393+
- `nₗ`, - cloud liquid number concentration per mass [kg⁻¹]
394+
- `ρ`: Air density [kg/m³].
395+
- `w`: Vertical velocity [m/s].
396+
- `air_params`: Struct containing physical constants related to air.
397+
- `aerosol_params`: Struct containing aerosol properties, such as hygroscopicity parameters (κ).
398+
- `arg_params`: Activation scheme parameters specific to Abdul-Razzak and Ghan (2000).
399+
- `thermo_params`: Thermodynamic parameters for computing saturation, pressure, temperature, etc.
400+
- `ts`: Thermodynamic state (e.g., prognostic variables) used for evaluating the phase partition.
401+
- `dt`: Time step (s) over which the activation tendency is applied.
402+
403+
# Returns
404+
- Tendency of cloud droplet number concentration per mass of air due to aerosol activation [m⁻³/s].
406405
"""
407406
function aerosol_activation_sources(
408-
cm_params::CMP.CloudLiquid{FT},
409-
thp,
410-
ρ,
411-
Tₐ,
407+
seasalt_num,
408+
seasalt_mean_radius,
409+
sulfate_num,
412410
qₜ,
413411
qₗ,
414412
qᵢ,
415-
qᵣ,
416-
qₛ,
417-
n_dp,
418-
n_dp_prescribed,
413+
nₗ,
414+
ρ,
415+
w,
416+
cmc,
417+
thermo_params,
418+
ts,
419419
dt,
420-
) where {FT}
421-
r_dp = FT(2e-6) # 2 μm
422-
m_dp = 4 / 3 * FT(π) * r_dp^3 * cm_params.ρw
423-
Sn = cloud_sources(cm_params, thp, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Tₐ, dt) / m_dp
420+
)
421+
422+
FT = eltype(nₗ)
423+
air_params = cmc.aps
424+
arg_params = cmc.arg
425+
aerosol_params = cmc.aerosol
426+
T = Tₐ(thermo_params, ts)
427+
p = Pₐ(thermo_params, ts)
428+
S = CMTDI.supersaturation_over_liquid(thermo_params, qₜ, qₗ, qᵢ, ρ, T)
429+
n_aer = seasalt_num + sulfate_num
430+
if S < 0 || n_aer < eps(FT)
431+
return FT(0)
432+
end
433+
434+
seasalt_mode = CMAM.Mode_κ(
435+
seasalt_mean_radius,
436+
aerosol_params.seasalt_std,
437+
seasalt_num * ρ,
438+
(FT(1),),
439+
(FT(1),),
440+
(FT(0),),
441+
(aerosol_params.seasalt_kappa,),
442+
)
443+
sulfate_mode = CMAM.Mode_κ(
444+
aerosol_params.sulfate_radius,
445+
aerosol_params.sulfate_std,
446+
sulfate_num * ρ,
447+
(FT(1),),
448+
(FT(1),),
449+
(FT(0),),
450+
(aerosol_params.sulfate_kappa,),
451+
)
452+
453+
aerosol_dist = CMAM.AerosolDistribution((seasalt_mode, sulfate_mode))
454+
455+
args = (
456+
arg_params,
457+
aerosol_dist,
458+
air_params,
459+
thermo_params,
460+
T,
461+
p,
462+
w,
463+
qₜ,
464+
qₗ,
465+
qᵢ,
466+
ρ * nₗ,
467+
FT(0),
468+
) #assuming no ice particles
469+
S_max = CMAA.max_supersaturation(args...)
470+
n_act = CMAA.total_N_activated(args...) / ρ
424471

425472
return ifelse(
426-
Sn > FT(0),
427-
triangle_inequality_limiter(Sn, limit((n_dp_prescribed - n_dp), dt, 2)),
428-
-triangle_inequality_limiter(abs(Sn), limit(n_dp, dt, 2)),
473+
S_max < S || isnan(n_act) || n_act < nₗ,
474+
FT(0),
475+
triangle_inequality_limiter(n_act - nₗ, n_aer - nₗ) / dt,
429476
)
430477
end
431478

432479
"""
433480
compute_warm_precipitation_sources_2M!(Sᵖ, S₂ᵖ, Snₗᵖ, Snᵣᵖ, Sqₗᵖ, Sqᵣᵖ, ρ, nₗ, nᵣ, qₜ, qₗ, qᵢ, qᵣ, qₛ, ts, dt, sb, thp)
434481
435-
- Sᵖ, S₂ᵖ - temporary containters to help compute precipitation source terms
436-
- Snₗᵖ, Snᵣᵖ, Sqₗᵖ, Sqᵣᵖ - cached storage for precipitation source terms
437-
- ρ - air density
438-
- nₗ, nᵣ - cloud liquid and rain number concentration per mass [1 / kg of moist air]
439-
- qₜ, qₗ, qᵢ, qᵣ, qₛ - total water, cloud liquid, cloud ice, rain and snow specific humidity
440-
- ts - thermodynamic state (see td package for details)
441-
- dt - model time step
442-
- thp, mp - structs with thermodynamic and microphysics parameters
482+
- `Sᵖ`, `S₂ᵖ` - temporary containters to help compute precipitation source terms
483+
- `Snₗᵖ`, `Snᵣᵖ`, `Sqₗᵖ`, `Sqᵣᵖ` - cached storage for precipitation source terms
484+
- `ρ` - air density
485+
- `nₗ`, `nᵣ` - cloud liquid and rain number concentration per mass [1 / kg of moist air]
486+
- `qₜ`, `qₗ`, `qᵢ`, `qᵣ`, `qₛ` - total water, cloud liquid, cloud ice, rain and snow specific humidity
487+
- `ts` - thermodynamic state (see td package for details)
488+
- `dt` - model time step
489+
- `thp`, `mp` - structs with thermodynamic and microphysics parameters
443490
444491
Computes precipitation number and mass sources due to warm precipitation processes based on the 2-moment
445492
[Seifert and Beheng (2006) scheme](https://clima.github.io/CloudMicrophysics.jl/dev/Microphysics2M/).

src/parameters/create_parameters.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ cloud_parameters(toml_dict::CP.AbstractTOMLDict) = (;
135135
"ClimaAtmos",
136136
).prescribed_cloud_droplet_number_concentration,
137137
aml = aerosol_ml_parameters(toml_dict),
138+
aps = CM.Parameters.AirProperties(toml_dict),
139+
arg = CM.Parameters.AerosolActivationParameters(toml_dict),
140+
aerosol = prescribed_aerosol_parameters(toml_dict),
138141
)
139142

140143
microphys_1m_parameters(::Type{FT}) where {FT <: AbstractFloat} =
@@ -195,6 +198,24 @@ function aerosol_ml_parameters(toml_dict)
195198
return CP.get_parameter_values(toml_dict, name_map, "ClimaAtmos")
196199
end
197200

201+
function prescribed_aerosol_parameters(toml_dict)
202+
name_map = (;
203+
:MERRA2_seasalt_aerosol_bin01_radius => :SSLT01_radius,
204+
:MERRA2_seasalt_aerosol_bin02_radius => :SSLT02_radius,
205+
:MERRA2_seasalt_aerosol_bin03_radius => :SSLT03_radius,
206+
:MERRA2_seasalt_aerosol_bin04_radius => :SSLT04_radius,
207+
:MERRA2_seasalt_aerosol_bin05_radius => :SSLT05_radius,
208+
:seasalt_aerosol_kappa => :seasalt_kappa,
209+
:seasalt_aerosol_density => :seasalt_density,
210+
:mam3_stdev_coarse => :seasalt_std,
211+
:MERRA2_sulfate_aerosol_radius => :sulfate_radius,
212+
:sulfate_aerosol_kappa => :sulfate_kappa,
213+
:sulfate_aerosol_density => :sulfate_density,
214+
:mam3_stdev_accum => :sulfate_std,
215+
)
216+
return CP.get_parameter_values(toml_dict, name_map, "ClimaAtmos")
217+
end
218+
198219
to_svec(x::AbstractArray) = SA.SVector{length(x)}(x)
199220
to_svec(x) = x
200221
to_svec(x::NamedTuple) = map(x -> to_svec(x), x)

0 commit comments

Comments
 (0)