Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaporation scheme described in the paper #1023

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kmdeck
Copy link
Member

@kmdeck kmdeck commented Feb 10, 2025

Purpose

Adds the evaporation scheme from the paper.

Equations

in turbulent fluxes, surface specific humidity, and ice fraction functions:
image

soil resistance:
image
image
image

Comparison to data

Plot produced by docs/tutorials/standalone/Soil/evaporation.jl
evaporation_lehmann2008_fig8b

To-do

Look at long runs

@kmdeck kmdeck requested a review from juliasloan25 February 11, 2025 00:03
@kmdeck
Copy link
Member Author

kmdeck commented Feb 11, 2025

Purpose

Adds the evaporation scheme from the paper.

Adds in the previous parameterization that we were using. In the past, we thought this had NaNs, so we switched away from it - revisit now that other parts of the model have stabilized.

To-do

Look at long runs

@kmdeck kmdeck closed this Feb 11, 2025
@kmdeck kmdeck reopened this Feb 11, 2025
# it instead allocated and returned the field.
# This needs to be updated everywhere, and then these functions
# can become e.g. surface_specific_humidity!(p.soil.q_sfc, ...)
ClimaLand.surface_specific_humidity(model, Y, p, T_sfc, ρ_sfc)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also updates p.soil.ice_frac in place, and that seems confusing. I will move this into a different function and make it obvious (but am going to let the current long run keep going)

@@ -960,18 +955,8 @@ function soil_turbulent_fluxes_at_a_point(
q_air::FT =
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quite a large diff - may be better to look at file directly?

porous media, Geophys. Res. Lett., 35, L19407, doi:10.1029/
2008GL035230.
"""
function soil_tortuosity(θ_l::FT, θ_i::FT, ν::FT) where {FT}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had previously implement a lot of these equations, commented them out, and this adds them back to soil_hydrology_parameterizations.

@kmdeck kmdeck requested a review from Sbozzolo February 11, 2025 00:22
@kmdeck
Copy link
Member Author

kmdeck commented Feb 11, 2025

Pre-turbulent fluxes
Set surface resistance (r_soil) and q_soil:

ClimaLand.surface_specific_humidity(model, Y, p, T_sfc, ρ_sfc)
q_sfc = p.soil.q_sfc
ClimaLand.surface_resistance(model, Y, p, t)
r_sfc = p.soil.r_sfc

As noted, the call to q_soil also updates ice_frac (I will change this to be explicit):

@. ice_frac = ice_fraction(θ_l_sfc, θ_i_sfc, ν_sfc, θ_r_sfc, _ρ_l, _ρ_i)

This uses Equation 74,

q_soiil, Equation 73:

@. p.soil.q_sfc =
(1 - ice_frac) *
Thermodynamics.q_vap_saturation_generic(
thermo_params,
T_sfc,
ρ_sfc,
Thermodynamics.Liquid(),
) *
exp(g * ψ_sfc * M_w / (R * T_sfc)) +
ice_frac * Thermodynamics.q_vap_saturation_generic(
thermo_params,
T_sfc,
ρ_sfc,
Thermodynamics.Ice(),
)

Within turbulent fluxes:

Equation 65:

r_ae::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(states)) # aerodynamic resistance

Equation 66
Equation 67
E::FT = E0 * r_ae / (r_ae + r_sfc) # convert from potential rate to actual rate

Both have q_0 = q_soil, the latter has r_0 = r_soil.

Equations 75/76:

Ẽ_l = E * (1 - ice_frac) / _ρ_liq # need a volume flux
Ẽ_i = E * ice_frac / _ρ_liq # need a volume flux

Soil resistance Equation 79:

Branch of 79:

if q_air > q_sfc # condensation shouldnt require soil resistance term
r_sfc *= 0
end

Equation 82:

dsl::FT = dry_soil_layer_thickness(S_w, S_c, d_ds)

Equation 80, with a worse name:

r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) *- 2 * (θ_safe)^(1 / 2))

Equation 81 + top branch of 79:

r_soil = dsl / (_D_vapor * τ_a) + factor * r_shell# [s\m]

Copy link
Member

@juliasloan25 juliasloan25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! I just had a couple software-related comments

Comment on lines +95 to +96
vg_n = FT(8.91)#FT(6.34) optimized values by root finding
vg_α = FT(3) #FT(7.339) optimized values by root finding
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete commented out values

# it instead allocated and returned the field.
# This needs to be updated everywhere, and then these functions
# can become e.g. surface_specific_humidity!(p.soil.q_sfc, ...)
ClimaLand.surface_specific_humidity(model, Y, p, T_sfc, ρ_sfc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ClimaLand.surface_specific_humidity(model, Y, p, T_sfc, ρ_sfc)
ClimaLand.surface_specific_humidity!(model, Y, p, T_sfc, ρ_sfc)

This function should have a ! since it's modifying p in-place

# can become e.g. surface_specific_humidity!(p.soil.q_sfc, ...)
ClimaLand.surface_specific_humidity(model, Y, p, T_sfc, ρ_sfc)
q_sfc = p.soil.q_sfc
ClimaLand.surface_resistance(model, Y, p, t)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ClimaLand.surface_resistance(model, Y, p, t)
ClimaLand.surface_resistance!(model, Y, p, t)

here too (modifies p.soil.r_sfc)

@@ -822,7 +822,6 @@ function ClimaLand.get_drivers(model::EnergyHydrology)
end



function turbulent_fluxes!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add a docstring for this function please?

return S_w < S_c ? d_ds * (S_c - S_w) / S_c : FT(0)
ice_frac = p.soil.ice_frac
@. ice_frac = ice_fraction(θ_l_sfc, θ_i_sfc, ν_sfc, θ_r_sfc, _ρ_l, _ρ_i)
@. p.soil.q_sfc =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function should be surface_specific_humidity because it's modifying p

) where {FT <: AbstractFloat, C, EP}

Computes turbulent surface fluxes for soil at a point on a surface given
(1) Surface state conditions (`T_sfc`, `θ_l_sfc`, `θ_i_sfc`)
(1) Surface state conditions (`T_sfc`, `q_sfc`, `ρ_sfc`, `ice_frac`)
(2) Surface properties, such as the topographical height of the surface (`h_sfc`),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point (3) is missing

Comment on lines +1125 to +1129
ice_frac * Thermodynamics.q_vap_saturation_generic(
thermo_params,
T_sfc,
ρ_sfc,
Thermodynamics.Ice(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, but you could collect Thermodynamics.q_vap_saturation_generic in this expression to compute it only once

Computes the resistance of the top of the soil column to
water vapor diffusion, as a function of the surface
volumetric liquid water fraction `θ_l`, the augmented
liquid water fraction `ϑ_l`, the volumetric ice water
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to make sure all the function parameters are clearly identified in the docstring. For example, I don't understand how ϑ_l enters here, or what d_ds is.

dsl::FT = dry_soil_layer_thickness(S_w, S_c, d_ds)
r_pore::FT = 2 * _σ * α / _ρ_liq / _grav
θ_safe = max(eps(FT), (θ_i + θ_l - θ_r))
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) * (π - 2 * (θ_safe)^(1 / 2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) *- 2 * (θ_safe)^(1 / 2))
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) *- 2 * sqrt(θ_safe))

Floating point exponents can be much more computationally expensive

θ_safe = max(eps(FT), (θ_i + θ_l - θ_r))
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) * (π - 2 * (θ_safe)^(1 / 2))
# This factor just damps r_shell to zero more quickly as x -> 0. numerical.
x = θ_safe / FT(0.001)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
x = θ_safe / FT(0.001)
x = θ_safe / 1_000

potentially avoids a conversion and better preserves accuracy

"""
dry_soil_layer_thickness(S_w::FT, S_c::FT, d_ds::FT)::FT where {FT}

Returns the maximum dry soil layer thickness that can develop under vapor flux;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to clearly identify the physical meaning of the input parameters

Comment on lines +400 to +409
"""
ice_fraction(θ_l::FT, θ_i::FT, ν::FT, θ_r::FT, ρ_l::FT, ρ_i::FT)::FT
where {FT}

Computes and returns the
fraction of the humidity in the pore space
that is due to sublimation.

f = θ_iρ_i/(θ_iρ_i+(θ_l-θ_r)ρ_l)
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""
ice_fraction(θ_l::FT, θ_i::FT, ν::FT, θ_r::FT, ρ_l::FT, ρ_i::FT)::FT where {FT}

Return the fraction of the humidity in the pore space that is due to sublimation.

$$f = \\frac{θ_i ρ_i}{θ_i ρ_i + (θ_l - θ_r) ρ_l}$$

"""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't render well on github, but you can add a math block so that it renders as equation int he docs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```math
f = \frac{θ_i ρ_i}{θ_i ρ_i + (θ_l - θ_r) ρ_l}
```

Comment on lines +400 to +409
"""
ice_fraction(θ_l::FT, θ_i::FT, ν::FT, θ_r::FT, ρ_l::FT, ρ_i::FT)::FT
where {FT}

Computes and returns the
fraction of the humidity in the pore space
that is due to sublimation.

f = θ_iρ_i/(θ_iρ_i+(θ_l-θ_r)ρ_l)
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't render well on github, but you can add a math block so that it renders as equation int he docs

Comment on lines +400 to +409
"""
ice_fraction(θ_l::FT, θ_i::FT, ν::FT, θ_r::FT, ρ_l::FT, ρ_i::FT)::FT
where {FT}

Computes and returns the
fraction of the humidity in the pore space
that is due to sublimation.

f = θ_iρ_i/(θ_iρ_i+(θ_l-θ_r)ρ_l)
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```math
f = \frac{θ_i ρ_i}{θ_i ρ_i + (θ_l - θ_r) ρ_l}
```

dsl::FT = dry_soil_layer_thickness(S_w, S_c, d_ds)
r_pore::FT = 2 * _σ * α / _ρ_liq / _grav
θ_safe = max(eps(FT), (θ_i + θ_l - θ_r))
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) * (π - 2 * (θ_safe)^(1 / 2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
r_shell::FT = r_pore / _D_vapor / (4 * θ_safe) *- 2 * (θ_safe)^(1 / 2))
r_shell::FT = r_pore / (4 * _D_vapor * θ_safe) *- 2 * (θ_safe)^(1 / 2))

S_w = effective_saturation(ν, θ_l + θ_i, θ_r)
τ_a = soil_tortuosity(θ_l, θ_i, ν)
dsl::FT = dry_soil_layer_thickness(S_w, S_c, d_ds)
r_pore::FT = 2 * _σ * α / _ρ_liq / _grav
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
r_pore::FT = 2 ** α / _ρ_liq / _grav
r_pore::FT = 2 ** α / (_ρ_liq * _grav)

_D_vapor = LP.D_vapor(earth_param_set)
_ρ_liq = LP.ρ_cloud_liq(earth_param_set)
_grav = LP.grav(earth_param_set)
_σ = FT(7.2e-2) # need to add to CP, surface tension of water N/m
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this if there's already a Stafan-Boltzmann constant?

"""
function soil_tortuosity(θ_l::FT, θ_i::FT, ν::FT) where {FT}
safe_θ_a = max(ν - θ_l - θ_i, eps(FT))
return safe_θ_a^(FT(2.5)) / ν
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return safe_θ_a^(FT(2.5)) / ν
return sqrt(safe_θ_a^5) / ν

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants