rcontroll practical work - Day 2

Hands-on TROLL 4 discovery with rcontroll

Sylvain Schmitt

CIRAD

Apr 7, 2026

Introduction

TROLL 4.0: representing water and carbon fluxes, leaf phenology, and intraspecific trait variation in a mixed-species individual-based forest dynamics model (Maréchaux et al. 2025)

R Environment

During this practical session, we will use the TROLL model 4.0 with raw files from a simulation output. We will also use the tidyverse packages for table manipulation and graph creation. Download them (prerequisite!):

install.packages("tidyverse")

Load them with:

library(tidyverse)

Inputs

Raw simulations inputs and outputs are shared in data/day2/.

Files

list.files("data/day2/", pattern = "input")
[1] "R1_input_climate.txt"  "R1_input_daily.txt"    "R1_input_global.txt"  
[4] "R1_input_pedology.txt" "R1_input_species.txt" 

Species

TROLL 4 relies on two new following functional traits:

  • LA: leaf area (cm2)
  • TLP: leaf water potential at turgor loss point (MPa)

Species

read_tsv("data/day2/R1_input_species.txt") %>% 
  select(s_name, s_leafarea, s_tlp) %>% 
  ggplot(aes(s_leafarea, s_tlp)) +
  geom_point() + theme_bw() +
  scale_x_log10() + xlab("LA [cm2]") + ylab("TLP [MPa]")

Climate

TROLL 4 relies now solely on half-hourly values of the 5 climate variables:

  • Temperature (degree C)
  • Rainfall (cm)
  • WindSpeed (m/s)
  • VaporPressureDeficit (hPa)
  • Iradiance (W/m2)

NB : rcontroll include a function to help prepare climate data (day 3 session).

Climate

read_tsv("data/day2/R1_input_daily.txt")
# A tibble: 70,080 × 6
   DayJulian time_numeric  Temp  Snet    VPD    WS
       <dbl>        <dbl> <dbl> <dbl>  <dbl> <dbl>
 1         1          6    23.2   1.1 0.0830  2.35
 2         1          6.5  23.3   1.1 0.0981  2.39
 3         1          7    23.4   1.1 0.118   2.47
 4         1          7.5  23.5  24.9 0.120   2.51
 5         1          8    23.6  49.3 0.140   2.71
 6         1          8.5  23.9 101.  0.209   3.24
 7         1          9    24.2 154.  0.285   3.68
 8         1          9.5  24.6 181.  0.318   3.64
 9         1         10    25.0 209.  0.310   3.25
10         1         10.5  25.4 295.  0.295   2.82
# ℹ 70,070 more rows

Climate

read_tsv("data/day2/R1_input_climate.txt")
# A tibble: 2,920 × 2
   NightTemperature Rainfall
              <dbl>    <dbl>
 1             26.0   20.0  
 2             26.6    2.72 
 3             26.2    6.26 
 4             26.7   13.7  
 5             25.4   13.2  
 6             24.3   16.6  
 7             25.9    1.67 
 8             26.2    7.40 
 9             23.2   49.8  
10             25.6    0.399
# ℹ 2,910 more rows

Climate

g <- read_tsv("data/day2/R1_input_daily.txt") %>% 
  select(-time_numeric) %>% 
  group_by(DayJulian) %>% 
  summarise_all(mean) %>% 
  bind_cols(read_tsv("data/day2/R1_input_climate.txt")) %>% 
  gather(variable, value, -DayJulian) %>% 
  ggplot(aes(DayJulian, value)) +
  geom_line() +
  facet_wrap(~ variable, scales = "free_y") +
  theme_bw() + xlab("") + ylab("")

Climate

g

Climate

g <- read_tsv("data/day2/R1_input_climate.txt") %>% 
  mutate(date = as_date("1999-01-01") + 1:n()) %>% 
  group_by(pentad = floor_date(date, "5 days")) %>% 
  summarise(pr = sum(Rainfall)) %>% 
  ggplot(aes(yday(pentad), pr)) +
  geom_line(aes(group = year(pentad))) +
  theme_bw() +
  geom_smooth() + xlab("") + ylab("5-days rainfall [mm]")

Climate

g 

Parameters

TROLL 4 includes new ecosystem parameters controlling phenology, new intraspecific parameters controlling LA and TLP variations, and new macros controlling soil layer weights and water retention curve equations:

  • pheno_a0: threshold for change in old leaf shedding rate, in proportion of TLP
  • pheno_b0: threshold for change in old leaf shedding rate, in proportion of tree height
  • pheno_delta: amplitude of change in old leaf shedding rate
  • sigma_LA: intraspecific variation in LA (lognormal)
  • sigma_TLP: intraspecific variation in TLP (normal)
  • SOIL_LAYER_WEIGHT: soil layer weights: relative biomass, conductance, max transpiration
  • WATER_RETENTION_CURVE: water retention curve (Brooks&Corey or Van Genuchten Mualem)

Soil

TROLL 4 relies on multiple soil information to parametrize the water retention curves with Brooks&Corey or Van Genuchten Mualem:

  • number of layers
  • layer thicness (m)
  • Silt: proportion of silt (%)
  • Clay: proportion of clay (%)
  • Sand: proportion of sand (%)
  • SOC: soil organic content (g)
  • DBD: dry bulk density (kg)
  • pH
  • CEC: cation exchange capacity (cmolC)

Soil

read_tsv("data/day2/R1_input_pedology.txt")
# A tibble: 5 × 8
  layer_thickness proportion_Silt proportion_Clay proportion_Sand   SOC   DBD
            <dbl>           <dbl>           <dbl>           <dbl> <dbl> <dbl>
1             0.1            2.64            60.1            37.3  2.54  1.12
2             0.4            2.64            60.1            37.3  2.54  1.12
3             1              2.64            60.1            37.3  2.54  1.12
4             2.5            2.64            60.1            37.3  2.54  1.12
5            12.1            2.64            60.1            37.3  2.54  1.12
# ℹ 2 more variables: pH <dbl>, CEC <dbl>

Soil

read_tsv("data/day2/R1_input_pedology.txt") %>% 
  mutate(depth = cumsum(layer_thickness)) %>% 
  gather(variable, value, -depth) %>% 
  ggplot(aes(depth, value)) + geom_line() + geom_point() +
  facet_wrap(~ variable, scales = "free_x") + coord_flip() +
  scale_x_reverse() + theme_bw() + xlab("") + ylab("")

Outputs

Daily carbon and water fluxes in atmopshere and soil.

Files

list.files("data/day2/")
 [1] "R1_0_LAIdynamics.txt"    "R1_0_LAImature.txt"     
 [3] "R1_0_LAIold.txt"         "R1_0_LAIyoung.txt"      
 [5] "R1_0_soilproperties.txt" "R1_0_sumstats.txt"      
 [7] "R1_0_water_balance.txt"  "R1_input_climate.txt"   
 [9] "R1_input_daily.txt"      "R1_input_global.txt"    
[11] "R1_input_pedology.txt"   "R1_input_species.txt"   

Forest dynamics

read_tsv("data/day2/R1_0_sumstats.txt") %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(date, agb/10^3)) +
  geom_line() +
  theme_bw() + xlab("") + ylab("AGB [t/ha]")

GPP: Gross Primary Productivity

read_tsv("data/day2/R1_0_sumstats.txt") %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(date, gpp*10^2*365/10^3)) +
  geom_line() +
  theme_bw() + xlab("") + ylab("GPP [kgC/m2/yr]")

GPP - Seasonality

read_tsv("data/day2/R1_0_sumstats.txt") %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(yday(date), gpp*10^2*365/10^3)) +
  geom_line(aes(group = year(date))) +
  theme_bw() + geom_smooth() + xlab("Day") + ylab("GPP [kgC/m2/yr]")

GPP - Drivers

par <- read_tsv("data/day2/R1_input_daily.txt") %>% 
  group_by(DayJulian) %>% 
  mutate(par = Snet*2.27*60*30/10^3/10^3/4) %>% 
  summarise(par = sum(par))
g <- read_tsv("data/day2/R1_0_sumstats.txt") %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  mutate(par = par$par) %>% 
  ggplot(aes(par, gpp*10^2*365/10^3)) +
  geom_point() +
  theme_bw() +
  geom_smooth() + xlab("PAR [mol/m2/day]") + ylab("GPP [kgC/m2/yr]")

GPP - Drivers

g

LAI: Leaf Area Index

read_tsv("data/day2/R1_0_LAIdynamics.txt") %>%
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(date, LAI)) +
  geom_line() + theme_bw() + xlab("") + ylab("LAI [m2/m2]")

LAI age

g <- list(
  young = read_tsv("data/day2/R1_0_LAIyoung.txt", col_types = "d"),
  mature = read_tsv("data/day2/R1_0_LAImature.txt", col_types = "d"),
  old = read_tsv("data/day2/R1_0_LAIold.txt", col_types = "d")
) %>% bind_rows(.id = "age") %>% 
  gather(height, lai, -age, -iter) %>% 
  group_by(iter, age) %>% 
  summarise(lai = sum(lai)) %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(date, lai, col = age)) +
  geom_line() +
  theme_bw() + xlab("") + ylab("LAI [m2/m2]")

LAI age

g

Litterfall

read_tsv("data/day2/R1_0_sumstats.txt") %>% 
  mutate(date = as_date("1999-01-01") + iter) %>% 
  ggplot(aes(date, litterfall*365)) +
  geom_line() +
  theme_bw() + xlab("") + ylab("Litterfall [MgC/ha/yr]")

Water balance

Water balance

\[\Delta SWC = P-I-Q-E-T-L\]

Water balance

g <- read_tsv("data/day2/R1_0_water_balance.txt") %>%
  mutate(date = as_date("1999-01-01") + iter) %>%
  mutate(precipitation = precipitation/10^3) %>% 
  select(date, precipitation, interception, 
         transpitation_0, transpitation_1, transpitation_2,
         transpitation_3, transpitation_4,
         leak, evaporation, runoff) %>% 
  gather(component, value, -date) %>% 
  mutate(value = value*1000) %>% 
  filter(value >= 1) %>% 
  ggplot(aes(date, value, fill = component)) +
  geom_col() +
  theme_bw() +
  scale_y_log10() + xlab("") + ylab("[mm]") + 
  facet_wrap(~ component) + scale_fill_discrete(guide = "none")

Water balance

g

Water balance

g <- read_tsv("data/day2/R1_0_water_balance.txt") %>%
  mutate(date = as_date("1999-01-01") + iter) %>%
  mutate(transpiration = (transpitation_0 + transpitation_1 + transpitation_2 + 
                          transpitation_3 + transpitation_4)*1000,
         "soil evaporation" = evaporation*1000,
         "canopy evaporation" = (precipitation/1000-throughfall)*1000) %>% 
  select(date, transpiration, "soil evaporation", "canopy evaporation") %>% 
  gather(component, value, -date) %>% 
  ggplot(aes(date, value)) +
  geom_col() +
  facet_wrap(~ component, nrow = 3) +
  theme_bw() + xlab("") + ylab("[mm]")

Water balance

g 

ET : Evapotranspiration

read_tsv("data/day2/R1_0_water_balance.txt") %>%
  mutate(date = as_date("1999-01-01") + iter) %>%
  mutate(et = (transpitation_0 + transpitation_1 + transpitation_2 + 
                 transpitation_3 + transpitation_4 + evaporation + 
                 (precipitation/1000-throughfall))*1000) %>% 
  ggplot(aes(date, et)) +
  geom_line() +   theme_bw() + xlab("") + ylab("ET [mm]")

SWC: Soil Water Content

read_tsv("data/day2/R1_0_water_balance.txt") %>%
  mutate(date = as_date("1999-01-01") + iter) %>%
  select(date, starts_with("SWC")) %>% 
  gather(depth, swc, -date) %>% 
  separate(depth, c("x", "depth")) %>% 
  ggplot(aes(date, swc, col = depth)) +
  geom_line() + theme_bw() + xlab("") + ylab("SWC [m3/m3]")

Soil water content

resolution <- 0.1
layers_depths <- c(0.1, 0.5, 1.5, 4, 16.1)
dry_months <- 10:11
wet_months <- 6:7
depths <- tibble(depth = layers_depths) %>%
  mutate(depth_max = cumsum(depth)) %>% 
  mutate(depth_min = lag(depth_max)) %>% 
  mutate(depth_min = ifelse(is.na(depth_min), 0, depth_min))
data <- read_tsv("data/day2/R1_0_water_balance.txt") %>% 
  mutate(date = as_date("1999/01/01") + iter) %>% 
  select(date, starts_with("SWC"), starts_with("SWP")) %>% 
  gather(variable, value, -date) %>% 
  separate(variable, c("variable", "layer"), convert = TRUE) %>% 
  mutate(depth_max = depths$depth_max[layer+1]) %>% 
  mutate(depth_min = depths$depth_min[layer+1]) %>% 
  select(date, depth_min, depth_max, variable, value)
raster <- data %>% 
  mutate(depth_min = round(depth_min, 1),
         depth_max = round(depth_max, 1)) %>% 
  rowwise() %>% 
  mutate(depth = list(seq(depth_min, 
                          depth_max, 
                          by = resolution))) %>% 
  unnest(depth)

Soil water content

raster %>% 
  filter(variable == "SWC") %>% 
  ggplot(aes(date, depth, fill = value)) +
  geom_raster(vjust = 0) + theme_bw() +
  scale_y_reverse() + xlab("") + ylab("Depth [ m ]") +
  scale_fill_viridis_c(expression("SWC ["~m^3~m^3~"]"),
                       direction = -1)

Soil water profiles

g <- data %>% 
  filter(variable == "SWC") %>% 
  filter(month(date) %in% c(dry_months, wet_months)) %>% 
  mutate(season = ifelse(month(date) %in% dry_months, "dry", "wet")) %>% 
  group_by(variable, depth_min, depth_max, season) %>% 
  summarise(m = median(value), 
            ll = quantile(value, .05), 
            hh = quantile(value, .95)) %>% 
  mutate(depth = (depth_min + depth_max)/2) %>% 
  ggplot(aes(depth, m, col = season)) +
  geom_smooth(se = FALSE) +
  geom_linerange(aes(ymin = ll, ymax = hh)) +
  geom_point() +
  theme_bw() +
  coord_flip() +
  scale_x_reverse("Depth [ m ]") +
  ylab(expression("SWC ["~m^3~m^3~"]"))

Soil water profiles

g

References

Maréchaux, Isabelle, Fabian Jörg Fischer, Sylvain Schmitt, and Jérôme Chave. 2025. “TROLL 4.0: Representing Water and Carbon Fluxes, Leaf Phenology, and Intraspecific Trait Variation in a Mixed-Species Individual-Based Forest Dynamics Model Part 1: Model Description.” Geoscientific Model Development 18 (16): 5143–5204. https://doi.org/10.5194/gmd-18-5143-2025.