Skip to content

Commit b3eb3c5

Browse files
committed
ARG aerosol activation applied at cloud base
1 parent 7be8e1e commit b3eb3c5

File tree

6 files changed

+211
-76
lines changed

6 files changed

+211
-76
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ AtmosphericProfilesLibrary = "0.1.7"
4444
ClimaComms = "0.6.8"
4545
ClimaCore = "0.14.34"
4646
ClimaDiagnostics = "0.2.12"
47-
ClimaParams = "0.10.32"
47+
ClimaParams = "0.10.33"
4848
ClimaTimeSteppers = "0.8.2"
4949
ClimaUtilities = "0.1.22"
5050
CloudMicrophysics = "0.24.1"

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/cache/temporary_quantities.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ function temporary_quantities(Y, atmos)
2222
temp_field_level = Fields.level(Fields.Field(FT, center_space), 1),
2323
temp_field_level_2 = Fields.level(Fields.Field(FT, center_space), 1),
2424
temp_field_level_3 = Fields.level(Fields.Field(FT, center_space), 1),
25+
temp_tuple_field_level = similar(
26+
Fields.level(Fields.Field(FT, center_space), 1),
27+
Tuple{FT, FT},
28+
),
2529
temp_data = Fields.field_values(Fields.Field(FT, center_space)),
2630
temp_data_face_level = Fields.field_values(
2731
Fields.level(Fields.Field(FT, face_space), half),

src/parameterized_tendencies/microphysics/cloud_condensate.jl

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,70 @@ 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 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.ρn_liq, Y.c.ρ),
160+
Y.c.ρ,
161+
ᶜw,
162+
(cmc,),
163+
thp,
164+
ᶜts,
165+
dt,
166+
)
167+
# Use the Snₗ tendnecy at cloud base
168+
cloud_base_Snₗ_and_z = p.scratch.temp_tuple_field_level
169+
z = p.scratch.ᶜtemp_scalar
170+
z = Fields.coordinate_field(Snₗ).z
171+
find_cloud_base!(Snₗ, z, cloud_base_Snₗ_and_z)
172+
@. Snₗ = ifelse(z == last(cloud_base_Snₗ_and_z), Snₗ, FT(0))
173+
@. Yₜ.c.ρn_liq += Y.c.ρ * Snₗ
123174
end

src/parameterized_tendencies/microphysics/microphysics_wrappers.jl

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

33
import Thermodynamics as TD
4+
import ClimaCore.Operators as Operators
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
@@ -25,33 +28,6 @@ function limit(q, dt, n::Int)
2528
return max(FT(0), q) / float(dt) / n
2629
end
2730

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-
5531
"""
5632
cloud_sources(cm_params, thp, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Tₐ, dt)
5733
@@ -373,46 +349,129 @@ end
373349
#####
374350

375351
"""
376-
aerosol_activation_sources(cm_params, thp, ρ, Tₐ, qₜ, qₗ, qᵢ, qᵣ, qₛ, n_dp, n_dp_prescribed, dt)
352+
find_cloud_base!(Snₗ, z, cloud_base_Snₗ_and_z)
377353
378-
- cm_params - CloudMicrophysics parameters struct for cloud water or ice condensate
379-
- thp - Thermodynamics parameters struct
380-
- ρ - air density
381-
- Tₐ - air temperature
382-
- qₜ - total specific humidity
383-
- qₗ - liquid specific humidity
384-
- qᵢ - ice specific humidity
385-
- qᵣ - rain specific humidity
386-
- qₛ - snow specific humidity
387-
- n_dp - number concentration droplets (liquid or ice) per mass
388-
_ n_dp_prescribed - prescribed number concentration of droplets (liquid or ice) per mass
389-
- dt - model time step
354+
Finds the cloud base in each vertical column based on the first occurrence of aerosol activation.
355+
356+
This function performs a vertical column-wise reduction to identify the first level (from bottom to
357+
top) where `Snₗ > 0`, indicating the onset of cloud droplet activation (i.e., cloud base). The result
358+
is stored in `cloud_base_Snₗ_and_z` as a tuple `(Snₗ, z)` for each column.
359+
360+
# Arguments
361+
- `Snₗ`: Field of cloud droplet number concentration tendencies [m⁻³/s], from an activation parameterization.
362+
- `z`: Field of geometric height [m] corresponding to each grid point.
363+
- `cloud_base_Snₗ_and_z`: Output field to store the `(Snₗ, z)` values at cloud base for each column.
364+
"""
365+
function find_cloud_base!(Snₗ, z, cloud_base_Snₗ_and_z)
366+
Operators.column_reduce!(
367+
((target_Snₗ_value, target_z_value), (Snₗ_value, z_value)) -> ifelse(
368+
target_Snₗ_value == 0 && Snₗ_value > 0,
369+
(Snₗ_value, z_value),
370+
(target_Snₗ_value, target_z_value),
371+
), # reduction function
372+
cloud_base_Snₗ_and_z, # destination for output (a field of tuples)
373+
Base.broadcasted(tuple, Snₗ, z),
374+
)
375+
end
390376

391-
Returns the activation rate. #TODO This function temporarily computes activation rate
392-
based on mass rates and a prescribed droplet mass (no activation parameterization yet).
377+
"""
378+
aerosol_activation_sources(
379+
seasalt_num,
380+
seasalt_mean_radius,
381+
sulfate_num,
382+
nₗ,
383+
ρ,
384+
w,
385+
air_params,
386+
aerosol_params,
387+
arg_params,
388+
thermo_params,
389+
ts,
390+
dt,
391+
)
392+
393+
Computes the source term for cloud droplet number concentration per mass due to aerosol activation,
394+
based on the Abdul-Razzak and Ghan (2000) parameterization.
395+
396+
This function estimates the number of aerosols activated into cloud droplets per mass of air per second
397+
from a bi-modal aerosol distribution (sea salt and sulfate), given local supersaturation and vertical
398+
velocity. The result is returned as a tendency (per second) of liquid droplet number concentration.
399+
400+
# Arguments
401+
- `seasalt_num`: Number concentration per mass of sea salt aerosols [kg⁻¹].
402+
- `seasalt_mean_radius`: Mean dry radius of sea salt aerosol mode [m].
403+
- `sulfate_num`: Number concentration per mass of sulfate aerosols [kg⁻¹].
404+
- `nₗ`: Cloud droplet number concentration per mass [kg⁻¹].
405+
- `ρ`: Air density [kg/m³].
406+
- `w`: Vertical velocity [m/s].
407+
- `air_params`: Struct containing physical constants related to air.
408+
- `aerosol_params`: Struct containing aerosol properties, such as hygroscopicity parameters (κ).
409+
- `arg_params`: Activation scheme parameters specific to Abdul-Razzak and Ghan (2000).
410+
- `thermo_params`: Thermodynamic parameters for computing saturation, pressure, temperature, etc.
411+
- `ts`: Thermodynamic state (e.g., prognostic variables) used for evaluating the phase partition.
412+
- `dt`: Time step (s) over which the activation tendency is applied.
413+
414+
# Returns
415+
- Tendency of cloud droplet number concentration per mass of air due to aerosol activation [m⁻³/s].
393416
"""
394417
function aerosol_activation_sources(
395-
cm_params::CMP.CloudLiquid{FT},
396-
thp,
418+
seasalt_num,
419+
seasalt_mean_radius,
420+
sulfate_num,
421+
nₗ,
397422
ρ,
398-
Tₐ,
399-
qₜ,
400-
qₗ,
401-
qᵢ,
402-
qᵣ,
403-
qₛ,
404-
n_dp,
405-
n_dp_prescribed,
423+
w,
424+
cmc,
425+
thermo_params,
426+
ts,
406427
dt,
407-
) where {FT}
408-
r_dp = FT(2e-6) # 2 μm
409-
m_dp = 4 / 3 * FT(π) * r_dp^3 * cm_params.ρw
410-
Sn = cloud_sources(cm_params, thp, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Tₐ, dt) / m_dp
428+
)
429+
430+
FT = eltype(nₗ)
431+
air_params = cmc.aps
432+
arg_params = cmc.arg
433+
aerosol_params = cmc.aerosol
434+
q = TD.PhasePartition(thermo_params, ts)
435+
T = TD.air_temperature(thermo_params, ts)
436+
p = TD.air_pressure(thermo_params, ts)
437+
S::FT = TD.supersaturation(thermo_params, q, ρ, T, TD.Liquid())
438+
439+
seasalt_mode = CMAM.Mode_κ(
440+
seasalt_mean_radius,
441+
aerosol_params.seasalt_std,
442+
seasalt_num * ρ,
443+
(FT(1.0),),
444+
(FT(1.0),),
445+
(FT(0.0),),
446+
(aerosol_params.seasalt_kappa,),
447+
)
448+
sulfate_mode = CMAM.Mode_κ(
449+
aerosol_params.sulfate_radius,
450+
aerosol_params.sulfate_std,
451+
sulfate_num * ρ,
452+
(FT(1.0),),
453+
(FT(1.0),),
454+
(FT(0.0),),
455+
(aerosol_params.sulfate_kappa,),
456+
)
457+
458+
aerosol_dist = CMAM.AerosolDistribution((seasalt_mode, sulfate_mode))
459+
n_act::FT =
460+
CMAA.total_N_activated(
461+
arg_params,
462+
aerosol_dist,
463+
air_params,
464+
thermo_params,
465+
T,
466+
p,
467+
w,
468+
q,
469+
) / ρ
411470

412471
return ifelse(
413-
Sn > FT(0),
414-
triangle_inequality_limiter(Sn, limit((n_dp_prescribed - n_dp), dt, 2)),
415-
-triangle_inequality_limiter(abs(Sn), limit(n_dp, dt, 2)),
472+
S < 0 || isnan(n_act),
473+
FT(0),
474+
triangle_inequality_limiter(n_act, n_act - nₗ) / dt,
416475
)
417476
end
418477

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)