Soil models

Warning

This page is a work in progress. If you have any questions or notice any errors, please raise an issue.

Overview

SoilModel is a model of soil physics that couples the relevant processes governing energy, water, and carbon in natural soils. It is the primary model type for simulating heat conduction, freeze-thaw processes (e.g. permafrost), and variably saturated water flow in the subsurface.

arch = CPU()
grid = ColumnGrid(arch, Float32, ExponentialSpacing(N = 10)) # 10 soil layers
model = SoilModel(grid)
integrator = initialize(model)
Integrator of SoilModel{Float32, Oceananigans.Grids.RectilinearGrid{Float32, Oceananigans.Grids.Periodic, Oceananigans.Grids.Flat, Oceananigans.Grids.Bounded, Oceananigans.Grids.StaticVerticalDiscretization{Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (17,), (-3,), Vector{Float32}}, Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (16,), (-3,), Vector{Float32}}, Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (17,), (-4,), Vector{Float32}}, Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (16,), (-3,), Vector{Float32}}}, Float32, Float32, Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (3,), (-1,), StepRangeLen{Float32, Float64, Float64, Int64}}, Oceananigans.Utils.StaticStridesOffsetArray{Float32, 1, (3,), (-1,), StepRangeLen{Float32, Float64, Float64, Int64}}, Float32, Float32, Nothing, Nothing, CPU, Oceananigans.Grids.GridSize{1, 1, 10, 1, 0, 3}}, SoilEnergyWaterCarbon{Float32, SoilStratigraphy{Float32, 1, Tuple{ConstantSoilHorizon{Float32, :soil, ConstantSoilPorosity{Float32}}}}, SoilThermodynamics{Float32, SoilEnergyTemperatureClosure, SoilThermalProperties{Float32, FreeWater, InverseQuadratic, SoilThermalConductivities{Float32}}}, SoilHydrology{Float32, RichardsEq, SoilSaturationPressureClosure, SoilHydraulicsSURFEX{Float32, BrooksCorey{FreezeCurves.SoilWaterVolume{Float32, Float32, Float32}, Float32, Float32}, UnsatKBrooksCorey{Float32}}, Nothing}, ConstantSoilCarbonDensity{Float32}}, DefaultInitializer{Float32}, IMEX{Float32, ForwardEuler{Float32}, BackwardEuler{Float32, Picard}}} with timestepper IMEX{Float32, ForwardEuler{Float32}, BackwardEuler{Float32, Picard}}(ForwardEuler{Float32}(3600.0f0), BackwardEuler{Float32, Picard}(Picard(2)))
├── Current time: 0.0
├── StateVariables{Float32}(clock = Clock{Float32, Float64}(time=0 seconds, iteration=0, last_Δt=Inf days), prognostic = (:internal_energy, :saturation_water_ice), auxiliary = (:temperature, :liquid_water_fraction, :pressure_head, :ground_temperature, :hydraulic_conductivity, :water_table, :water_flux), inputs = (), namespaces = (:soil,), timestepper_cache = IMEXCache)
Terrarium.SoilModel — Type
mutable struct SoilModel{NF, GridType<:Oceananigans.Grids.AbstractGrid, Soil<:Terrarium.AbstractSoil, Initializer<:Terrarium.AbstractInitializer, Timestepper<:Terrarium.AbstractTimeStepper} <: Terrarium.AbstractSoilModel{NF, GridType}

General implementation of a 1D column model of soil energy, water, and carbon transport.

Properties:

  • grid::Oceananigans.Grids.AbstractGrid: Spatial grid type

  • soil::Terrarium.AbstractSoil: Soil processes

  • constants::PhysicalConstants: Physical constants

  • initializer::Terrarium.AbstractInitializer: State variable initializer

  • timestepper::Terrarium.AbstractTimeStepper: Time stepper: a single AbstractTimeStepper (e.g. ForwardEuler, Heun) or an IMEX; defaults to IMEX for Richards flow and ForwardEuler otherwise

source

The default timestepper of SoilModel depends on the soil hydrology. With Richards-equation water flow (the default), it is IMEX(ForwardEuler(NF; Δt = 3600), BackwardEuler(NF)), which treats soil heat conduction and water flow implicitly with a 1-hour time step. Without water flow (NoFlow), it is ForwardEuler(NF). Value boundary conditions such as PrescribedSurfaceTemperature require an explicit timestepper, e.g. SoilModel(grid; soil, timestepper = ForwardEuler(NF)). See Time stepping for details.

variables(model)
Variables
├─ Prognostic: 
├── internal_energy [J m^-3] on XYZ{Center, Center, Center} of the ground domain
├── saturation_water_ice [-] on XYZ{Center, Center, Center} of the ground domain
├─ Auxiliary: 
├── temperature [°C] on XYZ{Center, Center, Center} of the ground domain
├── liquid_water_fraction [-] on XYZ{Center, Center, Center} of the ground domain
├── pressure_head [m] on XYZ{Center, Center, Center} of the ground domain
├── ground_temperature [°C] on XY{Center, Center, Terrarium.Coordinate{typeof(lastindex), Center}} of the ground domain
├── hydraulic_conductivity [m s^-1] on XYZ{Center, Center, Face} of the ground domain
├── water_table [m] on XY{Center, Center, Nothing} of the ground domain
├── water_flux [m s^-1] on XYZ{Center, Center, Face} of the ground domain
├─ Inputs: 
├─ Namespaces:
├── Terrarium.Namespace{:soil, Terrarium.Variables}

Components

FieldTypeScopeProcess page
stratAbstractStratigraphyVertical structure of soilSoil stratigraphy
energyAbstractSoilThermodynamicsHeat conduction and freeze-thaw of soil waterSoil energy balance
hydrologyAbstractSoilHydrologyVertical flow of water between soil layersSoil hydrology
biogeochemAbstractSoilBiogeochemistrySoil organic carbon and biogeochemical fluxesNot yet added

Each component is summarized briefly below. Follow the linked process pages for full theoretical background, available concrete types, state variables, and method signatures.

Stratigraphy

The strat component parameterizes the vertical distribution of soil material properties (texture, porosity, organic content). It provides kernel functions used by the energy and hydrology sub-processes to look up spatially varying material properties at each grid cell. By default HomogeneousSoilStratigraphy is used, which specifies a single uniform material throughout all vertical profiles. See Soil stratigraphy for details.

Energy balance

The energy component represents heat conduction in the soil column, including the latent heat of freeze-thaw phase change. The default implementation is SoilThermodynamics, which evolves the volumetric internal energy $U$ (J m⁻³) as the prognostic variable and derives temperature via the SoilEnergyTemperatureClosure. See Soil energy balance for details.

Hydrology

The hydrology component governs the vertical movement of water in the soil column. The default implementation is SoilHydrology, which evolves total saturation (liquid + ice) as the prognostic variable. By default, the vertical flow follows the Richards equation (RichardsEq); selecting NoFlow as the vertical_flow operator keeps the soil water immobile. See Soil hydrology for details.

Biogeochemistry

The biogeochem component simulates the spatial distribution of soil organic carbon and associated biogeochemical fluxes. The default implementation is ConstantSoilCarbonDensity, which prescribes a spatially homogeneous organic carbon density and does not define any prognostic variables.

Initializers

Terrarium provides a hierarchy of initializers for SoilModel. The top-level initializer is SoilInitializer, which composes separate sub-initializers for energy, hydrology, and biogeochemistry. It is the recommended initializer for most use cases:

Terrarium.SoilInitializer — Type
struct SoilInitializer{NF, EnergyInit<:Terrarium.AbstractInitializer{NF}, HydrologyInit<:Terrarium.AbstractInitializer{NF}, BGCInit<:Terrarium.AbstractInitializer{NF}} <: Terrarium.AbstractInitializer{NF}

Initializer for coupled soil energy/hydrology/biogeochemistry models.

source

Passing a SoilInitializer to SoilModel overrides the DefaultInitializer:

initializer = SoilInitializer(Float32;
    energy    = QuasiThermalSteadyState(Float32; T₀ = 2.0f0),
    hydrology = SaturationWaterTable(Float32; water_table_depth = 3.0f0),
)
model = SoilModel(grid; initializer)
SoilModel{Float32} on CPU()
├── grid:  1×1×10 RectilinearGrid{Float32, Periodic, Flat, Bounded} on CPU with 1×0×3 halo
├── soil:  SoilEnergyWaterCarbon{Float32, SoilStratigraphy{Float32, 1, Tuple{ConstantSoilHorizon{Float32, :soil, ConstantSoilPorosity{Float32}}}}, SoilThermodynamics{Float32, SoilEnergyTemperatureClosure, SoilThermalProperties{Float32, FreeWater, InverseQuadratic, SoilThermalConductivities{Float32}}}, SoilHydrology{Float32, RichardsEq, SoilSaturationPressureClosure, SoilHydraulicsSURFEX{Float32, BrooksCorey{FreezeCurves.SoilWaterVolume{Float32, Float32, Float32}, Float32, Float32}, UnsatKBrooksCorey{Float32}}, Nothing}, ConstantSoilCarbonDensity{Float32}}
├── constants:  PhysicalConstants{Float32}
├── initializer:  SoilInitializer{Float32, QuasiThermalSteadyState{Float32, Float32, Float32}, SaturationWaterTable{Float32, Float32, Float32}, DefaultInitializer{Float32}}
├── timestepper:  IMEX{Float32, ForwardEuler{Float32}, BackwardEuler{Float32, Picard}}

Spatially varying initial values

The parameters of the energy and hydrology initializers are declared as input variables of the model, with the values given to the initializer as their defaults:

InitializerInput variables
ConstantSoilTemperatureinitial_surface_temperature (°C)
QuasiThermalSteadyStateinitial_surface_temperature (°C), geothermal_heat_flux (W/m²)
ConstantSaturationinitial_saturation
SaturationWaterTablevadose_zone_saturation (saturation or volumetric water content, see below), water_table_depth (m)

Each value may therefore be a number, a function of the horizontal node coordinates, an array, a Field, or an AbstractFieldInitializer. These defaults are re-applied at every initialization, so changes to their parameters take effect, and an InputSource with the same name and units always takes precedence over them. Spatial data, such as a regridded climatology, should preferably be supplied this way rather than as a Field stored in the initializer: the initializer is part of the model, which should stay independent of any particular grid and free of state. Field initializers such as LatitudinalClimatology expose their own parameters as model parameters. Note that geothermal_heat_flux is the same variable read by the GeothermalHeatFlux bottom boundary condition, so the initial profile and the boundary condition stay consistent.

column_grid = ColumnGrid(arch, Float32, ExponentialSpacing(N = 10), 3) # three columns
energy = QuasiThermalSteadyState(Float32; T₀ = x -> 2.0f0 * x, Qgeo = 0.05f0)
model = SoilModel(column_grid; initializer = SoilInitializer(Float32; energy))
integrator = initialize(model)
interior(integrator.state.initial_surface_temperature)
3×1×1 view(::Array{Float32, 3}, 4:6, 1:1, 1:1) with eltype Float32:
[:, :, 1] =
 0.33333334
 1.0
 1.6666666

Energy initializers

Terrarium.ConstantSoilTemperature — Type
struct ConstantSoilTemperature{NF, T0} <: Terrarium.AbstractInitializer{NF}

Initializer for soil/ground temperature that sets the temperature profile of each column to the value of the initial_surface_temperature input variable, whose default is T₀.

T₀ may be a number, a function of the horizontal node coordinates, an array, a Field, or an AbstractFieldInitializer. It is re-applied as the default of the input variable at every initialization and can be overridden by an InputSource named initial_surface_temperature.

Properties:

  • T₀::Any: Initial surface temperature (°C)
source
Terrarium.QuasiThermalSteadyState — Type
struct QuasiThermalSteadyState{NF, T0, QG} <: Terrarium.AbstractInitializer{NF}

Initializer that sets soil/ground temperature to a thermal quasi-steady state based on the given surface temperature, geothermal heat flux, and bulk (constant) thermal conductivity:

T(z) = T₀ - Qgeo / k_eff * z

with depth z ≤ 0 (m). Note that this is not a true thermal steady state, which would require iterative calculation of the thermal conductivity from the soil properties and initial temperature profile.

The surface temperature and the geothermal heat flux are declared as the input variables initial_surface_temperature (°C) and geothermal_heat_flux (W/m²) with defaults T₀ and Qgeo. The latter is the same variable read by the GeothermalHeatFlux bottom boundary condition, so that the initial profile and the boundary condition are consistent. Each default may be a number, a function of the horizontal node coordinates, an array, a Field, or an AbstractFieldInitializer. Defaults are re-applied at every initialization, so that changes to parameters take effect, and can be overridden by an InputSource of the same name (with matching units).

Properties:

  • T₀::Any: Initial surface temperature (°C)

  • Qgeo::Any: Geothermal heat flux (W/m²)

  • k_eff::Any: Bulk thermal conductivity (W/m/K)

source
Terrarium.PiecewiseLinearInitialSoilTemperature — Type
struct PiecewiseLinearInitialSoilTemperature{NF, N}

Represents a piecewise linear temperature initializer specified from the given knots.

initializer = PiecewiseLinearInitialSoilTemperature(
    0.0u"m" => 5.0, # always in °C!
    0.5u"m" => 2.0,
    1.0u"m" => 1.0,
    10.0u"m" => 1.5,
    ...
)

Properties:

  • knots::NTuple{N, NF} where {NF, N}
source

Hydrology initializers

Terrarium.SaturationWaterTable — Type
struct SaturationWaterTable{NF, S, D} <: Terrarium.AbstractInitializer{NF}

Simple initialization scheme for soil/ground saturation that sets the initial water table at the given depth and the water content in all layers in the vadose (unsaturated) zone to a constant value.

The vadose zone water content is given as a Saturation or as a VolumetricWaterContent; plain (unwrapped) values are interpreted as saturation. Volumetric water content is converted to saturation by dividing by the soil porosity at each grid cell (and capped at full saturation). The wrapped value is declared as the input variable vadose_zone_saturation, which therefore holds either the saturation (-) or the volumetric water content (m³/m³) as indicated by the marker type, and the water table depth as water_table_depth (m), with defaults taken from the corresponding properties. See QuasiThermalSteadyState for the accepted forms of the defaults.

julia> init = SaturationWaterTable(Float32; vadose_zone_saturation = VolumetricWaterContent(0.2f0));

julia> init.vadose_zone_saturation
VolumetricWaterContent{Float32}(0.2f0)

Properties:

  • vadose_zone_saturation::Any: Water content in the vadose zone above the water table, as Saturation, VolumetricWaterContent, or a plain saturation value

  • water_table_depth::Any: Depth of the water table below the surface (m)

source

The vadose zone water content of SaturationWaterTable can be given either as saturation or as volumetric water content by wrapping the value in one of the following marker types; plain values are interpreted as saturation. Volumetric water content is converted to saturation by dividing by the soil porosity at each grid cell, so that it can be initialized directly from, e.g., soil moisture observations.

hydrology = SaturationWaterTable(Float32; vadose_zone_saturation = VolumetricWaterContent(0.2f0), water_table_depth = 3.0f0)
model = SoilModel(grid; initializer = SoilInitializer(Float32; hydrology))
integrator = initialize(model)
extrema(interior(integrator.state.saturation_water_ice))
(0.40816325f0, 1.0f0)
Terrarium.Saturation — Type
struct Saturation{T} <: Terrarium.AbstractWaterContent

Marker type indicating that the wrapped value is the saturation of the pore space with water and ice (-), i.e. the volumetric water and ice content divided by the porosity.

Properties:

  • value::Any: Saturation of the pore space with water and ice (-)
source
Terrarium.VolumetricWaterContent — Type
struct VolumetricWaterContent{T} <: Terrarium.AbstractWaterContent

Marker type indicating that the wrapped value is the volumetric water and ice content (m³/m³), i.e. the volume of water and ice per unit volume of soil. It is converted to saturation by dividing by the porosity of the soil.

Properties:

  • value::Any: Volumetric water and ice content (m³/m³)
source
Terrarium.ConstantSaturation — Type
struct ConstantSaturation{NF, S} <: Terrarium.AbstractInitializer{NF}

Initializer for soil water/ice that sets the saturation profile of each column to the value of the initial_saturation input variable, whose default is sat. See QuasiThermalSteadyState for the accepted forms of the default.

Properties:

  • sat::Any: Initial water/ice saturation (-)
source

Kernel functions

Terrarium.compute_quasi_steady_state_temperature — Function
compute_quasi_steady_state_temperature(
    i,
    j,
    k,
    grid,
    fields,
    init::QuasiThermalSteadyState
)

Quasi-steady-state temperature T₀ - Qgeo / k_eff * z at cell (i, j, k) from the initial_surface_temperature (°C) and geothermal_heat_flux (W/m²) fields.

source
Terrarium.compute_constant_temperature — Function
compute_constant_temperature(
    i,
    j,
    k,
    grid,
    fields,
    ::ConstantSoilTemperature
)

Initial temperature at cell (i, j, k) taken from the initial_surface_temperature (°C) field.

source
Terrarium.compute_vadose_zone_saturation — Function
compute_vadose_zone_saturation(
    i,
    j,
    k,
    grid,
    fields,
    saturation
)

Saturation in the vadose zone at cell (i, j, k) from the vadose_zone_saturation input field. For a Saturation or a plain value, the field holds the saturation and is returned directly; for a VolumetricWaterContent, it holds the volumetric water content θ, which is divided by the soil porosity ϕ and capped at full saturation.

source

Fallback

Boundary conditions

Terrarium provides a set of named boundary condition aliases for the most common SoilModel configurations. These return NamedTuples in the format expected by the boundary_conditions keyword argument of initialize.

Energy boundary conditions

Terrarium.SoilHeatFlux — Function

Alias for FluxBoundaryCondition on internal_energy with name soil_heat_flux representing the net heat flux into the top of the soil column. Without snow this equals the surface energy balance ground_heat_flux; with snow it is the (blended) conductive flux across the snow base.

source
Terrarium.GeothermalHeatFlux — Function

Alias for FluxBoundaryCondition on internal_energy with name geothermal_heat_flux representing the geothermal heat flux at the bottom boundary of the soil column.

source

Hydrology boundary conditions

Terrarium.InfiltrationFlux — Function

Alias for PrescribedFlux with name infiltration representing liquid water infiltration at the soil surface.

source

Alias for a discrete-form FluxBoundaryCondition on saturation_water_ice that normalizes the physical infiltration flux (m/s of water depth) by the top-of-soil porosity at evaluation time, since saturation_water_ice is the dimensionless saturation (VWC / porosity). See saturation_infiltration_bc.

source
Terrarium.FreeDrainage — Function

Alias for PrescribedGradient representing a Neumann-type zero pressure gradient at the bottom of the soil column, thereby allowing free drainage of water.

source