Terrarium.jl
Terrarium is a 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-compatibility 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
You can install and use Terrarium as a Julia package by typing ] in your REPL and running:
pkg> add Terrariumor alternatively running
import Pkg
Pkg.add("Terrarium")followed by
using TerrariumIf you would like to not only use Terrarium but also actively develop it (or fix bugs 🐛), you can also install it as a development package:
pkg> dev Terrariumthough it is worth noting that this will clone the repository into your Julia home directory by default. You can also fork/clone the repository yourself and start hacking!
git clone git@github.com:NumericalEarth/Terrarium.jlYou can then initialize the project environment by setting the repository as your working directory and running
julia --project=. -e "import Pkg; Pkg.instantiate()"To run the example scripts, you will need to set the project directory to the examples/ directory,
julia --project=examples -e "import Pkg; Pkg.instantiate()"
julia --project=examples examples/simulations/soil_heat_global.jlYou 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}}, 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 = (), timestepper_cache = ())
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}, 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}, 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}}, 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 = (), timestepper_cache = ())
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
Introduction
Running Terrarium
Extending Terrarium
Models
Processes
- Atmospheric inputs
- Soil processes
- Surface energy balance
- Surface hydrology
- Vegetation
- Physical constants
- Physics utilities