Skip to content

Commit a566dcd

Browse files
juliasloan25kmdeck
authored andcommittedOct 7, 2024
use SF.jl sensible heat flux
1 parent 951bfe5 commit a566dcd

File tree

4 files changed

+134
-77
lines changed

4 files changed

+134
-77
lines changed
 

‎src/shared_utilities/drivers.jl

+28-16
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ Computes turbulent surface fluxes at a point on a surface given
304304
305305
This returns an energy flux and a liquid water volume flux, stored in
306306
a tuple with self explanatory keys.
307+
308+
Please note that this function, if r_sfc is set to zero, makes no alteration
309+
to the output of the `SurfaceFluxes.surface_conditions` call, aside
310+
from unit conversion of evaporation from a mass to a liquid water volume
311+
flux.
307312
"""
308313
function turbulent_fluxes_at_a_point(
309314
T_sfc::FT,
@@ -341,7 +346,7 @@ function turbulent_fluxes_at_a_point(
341346
)
342347

343348
# State containers
344-
sc = SurfaceFluxes.ValuesOnly(
349+
states = SurfaceFluxes.ValuesOnly(
345350
state_in,
346351
state_sfc,
347352
z_0m,
@@ -350,26 +355,33 @@ function turbulent_fluxes_at_a_point(
350355
gustiness = gustiness,
351356
)
352357
surface_flux_params = LP.surface_fluxes_parameters(earth_param_set)
353-
conditions = SurfaceFluxes.surface_conditions(
354-
surface_flux_params,
355-
sc;
356-
tol_neutral = SFP.cp_d(surface_flux_params) / 100000,
357-
)
358+
scheme = SurfaceFluxes.PointValueScheme()
359+
conditions =
360+
SurfaceFluxes.surface_conditions(surface_flux_params, states, scheme)
358361
_LH_v0::FT = LP.LH_v0(earth_param_set)
359362
_ρ_liq::FT = LP.ρ_cloud_liq(earth_param_set)
360363

361-
cp_m::FT = Thermodynamics.cp_m(thermo_params, ts_in)
362-
T_in::FT = Thermodynamics.air_temperature(thermo_params, ts_in)
363-
ΔT = T_in - T_sfc
364-
r_ae::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(sc))
365-
ρ_air::FT = Thermodynamics.air_density(thermo_params, ts_in)
364+
# aerodynamic resistance
365+
r_ae::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(states))
366+
367+
# latent heat flux
368+
E0::FT =
369+
SurfaceFluxes.evaporation(surface_flux_params, states, conditions.Ch) # mass flux at potential evaporation rate
370+
E = E0 * r_ae / (r_sfc + r_ae) # mass flux accounting for additional surface resistance
371+
LH = _LH_v0 * E # Latent heat flux
366372

367-
E0::FT = SurfaceFluxes.evaporation(surface_flux_params, sc, conditions.Ch)
368-
E = E0 * r_ae / (r_sfc + r_ae)
373+
# sensible heat flux
374+
SH = SurfaceFluxes.sensible_heat_flux(
375+
surface_flux_params,
376+
conditions.Ch,
377+
states,
378+
scheme,
379+
)
380+
381+
# vapor flux in volume of liquid water with density 1000kg/m^3
369382
= E / _ρ_liq
370-
H = -ρ_air * cp_m * ΔT / r_ae
371-
LH = _LH_v0 * E
372-
return (lhf = LH, shf = H, vapor_flux = Ẽ, r_ae = r_ae)
383+
384+
return (lhf = LH, shf = SH, vapor_flux = Ẽ, r_ae = r_ae)
373385
end
374386

375387
"""

‎src/standalone/Soil/energy_hydrology.jl

+57-41
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,17 @@ Computes turbulent surface fluxes for soil at a point on a surface given
859859
860860
This returns an energy flux and a liquid water volume flux, stored in
861861
a tuple with self explanatory keys.
862+
863+
Notes:
864+
In this function, we call SF twice - once for ice, and once for water.
865+
We then combine the fluxes from them to get the total SH, LH.
866+
The vapor fluxes are applied to ice and water differently - as either a source term
867+
or a boundary condition- so they are kept separate.
868+
869+
For ice, we supply a beta factor and do not need an additional surface resistance,
870+
and so we use the output of surface_conditions directly.
871+
For water, we do, and it's a little complicated how we handle it.
872+
But once we correct E, we compute SH and LH the same as SurfaceFluxes.jl does.
862873
"""
863874
function soil_turbulent_fluxes_at_a_point(
864875
T_sfc::FT,
@@ -881,24 +892,31 @@ function soil_turbulent_fluxes_at_a_point(
881892
γT_ref::FT,
882893
earth_param_set::P,
883894
) where {FT <: AbstractFloat, P}
895+
# Parameters
896+
surface_flux_params = LP.surface_fluxes_parameters(earth_param_set)
884897
thermo_params = LP.thermodynamic_parameters(earth_param_set)
898+
_LH_v0::FT = LP.LH_v0(earth_param_set)
899+
_ρ_liq::FT = LP.ρ_cloud_liq(earth_param_set)
900+
901+
# Atmos air state
902+
state_air = SurfaceFluxes.StateValues(
903+
h_air - d_sfc - h_sfc,
904+
SVector{2, FT}(u_air, 0),
905+
thermal_state_air,
906+
)
907+
q_air::FT =
908+
Thermodynamics.total_specific_humidity(thermo_params, thermal_state_air)
909+
885910
# Estimate surface air density
886911
ρ_sfc::FT = ClimaLand.compute_ρ_sfc(thermo_params, thermal_state_air, T_sfc)
887-
# Compute saturated specific humidities at surface over ice and liquid water
888-
q_sat_ice::FT = Thermodynamics.q_vap_saturation_generic(
889-
thermo_params,
890-
T_sfc,
891-
ρ_sfc,
892-
Thermodynamics.Ice(),
893-
)
912+
913+
# Now compute surface fluxes from liquid water component:
894914
q_sat_liq::FT = Thermodynamics.q_vap_saturation_generic(
895915
thermo_params,
896916
T_sfc,
897917
ρ_sfc,
898918
Thermodynamics.Liquid(),
899919
)
900-
901-
# Evaporation:
902920
thermal_state_sfc::Thermodynamics.PhaseEquil{FT} =
903921
Thermodynamics.PhaseEquil_ρTq(thermo_params, ρ_sfc, T_sfc, q_sat_liq)# use to get potential evaporation E0, r_ae (weak dependence on q).
904922

@@ -916,12 +934,6 @@ function soil_turbulent_fluxes_at_a_point(
916934
SVector{2, FT}(0, 0),
917935
thermal_state_sfc,
918936
)
919-
state_air = SurfaceFluxes.StateValues(
920-
h_air - d_sfc - h_sfc,
921-
SVector{2, FT}(u_air, 0),
922-
thermal_state_air,
923-
)
924-
925937
# State containers
926938
states = SurfaceFluxes.ValuesOnly(
927939
state_air,
@@ -930,21 +942,9 @@ function soil_turbulent_fluxes_at_a_point(
930942
z_0b,
931943
gustiness = gustiness,
932944
)
933-
surface_flux_params = LP.surface_fluxes_parameters(earth_param_set)
934-
conditions = SurfaceFluxes.surface_conditions(
935-
surface_flux_params,
936-
states;
937-
tol_neutral = SFP.cp_d(surface_flux_params) / 100000,
938-
)
939-
_LH_v0::FT = LP.LH_v0(earth_param_set)
940-
_ρ_liq::FT = LP.ρ_cloud_liq(earth_param_set)
941-
cp_m::FT = Thermodynamics.cp_m(thermo_params, thermal_state_air)
942-
T_air::FT = Thermodynamics.air_temperature(thermo_params, thermal_state_air)
943-
ρ_air::FT = Thermodynamics.air_density(thermo_params, thermal_state_air)
944-
q_air::FT =
945-
Thermodynamics.total_specific_humidity(thermo_params, thermal_state_air)
946-
947-
ΔT::FT = T_air - T_sfc
945+
scheme = SurfaceFluxes.PointValueScheme()
946+
conditions =
947+
SurfaceFluxes.surface_conditions(surface_flux_params, states, scheme)
948948
r_ae_liq::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(states))
949949

950950
E0::FT =
@@ -970,9 +970,22 @@ function soil_turbulent_fluxes_at_a_point(
970970
else
971971
*= 0 # condensation, set to zero
972972
end
973-
H_liq::FT = -ρ_air * cp_m * ΔT / r_ae_liq
974973

975-
# Sublimation:
974+
# sensible heat flux
975+
SH_liq = SurfaceFluxes.sensible_heat_flux(
976+
surface_flux_params,
977+
conditions.Ch,
978+
states,
979+
scheme,
980+
)
981+
982+
# Repeat for ice component:
983+
q_sat_ice::FT = Thermodynamics.q_vap_saturation_generic(
984+
thermo_params,
985+
T_sfc,
986+
ρ_sfc,
987+
Thermodynamics.Ice(),
988+
)
976989
thermal_state_sfc =
977990
Thermodynamics.PhaseEquil_ρTq(thermo_params, ρ_sfc, T_sfc, q_sat_ice)
978991
β_i::FT = FT(1)
@@ -996,25 +1009,28 @@ function soil_turbulent_fluxes_at_a_point(
9961009
beta = β_i,
9971010
gustiness = gustiness,
9981011
)
999-
conditions = SurfaceFluxes.surface_conditions(
1000-
surface_flux_params,
1001-
states;
1002-
tol_neutral = SFP.cp_d(surface_flux_params) / 100000,
1003-
)
1012+
conditions =
1013+
SurfaceFluxes.surface_conditions(surface_flux_params, states, scheme)
10041014
S::FT =
10051015
SurfaceFluxes.evaporation(surface_flux_params, states, conditions.Ch)# sublimation rate, mass flux
10061016
::FT = S / _ρ_liq
10071017

10081018
r_ae_ice::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(states))
1009-
H_ice::FT = -ρ_air * cp_m * ΔT / r_ae_ice
1019+
# sensible heat flux from ice
1020+
SH_ice = SurfaceFluxes.sensible_heat_flux(
1021+
surface_flux_params,
1022+
conditions.Ch,
1023+
states,
1024+
scheme,
1025+
)
10101026

1011-
# Heat fluxes
1027+
# Heat fluxes for soil
10121028
LH::FT = _LH_v0 * (Ẽ + S̃) * _ρ_liq
1013-
H::FT = (H_ice + H_liq) / 2
1029+
SH::FT = (SH_ice + SH_liq) / 2
10141030
r_ae::FT = 2 * r_ae_liq * r_ae_ice / (r_ae_liq + r_ae_ice) # implied by definition of H
10151031
return (
10161032
lhf = LH,
1017-
shf = H,
1033+
shf = SH,
10181034
vapor_flux_liq = Ẽ,
10191035
r_ae = r_ae,
10201036
vapor_flux_ice = S̃,

‎src/standalone/Vegetation/canopy_boundary_fluxes.jl

+47-18
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ end
286286
287287
Computes the turbulent surface fluxes for the canopy at a point
288288
and returns the fluxes in a named tuple.
289+
290+
Note that an additiontal resistance is used in computing both
291+
evaporation and sensible heat flux, and this modifies the output
292+
of `SurfaceFluxes.surface_conditions`.
289293
"""
290294
function canopy_turbulent_fluxes_at_a_point(
291295
T_sfc::FT,
@@ -315,7 +319,7 @@ function canopy_turbulent_fluxes_at_a_point(
315319
)
316320

317321
# State containers
318-
sc = SurfaceFluxes.ValuesOnly(
322+
states = SurfaceFluxes.ValuesOnly(
319323
state_in,
320324
state_sfc,
321325
z_0m,
@@ -324,37 +328,62 @@ function canopy_turbulent_fluxes_at_a_point(
324328
gustiness = gustiness,
325329
)
326330
surface_flux_params = LP.surface_fluxes_parameters(earth_param_set)
327-
conditions = SurfaceFluxes.surface_conditions(
328-
surface_flux_params,
329-
sc;
330-
tol_neutral = SFP.cp_d(surface_flux_params) / 100000,
331-
)
331+
scheme = SurfaceFluxes.PointValueScheme()
332+
conditions =
333+
SurfaceFluxes.surface_conditions(surface_flux_params, states, scheme)
332334
_LH_v0::FT = LP.LH_v0(earth_param_set)
333335
_ρ_liq::FT = LP.ρ_cloud_liq(earth_param_set)
334-
335-
cp_m::FT = Thermodynamics.cp_m(thermo_params, ts_in)
336-
T_in::FT = Thermodynamics.air_temperature(thermo_params, ts_in)
337-
ΔT = T_in - T_sfc
338-
r_ae::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(sc))
339-
ρ_sfc::FT = Thermodynamics.air_density(thermo_params, ts_sfc)
336+
cp_m_sfc::FT = Thermodynamics.cp_m(thermo_params, ts_sfc)
337+
r_ae::FT = 1 / (conditions.Ch * SurfaceFluxes.windspeed(states))
340338
ustar::FT = conditions.ustar
339+
ρ_sfc = Thermodynamics.air_density(thermo_params, ts_sfc)
340+
T_int = Thermodynamics.air_temperature(thermo_params, ts_in)
341+
Rm_int = Thermodynamics.gas_constant_air(thermo_params, ts_in)
342+
ρ_air = Thermodynamics.air_density(thermo_params, ts_in)
343+
341344
r_b::FT = FT(1 / 0.01 * (ustar / 0.04)^(-1 / 2)) # CLM 5, tech note Equation 5.122
342345
leaf_r_b = r_b / LAI
343346
canopy_r_b = r_b / (LAI + SAI)
344-
E0::FT = SurfaceFluxes.evaporation(surface_flux_params, sc, conditions.Ch)
347+
348+
E0::FT =
349+
SurfaceFluxes.evaporation(surface_flux_params, states, conditions.Ch)
345350
E = E0 * r_ae / (leaf_r_b + leaf_r_stomata + r_ae) # CLM 5, tech note Equation 5.101, and fig 5.2b, assuming all sunlit, f_wet = 0
346351
= E / _ρ_liq
347-
H = -ρ_sfc * cp_m * ΔT / (canopy_r_b + r_ae) # CLM 5, tech note Equation 5.88, setting H_v = H and solving to remove T_s
352+
353+
SH =
354+
SurfaceFluxes.sensible_heat_flux(
355+
surface_flux_params,
356+
conditions.Ch,
357+
states,
358+
scheme,
359+
) * r_ae / (canopy_r_b + r_ae)
360+
# The above follows from CLM 5, tech note Equation 5.88, setting H_v = SH and solving to remove T_s, ignoring difference between cp in atmos and above canopy.
348361
LH = _LH_v0 * E
349-
# We ignore ∂r_ae/∂T_sfc, ∂u*/∂T_sfc
350-
∂LHF∂qc = ρ_sfc * _LH_v0 / (leaf_r_b + leaf_r_stomata + r_ae) # Note that our estimate of ρ_sfc depends on T_sfc, but we ignore the derivative here.
351-
∂SHF∂Tc = ρ_sfc * cp_m / (canopy_r_b + r_ae) # Same approximation re: ρ_sfc(T_sfc)
362+
363+
# Derivatives
364+
# We ignore ∂r_ae/∂T_sfc, ∂u*/∂T_sfc, ∂r_stomata∂Tc
365+
∂ρsfc∂Tc =
366+
ρ_air *
367+
(Thermodynamics.cv_m(thermo_params, ts_in) / Rm_int) *
368+
(T_sfc / T_int)^(Thermodynamics.cv_m(thermo_params, ts_in) / Rm_int - 1) /
369+
T_int
370+
∂cp_m_sfc∂Tc = 0 # Possibly can address at a later date
371+
372+
∂LHF∂qc =
373+
ρ_sfc * _LH_v0 / (leaf_r_b + leaf_r_stomata + r_ae) +
374+
LH / ρ_sfc * ∂ρsfc∂Tc
375+
376+
∂SHF∂Tc =
377+
ρ_sfc * cp_m_sfc / (canopy_r_b + r_ae) +
378+
SH / ρ_sfc * ∂ρsfc∂Tc +
379+
SH / cp_m_sfc * ∂cp_m_sfc∂Tc
352380
return (
353381
lhf = LH,
354-
shf = H,
382+
shf = SH,
355383
vapor_flux = Ẽ,
356384
r_ae = r_ae,
357385
∂LHF∂qc = ∂LHF∂qc,
358386
∂SHF∂Tc = ∂SHF∂Tc,
359387
)
388+
360389
end

‎test/standalone/Vegetation/canopy_model.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1070,12 +1070,12 @@ end
10701070
finitediff_SHF = (p_2.canopy.energy.shf .- p.canopy.energy.shf) ./ ΔT
10711071
estimated_SHF = p.canopy.energy.∂SHF∂Tc
10721072
@test parent(abs.(finitediff_SHF .- estimated_SHF) ./ finitediff_SHF)[1] <
1073-
0.05
1073+
0.15
10741074

10751075
finitediff_LHF = (p_2.canopy.energy.lhf .- p.canopy.energy.lhf) ./ ΔT
10761076
estimated_LHF = p.canopy.energy.∂LHF∂qc .* p.canopy.energy.∂qc∂Tc
10771077
@test parent(abs.(finitediff_LHF .- estimated_LHF) ./ finitediff_LHF)[1] <
1078-
0.5
1078+
0.3
10791079

10801080
# Error in `q` derivative is large
10811081
finitediff_q = (q_sfc2 .- q_sfc) ./ ΔT

0 commit comments

Comments
 (0)