Skip to content

Commit 9352716

Browse files
authored
add energy flux of infiltration to soil (#1164)
1 parent dc73dff commit 9352716

File tree

13 files changed

+492
-130
lines changed

13 files changed

+492
-130
lines changed

docs/src/APIs/Snow.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ ClimaLand.Snow.compute_water_runoff
2323
ClimaLand.Snow.energy_from_q_l_and_swe
2424
ClimaLand.Snow.energy_from_T_and_swe
2525
ClimaLand.Snow.snow_cover_fraction
26-
ClimaLand.Snow.volumetric_energy_flux_falling_rain
27-
ClimaLand.Snow.volumetric_energy_flux_falling_snow
26+
ClimaLand.Snow.energy_flux_falling_rain
27+
ClimaLand.Snow.energy_flux_falling_snow
2828
```
2929

3030
## Computing fluxes for snow

experiments/integrated/fluxnet/fluxnet_simulation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ timestepper = CTS.ARS111();
1313
ode_algo = CTS.IMEXAlgorithm(
1414
timestepper,
1515
CTS.NewtonsMethod(
16-
max_iters = 6,
16+
max_iters = 3,
1717
update_j = CTS.UpdateEvery(CTS.NewNewtonIteration),
1818
),
1919
);

experiments/integrated/fluxnet/snow_soil/simulation.jl

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -253,27 +253,28 @@ _ρ_i = FT(LP.ρ_cloud_ice(earth_param_set))
253253
_ρ_l = FT(LP.ρ_cloud_liq(earth_param_set))
254254
fig = Figure(size = (1600, 1200), fontsize = 26)
255255
ax1 = Axis(fig[2, 1], ylabel = "ΔEnergy (J/A)", xlabel = "Days")
256-
function compute_surface_energy_fluxes(p)
257-
ρe_flux_falling_snow =
258-
Snow.volumetric_energy_flux_falling_snow(atmos, p, land.snow.parameters)
259-
ρe_flux_falling_rain =
260-
Snow.volumetric_energy_flux_falling_rain(atmos, p, land.snow.parameters)
256+
function compute_atmos_energy_fluxes(p)
257+
e_flux_falling_snow =
258+
Snow.energy_flux_falling_snow(atmos, p, land.snow.parameters)
259+
e_flux_falling_rain =
260+
Snow.energy_flux_falling_rain(atmos, p, land.snow.parameters)
261261

262262
return @. (1 - p.snow.snow_cover_fraction) * (
263263
p.soil.turbulent_fluxes.lhf +
264264
p.soil.turbulent_fluxes.shf +
265-
p.soil.R_n
265+
p.soil.R_n +
266+
e_flux_falling_rain
266267
) +
267268
p.snow.snow_cover_fraction * (
268269
p.snow.turbulent_fluxes.lhf +
269270
p.snow.turbulent_fluxes.shf +
270271
p.snow.R_n +
271-
ρe_flux_falling_rain
272+
e_flux_falling_rain
272273
) +
273-
ρe_flux_falling_snow
274+
e_flux_falling_snow
274275
end
275276

276-
function compute_surface_water_vol_fluxes(p)
277+
function compute_atmos_water_vol_fluxes(p)
277278
return @. p.drivers.P_snow +
278279
p.drivers.P_liq +
279280
(1 - p.snow.snow_cover_fraction) * (
@@ -283,11 +284,37 @@ function compute_surface_water_vol_fluxes(p)
283284
p.snow.snow_cover_fraction * p.snow.turbulent_fluxes.vapor_flux
284285
end
285286

287+
function compute_energy_of_runoff(p)
288+
liquid_influx = @. p.snow.water_runoff * p.snow.snow_cover_fraction +
289+
(1 - p.snow.snow_cover_fraction) * p.drivers.P_liq
290+
e_flux_falling_rain =
291+
Soil.volumetric_internal_energy_liq.(p.drivers.T, earth_param_set) .*
292+
p.drivers.P_liq
293+
influx_energy = @. p.snow.energy_runoff * p.snow.snow_cover_fraction +
294+
(1 - p.snow.snow_cover_fraction) * e_flux_falling_rain
295+
runoff_fraction = @. 1 - ClimaLand.Soil.compute_infiltration_fraction(
296+
p.soil.infiltration,
297+
liquid_influx,
298+
)
299+
return runoff_fraction .* influx_energy
300+
end
301+
302+
function compute_runoff(p)
303+
liquid_influx = @. p.snow.water_runoff * p.snow.snow_cover_fraction +
304+
(1 - p.snow.snow_cover_fraction) * p.drivers.P_liq
305+
runoff_fraction = @. 1 - ClimaLand.Soil.compute_infiltration_fraction(
306+
p.soil.infiltration,
307+
liquid_influx,
308+
)
309+
return runoff_fraction .* liquid_influx
310+
end
311+
286312
ΔE_expected =
287313
cumsum(
288314
-1 .* [
289315
parent(
290-
compute_surface_energy_fluxes(sv.saveval[k]) .-
316+
compute_atmos_energy_fluxes(sv.saveval[k]) #.- compute_energy_of_runoff(sv.saveval[k])
317+
.-
291318
sv.saveval[k].soil.bottom_bc.heat,
292319
)[end] for k in 1:1:(length(sv.t) - 1)
293320
],
@@ -300,8 +327,9 @@ E_measured = [
300327
cumsum(
301328
-1 .* [
302329
parent(
303-
compute_surface_water_vol_fluxes(sv.saveval[k]) .-
304-
sv.saveval[k].soil.bottom_bc.water .+ sv.saveval[k].soil.R_s,
330+
compute_atmos_water_vol_fluxes(sv.saveval[k]) .-
331+
compute_runoff(sv.saveval[k]) .-
332+
sv.saveval[k].soil.bottom_bc.water,
305333
)[end] for k in 1:1:(length(sv.t) - 1)
306334
],
307335
) * (sv.t[2] - sv.t[1])
@@ -332,7 +360,6 @@ lines!(
332360
lines!(ax4, daily[2:end], ΔW_expected, label = "Expected")
333361
axislegend(ax4, position = :rt)
334362

335-
336363
ax3 = Axis(fig[2, 2], ylabel = "ΔE/E", xlabel = "Days", yscale = log10)
337364
lines!(
338365
ax3,

src/ClimaLand.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,11 @@ using .Pond
391391
import .Pond: surface_runoff
392392
include("standalone/Soil/Soil.jl")
393393
using .Soil
394-
import .Soil: soil_boundary_fluxes!, sublimation_source
394+
import .Soil:
395+
soil_boundary_fluxes!,
396+
sublimation_source,
397+
compute_liquid_influx,
398+
compute_infiltration_energy_flux
395399
import .Soil.Biogeochemistry: soil_temperature, soil_moisture
396400
include("standalone/Snow/Snow.jl")
397401
using .Snow

src/integrated/land.jl

Lines changed: 97 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -479,38 +479,113 @@ the presence of the canopy modifies the soil BC.
479479
function soil_boundary_fluxes!(
480480
bc::AtmosDrivenFluxBC,
481481
prognostic_land_components::Val{(:canopy, :snow, :soil, :soilco2)},
482-
soil::EnergyHydrology{FT},
482+
soil::EnergyHydrology,
483483
Y,
484484
p,
485485
t,
486-
) where {FT}
486+
)
487487
turbulent_fluxes!(p.soil.turbulent_fluxes, bc.atmos, soil, Y, p, t)
488-
# influx = maximum possible rate of infiltration given precip, snowmelt, evaporation/condensation
489-
# but if this exceeds infiltration capacity of the soil, runoff will
490-
# be generated.
491-
# Use top_bc.water as temporary storage to avoid allocation
492-
influx = p.soil.top_bc.water
493-
@. influx =
494-
p.snow.water_runoff * p.snow.snow_cover_fraction +
488+
# Liquid influx is a combination of precipitation and snowmelt in general
489+
liquid_influx =
490+
Soil.compute_liquid_influx(p, soil, prognostic_land_components)
491+
# This partitions the influx into runoff and infiltration
492+
Soil.update_infiltration_water_flux!(
493+
p,
494+
bc.runoff,
495+
liquid_influx,
496+
Y,
497+
t,
498+
soil,
499+
)
500+
# This computes the energy of the infiltrating water
501+
infiltration_energy_flux = Soil.compute_infiltration_energy_flux(
502+
p,
503+
bc.runoff,
504+
bc.atmos,
505+
prognostic_land_components,
506+
liquid_influx,
507+
soil,
508+
Y,
509+
t,
510+
)
511+
@. p.soil.top_bc.water =
512+
p.soil.infiltration +
495513
p.excess_water_flux +
496514
(1 - p.snow.snow_cover_fraction) *
497-
(p.drivers.P_liq + p.soil.turbulent_fluxes.vapor_flux_liq)
498-
# The update_runoff! function computes how much actually infiltrates
499-
# given influx and our runoff model bc.runoff, and updates
500-
# p.soil.infiltration in place
501-
Soil.Runoff.update_runoff!(p, bc.runoff, influx, Y, t, soil)
502-
@. p.soil.top_bc.water = p.soil.infiltration
515+
p.soil.turbulent_fluxes.vapor_flux_liq
516+
# The actual boundary condition is a mix of liquid water infiltration and
517+
# evaporation. The infiltration already has accounted for snow cover fraction,
518+
# because the influx it is computed from has accounted for that.
519+
# The last term, `excess water flux`, arises when snow melts in a timestep but
520+
# has a nonzero sublimation which was applied for the entire step.
503521
@. p.soil.top_bc.heat =
504522
(1 - p.snow.snow_cover_fraction) * (
505523
p.soil.R_n +
506524
p.soil.turbulent_fluxes.lhf +
507525
p.soil.turbulent_fluxes.shf
508526
) +
509527
p.excess_heat_flux +
510-
p.snow.snow_cover_fraction * p.ground_heat_flux
528+
p.snow.snow_cover_fraction * p.ground_heat_flux +
529+
infiltration_energy_flux
530+
511531
return nothing
512532
end
513533

534+
"""
535+
compute_liquid_influx(p,
536+
model,
537+
prognostic_land_components::Val{(:canopy, :snow, :soil, :soilco2)},
538+
)
539+
540+
Returns the liquid water volume flux at the surface of the soil; uses
541+
the same method as the soil+snow integrated model.
542+
"""
543+
function Soil.compute_liquid_influx(
544+
p,
545+
model,
546+
prognostic_land_components::Val{(:canopy, :snow, :soil, :soilco2)},
547+
)
548+
Soil.compute_liquid_influx(p, model, Val((:snow, :soil)))
549+
end
550+
551+
"""
552+
compute_infiltration_energy_flux(
553+
p,
554+
runoff,
555+
atmos,
556+
prognostic_land_components:::Val{(:canopy, :snow, :soil, :soilco2)},
557+
liquid_influx,
558+
model::EnergyHydrology,
559+
Y,
560+
t,
561+
)
562+
563+
Computes the energy associated with infiltration of
564+
liquid water into the soil; uses the same method as
565+
the soil+snow integrated model.
566+
"""
567+
function Soil.compute_infiltration_energy_flux(
568+
p,
569+
runoff,
570+
atmos,
571+
prognostic_land_components::Val{(:canopy, :snow, :soil, :soilco2)},
572+
liquid_influx,
573+
model::EnergyHydrology,
574+
Y,
575+
t,
576+
)
577+
Soil.compute_infiltration_energy_flux(
578+
p,
579+
runoff,
580+
atmos,
581+
Val((:snow, :soil)),
582+
liquid_influx,
583+
model,
584+
Y,
585+
t,
586+
)
587+
end
588+
514589
function snow_boundary_fluxes!(
515590
bc::Snow.AtmosDrivenSnowBC,
516591
prognostic_land_components::Val{(:canopy, :snow, :soil, :soilco2)},
@@ -535,19 +610,19 @@ function snow_boundary_fluxes!(
535610
p.snow.water_runoff
536611
) * p.snow.snow_cover_fraction
537612

538-
ρe_flux_falling_snow =
539-
Snow.volumetric_energy_flux_falling_snow(bc.atmos, p, model.parameters)
540-
ρe_flux_falling_rain =
541-
Snow.volumetric_energy_flux_falling_rain(bc.atmos, p, model.parameters)
613+
e_flux_falling_snow =
614+
Snow.energy_flux_falling_snow(bc.atmos, p, model.parameters)
615+
e_flux_falling_rain =
616+
Snow.energy_flux_falling_rain(bc.atmos, p, model.parameters)
542617

543618
# positive fluxes are TOWARDS atmos, but R_n positive if snow absorbs energy
544619
@. p.snow.total_energy_flux =
545-
ρe_flux_falling_snow +
620+
e_flux_falling_snow +
546621
(
547622
p.snow.turbulent_fluxes.lhf +
548623
p.snow.turbulent_fluxes.shf +
549624
p.snow.R_n - p.snow.energy_runoff - p.ground_heat_flux +
550-
ρe_flux_falling_rain
625+
e_flux_falling_rain
551626
) * p.snow.snow_cover_fraction
552627
return nothing
553628
end

0 commit comments

Comments
 (0)