This sets up the simulation that mimicks the lab experiment presented in Gardener 1970b and modeled also by Lehmann and Or, 2024.
For further details on how to setup a simulation, please see our other Soil tutorials. This one is very terse and does not provide complete explanations
The same experiment is carried out 3 times
- No evaporation (zero flux boundary conditions)
- With evaporation but no drainage (Ksat = 0)
- With evaporation and drainage
using CairoMakie
import SciMLBase
import ClimaTimeSteppers as CTS
using Thermodynamics
using ClimaCore
import ClimaParams as CP
using SurfaceFluxes
using StaticArrays
using Dates
using DelimitedFiles: readdlm
using ClimaLand
using ClimaLand.Domains: Column
using ClimaLand.Soil
import ClimaLand
import ClimaLand.Parameters as LP
import SurfaceFluxes.Parameters as SFP
FT = Float64;
earth_param_set = LP.LandParameters(FT)
thermo_params = LP.thermodynamic_parameters(earth_param_set);
Parameters
K_sat = FT(0.01 / 3600 / 24)
vg_n = FT(1.55)
vg_α = FT(1.5)
hcm = vanGenuchten{FT}(; α = vg_α, n = vg_n)
ν = FT(0.4)
θ_r = FT(0.04)
S_s = FT(1e-3)
ν_ss_om = FT(0.0)
ν_ss_quartz = FT(0.3)
ν_ss_gravel = FT(0.0)
emissivity = FT(1.0)
PAR_albedo = FT(0.2)
NIR_albedo = FT(0.4)
z_0m = FT(1e-3)
z_0b = FT(1e-4)
d_ds = FT(0.01)# 10mm
params = ClimaLand.Soil.EnergyHydrologyParameters(
FT;
ν,
ν_ss_om,
ν_ss_quartz,
ν_ss_gravel,
hydrology_cm = hcm,
K_sat,
S_s,
θ_r,
PAR_albedo,
NIR_albedo,
emissivity,
z_0m,
z_0b,
earth_param_set,
d_ds,
);
start_date = DateTime(2005)
SW_d = (t) -> 0
LW_d = (t) -> 294.15^4 * 5.67e-8
radiation = PrescribedRadiativeFluxes(
FT,
TimeVaryingInput(SW_d),
TimeVaryingInput(LW_d),
start_date,
)
PrescribedRadiativeFluxes{Float64, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#1#2"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#3#4"}, Dates.DateTime, Nothing}(ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#1#2"}(Main.var"##356".var"#1#2"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#3#4"}(Main.var"##356".var"#3#4"()), Dates.DateTime("2005-01-01T00:00:00"), nothing)
Atmos
T_air = FT(301.15)
rh = FT(0.38)
esat = Thermodynamics.saturation_vapor_pressure(
thermo_params,
T_air,
Thermodynamics.Liquid(),
)
e = rh * esat
q = FT(0.622 * e / (101325 - 0.378 * e))
precip = (t) -> 0.0
T_atmos = (t) -> T_air
u_atmos = (t) -> 1.0
q_atmos = (t) -> q
h_atmos = FT(0.1)
P_atmos = (t) -> 101325
gustiness = FT(1e-2)
atmos = PrescribedAtmosphere(
TimeVaryingInput(precip),
TimeVaryingInput(precip),
TimeVaryingInput(T_atmos),
TimeVaryingInput(u_atmos),
TimeVaryingInput(q_atmos),
TimeVaryingInput(P_atmos),
start_date,
h_atmos,
earth_param_set;
gustiness = gustiness,
)
PrescribedAtmosphere{Float64, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#5#6"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#5#6"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#7#8"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#9#10"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#11#12"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#13#14"}, ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{ClimaLand.var"#30#33"}, Dates.DateTime, Thermodynamics.Parameters.ThermodynamicsParameters{Float64}}(ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#5#6"}(Main.var"##356".var"#5#6"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#5#6"}(Main.var"##356".var"#5#6"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#7#8"}(Main.var"##356".var"#7#8"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#9#10"}(Main.var"##356".var"#9#10"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#11#12"}(Main.var"##356".var"#11#12"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{Main.var"##356".var"#13#14"}(Main.var"##356".var"#13#14"()), ClimaUtilitiesClimaCoreNCDatasetsExt.TimeVaryingInputsExt.AnalyticTimeVaryingInput{ClimaLand.var"#30#33"}(ClimaLand.var"#30#33"()), Dates.DateTime("2005-01-01T00:00:00"), 0.1, 0.01, Thermodynamics.Parameters.ThermodynamicsParameters{Float64}(273.16, 101325.0, 100000.0, 1859.0, 4181.0, 2100.0, 2.5008e6, 2.8344e6, 611.657, 273.16, 273.15, 1.0, 1000.0, 150.0, 298.15, 6864.8, 10513.6, 0.28571428571, 8.3144598, 0.02897, 0.01801528, 290.0, 220.0, 9.81, 233.0, 1.0))
Simulation setup - no evaporation Boundary conditions
zero_water_flux = WaterFluxBC((p, t) -> 0)
zero_heat_flux = HeatFluxBC((p, t) -> 0)
no_flux_boundary_fluxes = (;
top = WaterHeatBC(; water = zero_water_flux, heat = zero_heat_flux),
bottom = WaterHeatBC(; water = zero_water_flux, heat = zero_heat_flux),
);
t0 = Float64(0)
tf = Float64(24 * 3600 * 15)
dt = Float64(900.0)
Δz = 0.01
zmax = FT(0)
zmin = FT(-1.6)
nelems = Int((zmax - zmin) / Δz)
soil_domain = Column(; zlim = (zmin, zmax), nelements = nelems)
z = ClimaCore.Fields.coordinate_field(soil_domain.space.subsurface).z
soil = Soil.EnergyHydrology{FT}(;
parameters = params,
domain = soil_domain,
boundary_conditions = no_flux_boundary_fluxes,
sources = (),
);
Initial conditions
Y, p, cds = initialize(soil)
function estimated_ic(z)
0.34 / (1 + exp(-(z + 0.165) / 0.005)) + 0.05
end
function init_soil!(Y, z, params)
FT = eltype(Y.soil.ϑ_l)
Y.soil.ϑ_l .= estimated_ic.(z)
Y.soil.θ_i .= 0
T = FT(294.15)
ρc_s = @. Soil.volumetric_heat_capacity(
Y.soil.ϑ_l,
Y.soil.θ_i,
params.ρc_ds,
params.earth_param_set,
)
Y.soil.ρe_int =
Soil.volumetric_internal_energy.(
Y.soil.θ_i,
ρc_s,
T,
params.earth_param_set,
)
end
init_soil!(Y, z, soil.parameters)
set_initial_cache! = make_set_initial_cache(soil)
set_initial_cache!(p, Y, t0);
Timestepping:
exp_tendency! = make_exp_tendency(soil)
imp_tendency! = make_imp_tendency(soil);
jacobian! = ClimaLand.make_jacobian(soil);
jac_kwargs = (; jac_prototype = ImplicitEquationJacobian(Y), Wfact = jacobian!);
timestepper = CTS.ARS111();
ode_algo = CTS.IMEXAlgorithm(
timestepper,
CTS.NewtonsMethod(
max_iters = 1,
update_j = CTS.UpdateEvery(CTS.NewNewtonIteration),
),
);
Problem definition and callbacks
prob = SciMLBase.ODEProblem(
CTS.ClimaODEFunction(
T_exp! = exp_tendency!,
T_imp! = SciMLBase.ODEFunction(imp_tendency!; jac_kwargs...),
dss! = ClimaLand.dss!,
),
Y,
(t0, tf),
p,
);
saveat = Array(t0:3600.0:tf)
sv = (;
t = Array{Float64}(undef, length(saveat)),
saveval = Array{NamedTuple}(undef, length(saveat)),
)
saving_cb = ClimaLand.NonInterpSavingCallback(sv, saveat)
cb = SciMLBase.CallbackSet(saving_cb);
sol_no_evap =
SciMLBase.solve(prob, ode_algo; dt = dt, callback = cb, saveat = saveat);
Repeat with evaporation and drainage This requires different initial conditions
top_bc = ClimaLand.Soil.AtmosDrivenFluxBC(atmos, radiation)
evap_boundary_fluxes = (;
top = top_bc,
bottom = WaterHeatBC(; water = zero_water_flux, heat = zero_heat_flux),
)
soil = Soil.EnergyHydrology{FT}(;
parameters = params,
domain = soil_domain,
boundary_conditions = evap_boundary_fluxes,
sources = (),
)
Y, p, cds = initialize(soil)
init_soil!(Y, z, soil.parameters)
set_initial_cache! = make_set_initial_cache(soil)
set_initial_cache!(p, Y, t0)
soil_exp_tendency! = make_exp_tendency(soil)
exp_tendency! = make_exp_tendency(soil)
imp_tendency! = make_imp_tendency(soil);
jacobian! = ClimaLand.make_jacobian(soil);
timestepper = CTS.ARS111();
ode_algo = CTS.IMEXAlgorithm(
timestepper,
CTS.NewtonsMethod(
max_iters = 1,
update_j = CTS.UpdateEvery(CTS.NewNewtonIteration),
),
);
jac_kwargs = (; jac_prototype = ImplicitEquationJacobian(Y), Wfact = jacobian!);
prob = SciMLBase.ODEProblem(
CTS.ClimaODEFunction(
T_exp! = exp_tendency!,
T_imp! = SciMLBase.ODEFunction(imp_tendency!; jac_kwargs...),
dss! = ClimaLand.dss!,
),
Y,
(t0, tf),
p,
);
saveat = Array(t0:3600.0:tf)
sv = (;
t = Array{Float64}(undef, length(saveat)),
saveval = Array{NamedTuple}(undef, length(saveat)),
)
saving_cb = ClimaLand.NonInterpSavingCallback(sv, saveat)
updateat = deepcopy(saveat)
model_drivers = ClimaLand.get_drivers(soil)
updatefunc = ClimaLand.make_update_drivers(model_drivers)
driver_cb = ClimaLand.DriverUpdateCallback(updateat, updatefunc)
cb = SciMLBase.CallbackSet(driver_cb, saving_cb)
sol = SciMLBase.solve(prob, ode_algo; dt = dt, callback = cb, saveat = saveat)
evap = [
parent(sv.saveval[k].soil.turbulent_fluxes.vapor_flux_liq)[1] for
k in 1:length(sol.t)
];
# Repeat with no drainage (Ksat = 0, different BC), and with evaporation, in shorter domain
[ Info: Warning: No runoff model was provided; zero runoff generated.
This requires different boundary conditions yet again: Wet boundary at bottom, zero heat flux at bottom, the previously defined atmos driven evaporation at the top.
bottom_water_bc = MoistureStateBC((p, t) -> 0.35)
no_drainage_boundary_fluxes = (;
top = top_bc,
bottom = WaterHeatBC(; water = bottom_water_bc, heat = zero_heat_flux),
)
zmax = FT(0)
zmin = FT(-0.16)
nelems = Int((zmax - zmin) / Δz)
dt = Float64(10.0)
soil_domain = Column(; zlim = (zmin, zmax), nelements = nelems)
z_no_evap = ClimaCore.Fields.coordinate_field(soil_domain.space.subsurface).z
soil = Soil.EnergyHydrology{FT}(;
parameters = params,
domain = soil_domain,
boundary_conditions = no_drainage_boundary_fluxes,
sources = (),
)
Y, p, cds = initialize(soil)
init_soil!(Y, z_no_evap, soil.parameters)
set_initial_cache! = make_set_initial_cache(soil)
set_initial_cache!(p, Y, t0)
exp_tendency! = make_exp_tendency(soil)
imp_tendency! = make_imp_tendency(soil);
jacobian! = ClimaLand.make_jacobian(soil);
timestepper = CTS.ARS111();
ode_algo = CTS.IMEXAlgorithm(
timestepper,
CTS.NewtonsMethod(
max_iters = 1,
update_j = CTS.UpdateEvery(CTS.NewNewtonIteration),
),
);
jac_kwargs = (; jac_prototype = ImplicitEquationJacobian(Y), Wfact = jacobian!);
prob = SciMLBase.ODEProblem(
CTS.ClimaODEFunction(
T_exp! = exp_tendency!,
T_imp! = SciMLBase.ODEFunction(imp_tendency!; jac_kwargs...),
dss! = ClimaLand.dss!,
),
Y,
(t0, tf),
p,
);
saveat = Array(t0:3600.0:tf)
sv = (;
t = Array{Float64}(undef, length(saveat)),
saveval = Array{NamedTuple}(undef, length(saveat)),
)
saving_cb = ClimaLand.NonInterpSavingCallback(sv, saveat)
updateat = deepcopy(saveat)
model_drivers = ClimaLand.get_drivers(soil)
updatefunc = ClimaLand.make_update_drivers(model_drivers)
driver_cb = ClimaLand.DriverUpdateCallback(updateat, updatefunc)
cb = SciMLBase.CallbackSet(driver_cb, saving_cb)
sol_no_drainage =
SciMLBase.solve(prob, ode_algo; dt = dt, callback = cb, saveat = saveat)
evap_no_drainage = [
parent(sv.saveval[k].soil.turbulent_fluxes.vapor_flux_liq)[1] for
k in 1:length(sol.t)
];
Figures
fig = Figure(size = (800, 400))
ax = Axis(fig[1, 1], xlabel = "Day", ylabel = "Evaporation rate (mm/d)")
CairoMakie.lines!(
ax,
sol.t ./ 3600 ./ 24,
evap .* (1000 * 3600 * 24),
label = "With drainage",
color = :red,
)
CairoMakie.lines!(
ax,
sol_no_drainage.t ./ 3600 ./ 24,
evap_no_drainage .* (1000 * 3600 * 24),
label = "No drainage",
color = :blue,
)
CairoMakie.axislegend(ax)
ax2 = Axis(fig[1, 2], xlabel = "Day", ylabel = "Cumulative evaporation (mm)")
CairoMakie.lines!(
ax2,
sol.t ./ 3600 ./ 24,
cumsum(evap) .* (1000 * 3600),
color = :red,
)
CairoMakie.lines!(
ax2,
sol_no_drainage.t ./ 3600 ./ 24,
cumsum(evap_no_drainage) .* (1000 * 3600),
color = :blue,
)
save("evaporation_lehmann2024_figS6.png", fig);
fig2 = Figure(size = (800, 1200))
ax1 = Axis(fig2[1, 1], title = "Drainage only")
CairoMakie.ylims!(-0.35, 0)
CairoMakie.xlims!(0.0, 0.4)
linestyles = [:solid, :dash, :dashdot, :dashdotdot, :dot]
days = [0, 1, 2, 10]
for i in 1:1:4
CairoMakie.lines!(
ax1,
parent(sol_no_evap.u[days[i] * 24 + 1].soil.ϑ_l)[:],
parent(z)[:],
label = "$(days[i]) days",
color = :black,
linestyle = linestyles[i],
)
end
ax2 = Axis(fig2[2, 1], title = "Evap+Drainage", ylabel = "Depth(cm)")
CairoMakie.ylims!(-0.3, 0)
CairoMakie.xlims!(0.0, 0.4)
days = [0, 1, 2, 5, 13]
for i in 1:1:5
CairoMakie.lines!(
ax2,
parent(sol.u[days[i] * 24 + 1].soil.ϑ_l)[:],
parent(z)[:],
label = "$(days[i]) days",
color = :black,
linestyle = linestyles[i],
)
end
ax3 = Axis(fig2[3, 1], title = "Evap only", xlabel = "Volumetric Water Content")
CairoMakie.ylims!(-0.15, 0)
CairoMakie.xlims!(0.0, 0.4)
days = [0, 2, 9, 14]
for i in 1:1:4
CairoMakie.lines!(
ax3,
parent(sol_no_drainage.u[days[i] * 24 + 1].soil.ϑ_l)[:],
label = "$(days[i]) days",
parent(z_no_evap)[:],
color = :black,
linestyle = linestyles[i],
)
end
CairoMakie.axislegend(ax3, position = :lt)
CairoMakie.axislegend(ax2, position = :lt)
CairoMakie.axislegend(ax1, position = :lt)
save("evaporation_gardner_fig1.png", fig2);
This page was generated using Literate.jl.