Terrarium.jl

Terrarium is a new and upcoming framework for hybrid physics- and data-driven land and terrestrial ecosystem modeling across spatial and temporal scales. We envision Terrarium to be part of a new generation of Earth system component models that combine modularity, interactivity, GPU-compability and auto-differentiability (AD) for seamless integration of process-based and data-driven model components in both global and regional scale simulations.

Terrarium is being developed alongside SpeedyWeather.jl and Oceananigans.jl as the land component of a new, user-friendly, and fully GPU/AD-compatible Earth System Model in the Julia programming language.

Installation

Terrarium is still in a prototype stage and is not yet registered as a package in the Julia General registry.

However, you can still install the package from the repository via the package manager (type ] in your REPL):

pkg> add https://github.com/TUM-PIK-ESM/Terrarium.jl

If you would like to start hacking on the code directly, we recommend first cloning the repository:

git clone https://github.com/TUM-PIK-ESM/Terrarium.jl

and then initializing the project environment with

cd Terrarium.jl/
julia --project=. -e "import Pkg; Pkg.instantiate()"

To run the example scripts, make sure to set the project directory to the examples/ directory,

julia --project=examples examples/simulations/soil_heat_global.jl

You can also directly activate the example project environment from your REPL by first entering the package manager with ] and then running the command activate examples followed by instantiate.

Quick start

A natural first step with Terrarium is to set up and run your very first SoilModel. This represents a standalone model of transient heat, water, and carbon transport over a particular choice of grid. We start by choosing a ColumnGrid which represents one or more laterally independent vertical columns:

using Terrarium

# Set up a SoilModel on a ColumnGrid with 10 vertical soil layers that will run on the CPU with 32-bit precision
num_columns = 1
arch = CPU()
grid = ColumnGrid(arch, Float32, ExponentialSpacing(N=10), num_columns)
model = SoilModel(grid)
# Prescribe a constant surface temperature of 1°C
bcs = PrescribedSurfaceTemperature(:T_ub, 1.0)
integrator = initialize(model, ForwardEuler(eltype(grid)), boundary_conditions = bcs)
# Run the simulation forward for 10 model days
@time run!(integrator, period = Day(10))
Integrator of SoilModel{Float32, ColumnGrid{Float32, CPU, Oceananigans.Grids.RectilinearGrid{Float32, Oceananigans.Grids.Periodic, Oceananigans.Grids.Flat, Oceananigans.Grids.Bounded, Oceananigans.Grids.StaticVerticalDiscretization{OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}}, Float32, Float32, OffsetArrays.OffsetVector{Float32, StepRangeLen{Float32, Float64, Float64, Int64}}, Nothing, CPU}}, SoilEnergyWaterCarbon{Float32, HomogeneousStratigraphy{Float32, ConstantSoilPorosity{Float32}}, SoilEnergyBalance{Float32, Terrarium.ExplicitTwoPhaseHeatConduction, SoilEnergyTemperatureClosure, SoilThermalProperties{Float32, FreeWater, InverseQuadratic}}, SoilHydrology{Float32, NoFlow, SoilSaturationPressureClosure, SoilHydraulicsSURFEX{Float32, BrooksCorey{FreezeCurves.SoilWaterVolume{Float32, Float32, Float32}, Float32, Float32}, UnsatKLinear{Float32}}, Nothing}, ConstantSoilCarbonDensity{Float32}}, PhysicalConstants{Float32}, SoilInitializer{Float32, QuasiThermalSteadyState{Float32}, SaturationWaterTable{Float32}, DefaultInitializer{Float32}}} with ForwardEuler{Float32}
├── Current time: 864000.0
├── StateVariables{Float32}(clock = Clock{Float32, Float64}(time=10 days, iteration=2880, last_Δt=5 minutes), prognostic = (:internal_energy,), auxiliary = (:temperature, :liquid_water_fraction, :ground_temperature, :saturation_water_ice, :water_table, :hydraulic_conductivity), inputs = (), namespaces = ())

That's it! You already successfully ran a (very simple) simulation with Terrarium!

Note that setting num_columns = 1 here corresponds to a point simulation for a single vertical column. However, we can easily scale this up by set num_columns to any positive integer (up to the memory limit of your system, of course).

We can also easily adapt this code to run a global simulation over a suitable spatial grid. For this, we'll need to have RingGrids installed (or import it directly from Terrarium with using Terrarium.RingGrids). Optionally, if a GPU is available and CUDA.jl is installed in the current project or global Julia environment, we can accelerate the global simulation by simply changing CPU to GPU:

using Terrarium
using RingGrids: FullGaussianGrid
using CUDA # needs to be separately installed

rings = FullGaussianGrid(8) # Gaussian grid with 16 latitudinal rings, 8 per hemisphere (512 points, ~9.0˚ lat/lon)
arch = CUDA.functional() ? GPU() : CPU() # run on the GPU (if available)
grid = ColumnRingGrid(arch, Float32, ExponentialSpacing(N=10), rings) # create grid
model = SoilModel(grid)
# Prescribe a constant surface temperature of 1°C
bcs = PrescribedSurfaceTemperature(:T_ub, 1.0)
integrator = initialize(model, ForwardEuler(eltype(grid)), boundary_conditions = bcs)
# Run the simulation forward for 10 model days
@time run!(integrator, period = Day(10))
Integrator of SoilModel{Float32, ColumnRingGrid{Float32, CPU, RingGrids.FullGaussianGrid{SpeedyWeatherInternals.Architectures.CPU{KernelAbstractions.CPU}, Vector{UnitRange{Int64}}, Vector{Int64}}, Oceananigans.Grids.RectilinearGrid{Float32, Oceananigans.Grids.Periodic, Oceananigans.Grids.Flat, Oceananigans.Grids.Bounded, Oceananigans.Grids.StaticVerticalDiscretization{OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}, OffsetArrays.OffsetVector{Float32, Vector{Float32}}}, Float32, Float32, OffsetArrays.OffsetVector{Float32, StepRangeLen{Float32, Float64, Float64, Int64}}, Nothing, CPU}, RingGrids.Field{Bool, 1, Vector{Bool}, RingGrids.FullGaussianGrid{SpeedyWeatherInternals.Architectures.CPU{KernelAbstractions.CPU}, Vector{UnitRange{Int64}}, Vector{Int64}}}}, SoilEnergyWaterCarbon{Float32, HomogeneousStratigraphy{Float32, ConstantSoilPorosity{Float32}}, SoilEnergyBalance{Float32, Terrarium.ExplicitTwoPhaseHeatConduction, SoilEnergyTemperatureClosure, SoilThermalProperties{Float32, FreeWater, InverseQuadratic}}, SoilHydrology{Float32, NoFlow, SoilSaturationPressureClosure, SoilHydraulicsSURFEX{Float32, BrooksCorey{FreezeCurves.SoilWaterVolume{Float32, Float32, Float32}, Float32, Float32}, UnsatKLinear{Float32}}, Nothing}, ConstantSoilCarbonDensity{Float32}}, PhysicalConstants{Float32}, SoilInitializer{Float32, QuasiThermalSteadyState{Float32}, SaturationWaterTable{Float32}, DefaultInitializer{Float32}}} with ForwardEuler{Float32}
├── Current time: 864000.0
├── StateVariables{Float32}(clock = Clock{Float32, Float64}(time=10 days, iteration=2880, last_Δt=5 minutes), prognostic = (:internal_energy,), auxiliary = (:temperature, :liquid_water_fraction, :ground_temperature, :saturation_water_ice, :water_table, :hydraulic_conductivity), inputs = (), namespaces = ())

and voila! We have just run a GPU-accelerated, global-scale simulation of soil thermal dynamics with minimal additional effort. While more realistic simulations are of course more involved, this simple example demonstrates the core of what we aim to accomplish with Terrarium; a fast, user-friendly, and highly adaptable land model that can easily be configured to run on local, regional, and global scales.

Table of contents