Skip to content

Commit

Permalink
add Frostenberg parametrization
Browse files Browse the repository at this point in the history
  • Loading branch information
AgnieszkaMakulska committed Feb 24, 2024
1 parent 63b4804 commit f04f5ff
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 7 deletions.
2 changes: 1 addition & 1 deletion docs/src/plots/Frostenberg_fig1.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ frequency = [
IN.INP_concentration_frequency(ip, INPC, T) : missing for
INPC in INPC_range, T in T_range_kelvin
]
mu = [exp(log(-(ip.b * T)^9 * 10^(-9))) for T in T_range_celsius]
mu = [exp(log(-(T)^9 * 10^(-9))) for T in T_range_celsius]


PL.contourf(
Expand Down
87 changes: 87 additions & 0 deletions parcel/Example_Frostenberg_Immersion_Freezing.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import OrdinaryDiffEq as ODE
import CairoMakie as MK
import Thermodynamics as TD
import CloudMicrophysics as CM
import CLIMAParameters as CP

# definition of the ODE problem for parcel model
include(joinpath(pkgdir(CM), "parcel", "Parcel.jl"))
FT = Float32
# get free parameters
tps = TD.Parameters.ThermodynamicsParameters(FT)
wps = CMP.WaterProperties(FT)

# Initial conditions
ρₗ = wps.ρw
Nₐ = FT(0)
Nₗ = FT(500 * 1e3)
Nᵢ = FT(0)
r₀ = FT(1e-6)
p₀ = FT(800 * 1e2)
T₀ = FT(251)
qᵥ = FT(8.1e-4)
qₗ = Nₗ * 4 / 3 * FT(π) * r₀^3 * ρₗ / FT(1.2) # 1.2 should be ρₐ
qᵢ = FT(0)
x_sulph = FT(0.01)

# Moisture dependent initial conditions
q = TD.PhasePartition.(qᵥ + qₗ + qᵢ, qₗ, qᵢ)
R_v = TD.Parameters.R_v(tps)
Rₐ = TD.gas_constant_air(tps, q)
eₛ = TD.saturation_vapor_pressure(tps, T₀, TD.Liquid())
e = eᵥ(qᵥ, p₀, Rₐ, R_v)
Sₗ = FT(e / eₛ)
IC = [Sₗ, p₀, T₀, qᵥ, qₗ, qᵢ, Nₐ, Nₗ, Nᵢ, x_sulph]

# Simulation parameters passed into ODE solver
w = FT(0.7) # updraft speed
const_dt = FT(1) # model timestep
t_max = FT(1200)
aerosol = CMP.Illite(FT)
heterogeneous = "Frostenberg_random"
condensation_growth = "Condensation"
deposition_growth = "Deposition"
DSD = "Monodisperse"

# Plotting
fig = MK.Figure(resolution = (800, 600))
ax1 = MK.Axis(fig[1, 1], ylabel = "Supersaturation [-]")
ax2 = MK.Axis(fig[1, 2], ylabel = "Temperature [K]")
ax3 = MK.Axis(fig[2, 1], ylabel = "q_ice [g/kg]")
ax4 = MK.Axis(fig[2, 2], ylabel = "q_liq [g/kg]")
ax5 = MK.Axis(fig[3, 1], xlabel = "Time [min]", ylabel = "N_liq")
ax6 = MK.Axis(fig[3, 2], xlabel = "Time [min]", ylabel = "N_ice")

params = parcel_params{FT}(
const_dt = const_dt,
w = w,
aerosol = aerosol,
heterogeneous = heterogeneous,
condensation_growth = condensation_growth,
deposition_growth = deposition_growth,
size_distribution = DSD,
)
# solve ODE
sol = run_parcel(IC, FT(0), t_max, params)

# Plot results
MK.lines!(ax1, sol.t / 60, sol[1, :] .-1, label = "S_liq")
MK.lines!(ax1, sol.t / 60, S_i.(tps, sol[3, :], sol[1, :]) .- 1, label = "S_ice")
MK.lines!(ax2, sol.t / 60, sol[3, :])
MK.lines!(ax3, sol.t / 60, sol[6, :] * 1e3)
MK.lines!(ax4, sol.t / 60, sol[5, :] * 1e3)
sol_Nₗ = sol[8, :]
sol_Nᵢ = sol[9, :]
MK.lines!(ax5, sol.t / 60, sol_Nₗ)
MK.lines!(ax6, sol.t / 60, sol_Nᵢ)

MK.axislegend(
ax1,
framevisible = false,
labelsize = 12,
orientation = :horizontal,
nbanks = 2,
position = :rt,
)

MK.save("frostenberg_immersion_freezing.svg", fig)
16 changes: 15 additions & 1 deletion parcel/ParcelModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Base.@kwdef struct parcel_params{FT} <: CMP.ParametersType{FT}
const_dt = 1
w = FT(1)
r_nuc = FT(0.5 * 1.e-4 * 1e-6)
drawing_interval = FT(1)
γ = FT(1)
ip = CMP.Frostenberg2023(FT)
end

"""
Expand Down Expand Up @@ -51,6 +54,7 @@ function parcel_model(dY, Y, p, t)
Nₗ = clip!(Y[8]),
Nᵢ = clip!(Y[9]),
xS = Y[10],
t = t,
)

# Constants
Expand All @@ -60,7 +64,7 @@ function parcel_model(dY, Y, p, t)
ρₗ = wps.ρw

# Get the state values
(; Sₗ, p_air, T, qᵥ, qₗ, qᵢ, Nₗ, Nᵢ) = state
(; Sₗ, p_air, T, qᵥ, qₗ, qᵢ, Nₗ, Nᵢ, t) = state
# Get thermodynamic parameters, phase partition and create thermo state.
q = TD.PhasePartition(qᵥ + qₗ + qᵢ, qₗ, qᵢ)
ts = TD.PhaseNonEquil_pTq(tps, p_air, T, q)
Expand Down Expand Up @@ -162,6 +166,7 @@ Parcel state vector (all variables are in base SI units):
- Nₗ - number concentration of existing water droplets
- Nᵢ - concentration of activated ice crystals
- xS - percent mass sulphuric acid
- t - time in seconds since the beginning of the simulation
The parcel parameters struct comes with default values that can be overwritten:
- deposition - string with deposition ice nucleation parameterization choice ["None" (default), "MohlerAF", "MohlerRate", "ActivityBased", "P3_dep"]
Expand All @@ -175,6 +180,9 @@ The parcel parameters struct comes with default values that can be overwritten:
- const_dt - model timestep [s]. Parcel model is using a simple Euler timestepper. Default value is 1 s
- w - parcel vertical velocity [m/s]. Default value is 1 m/s
- r_nuc - assumed size of nucleating ice crystals. Default value is 5e-11 [m]
- ip - parameters of INPC frequency distribution for Frostenberg parametrization of immerison freezing
- drawing_interval - time in seconds between random draws in Frostenberg_random parametrization of immerison freezing
- γ - timescale for the stochastic process in Frostenberg_stichastic parametrization of immerison freezing
"""
function run_parcel(IC, t_0, t_end, pp)

Expand Down Expand Up @@ -214,6 +222,12 @@ function run_parcel(IC, t_0, t_end, pp)
imm_params = ABIFM{FT}(pp.H₂SO₄ps, pp.tps, pp.aerosol)
elseif pp.heterogeneous == "P3_het"
imm_params = P3_het{FT}(pp.ips, pp.const_dt)
elseif pp.heterogeneous == "Frostenberg_random"
imm_params = Frostenberg_random{FT}(pp.ip, pp.drawing_interval)
elseif pp.heterogeneous == "Frostenberg_mean"
imm_params = Frostenberg_mean{FT}(pp.ip)
elseif pp.heterogeneous == "Frostenberg_stochastic"
imm_params = Frostenberg_stochastic{FT}(pp.ip, pp.γ)
else
throw("Unrecognized heterogeneous mode")
end
Expand Down
14 changes: 14 additions & 0 deletions parcel/ParcelParameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ struct P3_het{FT} <: CMP.ParametersType{FT}
const_dt::FT
end

struct Frostenberg_random{FT} <: CMP.ParametersType{FT}
ip::CMP.ParametersType{FT}
drawing_interval::FT
end

struct Frostenberg_stochastic{FT} <: CMP.ParametersType{FT}
ip::CMP.ParametersType{FT}
γ::FT
end

struct Frostenberg_mean{FT} <: CMP.ParametersType{FT}
ip::CMP.ParametersType{FT}
end

struct ABHOM{FT} <: CMP.ParametersType{FT}
tps::TDP.ThermodynamicsParameters{FT}
ips::CMP.ParametersType{FT}
Expand Down
32 changes: 32 additions & 0 deletions parcel/ParcelTendencies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CloudMicrophysics.Common as CMO
import CloudMicrophysics.HetIceNucleation as CMI_het
import CloudMicrophysics.HomIceNucleation as CMI_hom
import CloudMicrophysics.Parameters as CMP
using Distributions

function deposition_nucleation(::Empty, state, dY)
FT = eltype(state)
Expand Down Expand Up @@ -106,6 +107,37 @@ function immersion_freezing(params::P3_het, PSD, state)
return max(FT(0), (Nᵢ_het - Nᵢ) / const_dt)
end

function immersion_freezing(params::Frostenberg_random, PSD, state)
FT = eltype(state)
(; ip, drawing_interval) = params
(; T, Nₗ, Nᵢ, t) = state
if mod(t, drawing_interval) == 0
μ = CMI_het.INP_concentration_mean(T)
INPC = exp(rand(Normal(μ, ip.σ)))
end
return min(Nₗ, max(FT(0), INPC - Nᵢ))
end

function immersion_freezing(params::Frostenberg_mean, PSD, state)
FT = eltype(state)
(; ip) = params
(; T, Nₗ, Nᵢ) = state
INPC = exp(CMI_het.INP_concentration_mean(T))
return min(Nₗ, max(FT(0), INPC - Nᵢ))
end

function immersion_freezing(params::Frostenberg_stochastic, PSD, state)
FT = eltype(state)
(; ip, γ) = params
(; T, Nₗ, Nᵢ, t) = state
μ = CMI_het.INP_concentration_mean(T)
g = ip.σ * sqrt(2*γ)
mean = (1 - exp(-γ*t)) * μ
st_dev = sqrt( g^2 /(2*γ) * (1 - exp(-2*γ*t)) )
INPC = exp(rand(Normal(mean, st_dev)))
return min(Nₗ, max(FT(0), INPC - Nᵢ))
end

function homogeneous_freezing(::Empty, PSD, state)
FT = eltype(state)
return FT(0)
Expand Down
26 changes: 21 additions & 5 deletions src/IceNucleation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export ABIFM_J
export P3_deposition_N_i
export P3_het_N_i
export INP_concentration_frequency
export INP_concentration_mean

"""
dust_activated_number_fraction(dust, ip, Si, T)
Expand Down Expand Up @@ -165,7 +166,7 @@ function P3_het_N_i(
end

"""
INP_concentration_frequency(INPC,T)
INP_concentration_frequency(params,INPC,T)
- `params` - a struct with INPC(T) distribution parameters
- `INPC` - concentration of ice nucleating particles [m^-3]
Expand All @@ -181,12 +182,27 @@ function INP_concentration_frequency(
T::FT,
) where {FT}

T_celsius = T - 273.15
(; σ, a, b) = params
μ = INP_concentration_mean(T)

μ = log(-(b * T_celsius)^9 * 10^(-9))
return 1 / (sqrt(2 * FT(π)) * params.σ) * exp(-(log(INPC) - μ)^2 / (2 * params.σ^2))
end


"""
INP_concentration_mean(T)
- `T` - air temperature [K]
Returns the logarithm of mean INP concentration (in m^-3), depending on the temperature.
Based on the function μ(T) in Frostenberg et al., 2023. See DOI: 10.5194/acp-23-10883-2023
"""
function INP_concentration_mean(
T::FT,
) where {FT}

return 1 / (sqrt(2 * FT(π)) * σ) * exp(-(log(a * INPC) - μ)^2 / (2 * σ^2))
T_celsius = T - FT(273.15)

return log(-(T_celsius)^9 * 10^(-9))
end

end # end module
Expand Down

0 comments on commit f04f5ff

Please sign in to comment.