Optirrig is a one-dimensional conceptual model that ties together thermal-time crop development, soil–plant water balance, and management rules to reproduce irrigated systems. It tracks plant status (LAI, biomass, yield), soil water reserves (RU), and nitrogen demand while applying irrigation rules that can be user-defined or phenology-driven.
OptirrigCORE is the R interface that wraps the Optirrig engine and makes the workflow reproducible:
Use OptirrigCORE for research, teaching exercises, rapid prototyping of decision rules, or operational campaign planning. The default assets get you started fast, and every component can be swapped for custom data whenever you need it.
This vignette walks you through a minimal OptirrigCORE workflow. You will learn how to:
remotes if missing).run_model() and capture the outputs.remotes.To install OptirrigCORE from the INRAE GitLab repository:
install.packages("remotes")
remotes::install_git("https://forge.inrae.fr/OptirrigHIVE/OptirrigCORE.git", build_vignettes = TRUE, dependencies = TRUE)Once installed, load the package:
OptirrigCORE requires daily weather data (rainfall, temperature, radiation, and reference evapotranspiration) to simulate crop growth and soil water balance.
In this vignette, we shall use example datasets shipped with the package, representing a maize experiment conducted in 2021 at the Lavalette experimental site (Montpellier, France). Using data() avoids file paths and makes the example fully reproducible.
data("MAIZE_21LAVALETTE_clim")
str(MAIZE_21LAVALETTE_clim)
#> spc_tbl_ [365 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
#> $ Date: chr [1:365] "01/01/2021" "02/01/2021" "03/01/2021" "04/01/2021" ...
#> $ P : num [1:365] 0 0.5 1.5 0 0.5 0 0 0.5 0 0 ...
#> $ etp : num [1:365] 0 0.3 0.6 0 0 0 0.1 0.1 1.1 0.9 ...
#> $ Rg : num [1:365] 711 178 346 643 734 816 716 837 497 234 ...
#> $ T : num [1:365] 3.7 2.6 3.6 2.4 0.9 -1.3 -1 1.4 4.7 2.6 ...
#> - attr(*, "spec")=List of 3
#> ..$ cols :List of 5
#> .. ..$ Date: list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#> .. ..$ P : list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. ..$ etp : list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. ..$ Rg : list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. ..$ T : list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> ..$ default: list()
#> .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
#> ..$ delim : chr ","
#> ..- attr(*, "class")= chr "col_spec"
#> - attr(*, "problems")=<pointer: (nil)>The climate dataset follows the standard Optirrig input format, with the columns below. In practice the model expects a daily time step and a date column that fully covers your simulation period.
| Column Name | Description |
|---|---|
Date |
Date in “dd/mm/yyyy” format |
P |
Daily rainfall (mm) |
T |
Daily temperature (°C) |
Rg |
Daily global solar radiation (J/cm²) |
etp |
Daily reference evapotranspiration (mm) |
As the model is a thermal-time driven, the Date column
is essential to assure that the climate data aligns with the simulation
period. In fact simulations period need to be fully included in the
climate data.
Internally, OptirrigCORE derives as . Both and are handled in , while is a dimensionless intercepted fraction and biomass outputs (, ) are expressed in .
If your Date column is stored as text, it’s fine:
OptirrigCORE will parse common formats (including
"dd/mm/yyyy"). Still, it’s a good habit to verify it
once:
As an optional step, you can also provide an irrigation input. The model can simulate irrigation using either simple decision rules (e.g., water-stress thresholds, water-turns, quotas, etc …) or a predefined calendar.
In this ex post example, we use the observed irrigation calendar from the 2021 experiment.
data("MAIZE_21LAVALETTE_irrig")
str(MAIZE_21LAVALETTE_irrig)
#> spc_tbl_ [12 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
#> $ Date: chr [1:12] "15/06/2021" "17/06/2021" "25/06/2021" "30/06/2021" ...
#> $ Dose: num [1:12] 16.1 9.1 20.2 18.6 5.6 19.4 18.5 27.2 59.9 4 ...
#> - attr(*, "spec")=List of 3
#> ..$ cols :List of 2
#> .. ..$ Date: list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#> .. ..$ Dose: list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> ..$ default: list()
#> .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
#> ..$ delim : chr ","
#> ..- attr(*, "class")= chr "col_spec"
#> - attr(*, "problems")=<pointer: (nil)>Now that climate (and optionally irrigation) data are loaded, the next step is to describe what you want to simulate: crop, soil, dates, and a few key options.
OptirrigCORE accepts several input styles (CSV files,
data.frames, or lists). For a single scenario, the simplest
and most explicit approach is to build a flat named list directly in R.
You can provide climate/irrigation either as in-memory tables (as we do
here) or as paths to files.
In this vignette we’ll keep it minimal:
"maize") and one soil profile
("Lavalette" in this example);OptirrigCORE ships with a set of pre-configured crops and soils that
can be used directly, without any additional setup. When you specify a
crop name (crop) or a soil code/profile (soil)
in your input object, OptirrigCORE automatically loads the corresponding
parameter set behind the scenes.
👉 You do not need to worry about the underlying parameterization: the name simply acts as a key pointing to a consistent, ready-to-use configuration.
Importantly, these presets are not restrictive: even when using built-in crops or soils, you can override or add any parameter you want at simulation setup time to explore alternative assumptions or management options.
Available crops : The table below lists the crops currently available in the installed version of the package. The value to use in the crop argument corresponds directly to the crop name shown.
| cultures | Calibration Status |
|---|---|
| maize | need |
| beetroot | done |
| potato | need |
| soybean | need |
| wheat | need |
For example, setting crop = “maize” automatically selects the parameter set describing maize phenology, growth, and water demand.
Available soils : Similarly, OptirrigCORE includes several standard soil profiles. Each soil is identified by a short code that reflects its dominant texture.
| sols | Description |
|---|---|
| C | Clay |
| CL | Clay Loam |
| L | Loam |
| LSa | Loamy Sand |
| Sa | Sand |
| SaC | Sandy Clay |
| SaCL | Sandy Clay Loam |
| SaL | Sandy Loam |
| SiC | Silty Clay |
| SiCL | Silty Clay Loam |
| SiL | Silty Loam |
For instance, using soil = “CL” automatically selects a clay loam soil profile with its associated hydraulic properties.
💡 Tip These built-in crops and soils are ideal for getting started and exploring the model. Advanced users can later introduce custom parameterizations if needed.
At minimum, a scenario need to include:
| Parameter | Description |
|---|---|
| run_id | Unique scenario name |
| crop | Crop identifier available in the internal library (e.g.,
maize, beetroot) |
| soil | Soil identifier from the bundled database or a custom soil (e.g.,
CL, SaC) |
| date_start | Simulation start date (string “dd/mm/yyyy”) |
| date_end | Simulation end date (string “dd/mm/yyyy”) |
| date_sowing | Sowing date start (string “dd/mm/yyyy”) |
| date_harvest | Harvest date end (string “dd/mm/yyyy”) |
| zmax | Maximum rooting depth (m) |
| ratio_R_init | Initial relative soil water reserve (0–1) |
| climate | Climate input (path or data.frame) |
| irrigation | Optional irrigation input (path or data.frame) |
| irrig_mod | 0 = rainfed/calendar, 1 = rule-driven irrigation |
Therefore, several methods exist to prepare simulation scenarios and
use them with run_model(). The function can accept either a
path to a CSV file, a data.frame, or a list,
provided that the objects already exist in the R environment and follow
the expected input format.
path: a CSV file where each row corresponds to a distinct simulation scenario. data.frame: each row of the data frame corresponds to a distinct simulation scenario. list: either a flat named list describing one scenario, or a named list of scenarios for batch runs.
For a first run, a flat list is the most direct option, as shown below:
# Example: flat list (all parameters at the same level)
input <- list(
run_id = "Run_1",
crop = "maize",
soil = "CL",
climate = MAIZE_21LAVALETTE_clim,
irrigation = MAIZE_21LAVALETTE_irrig,
fertilisation = NA,
date_sowing = "20/04/2021",
date_harvest = "06/09/2021",
date_start = "01/04/2021",
date_end = "06/09/2021",
irrig_mod = 0,
zmax = 1.2,
ratio_R_init = 0.7,
irrig_strategy_start = "25/04/2021",
irrig_strategy_end = "01/08/2021"
)
str(input)
#> List of 15
#> $ run_id : chr "Run_1"
#> $ crop : chr "maize"
#> $ soil : chr "CL"
#> $ climate : spc_tbl_ [365 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
#> ..$ Date: chr [1:365] "01/01/2021" "02/01/2021" "03/01/2021" "04/01/2021" ...
#> ..$ P : num [1:365] 0 0.5 1.5 0 0.5 0 0 0.5 0 0 ...
#> ..$ etp : num [1:365] 0 0.3 0.6 0 0 0 0.1 0.1 1.1 0.9 ...
#> ..$ Rg : num [1:365] 711 178 346 643 734 816 716 837 497 234 ...
#> ..$ T : num [1:365] 3.7 2.6 3.6 2.4 0.9 -1.3 -1 1.4 4.7 2.6 ...
#> ..- attr(*, "spec")=List of 3
#> .. ..$ cols :List of 5
#> .. .. ..$ Date: list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#> .. .. ..$ P : list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. .. ..$ etp : list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. .. ..$ Rg : list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. .. ..$ T : list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. ..$ default: list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
#> .. ..$ delim : chr ","
#> .. ..- attr(*, "class")= chr "col_spec"
#> ..- attr(*, "problems")=<pointer: (nil)>
#> $ irrigation : spc_tbl_ [12 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
#> ..$ Date: chr [1:12] "15/06/2021" "17/06/2021" "25/06/2021" "30/06/2021" ...
#> ..$ Dose: num [1:12] 16.1 9.1 20.2 18.6 5.6 19.4 18.5 27.2 59.9 4 ...
#> ..- attr(*, "spec")=List of 3
#> .. ..$ cols :List of 2
#> .. .. ..$ Date: list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
#> .. .. ..$ Dose: list()
#> .. .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
#> .. ..$ default: list()
#> .. .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
#> .. ..$ delim : chr ","
#> .. ..- attr(*, "class")= chr "col_spec"
#> ..- attr(*, "problems")=<pointer: (nil)>
#> $ fertilisation : logi NA
#> $ date_sowing : chr "20/04/2021"
#> $ date_harvest : chr "06/09/2021"
#> $ date_start : chr "01/04/2021"
#> $ date_end : chr "06/09/2021"
#> $ irrig_mod : num 0
#> $ zmax : num 1.2
#> $ ratio_R_init : num 0.7
#> $ irrig_strategy_start: chr "25/04/2021"
#> $ irrig_strategy_end : chr "01/08/2021"For batch runs, you can also pass a named list of scenario objects or a parameter table with one row per simulation.
If you’re unsure which parameters exist (or want to see defaults), these help pages are the quickest entry point: https://optirrighive.pages-forge.inrae.fr/OptirrigCORE/dev/articles/v00_params_overview.html
For more details on run_model() and plot(),
check the docs:
run_model() can take a path to a parameter CSV, a
data.frame, or a list.
Here we pass the in-memory object created as a simple list (one
scenario).
The returned object is a named list with one element per scenario
(run_id), each containing time series (states/fluxes) and
summary variables that you can plot or post-process.
outputs <- run_model(
input,
options = list(log = FALSE, force = TRUE, return_outputs = TRUE, save = FALSE)
)
str(outputs)
#> List of 1
#> $ Run_1:'data.frame': 159 obs. of 123 variables:
#> ..$ Date : Date[1:159], format: "2021-04-01" "2021-04-02" ...
#> ..$ P : num [1:159] 0 0 0 0 0 0 0 0 9.5 9 ...
#> ..$ ETP : num [1:159] 2.9 3.2 3.8 3.1 3.6 3.9 3.2 2.7 2.1 0.2 ...
#> ..$ Rg : num [1:159] 2259 2145 2405 2116 2214 ...
#> ..$ T : num [1:159] 10.8 13.3 15.3 12.1 13.8 14.1 7.8 7.4 11.8 9.6 ...
#> ..$ TT : num [1:159] 4.8 12.1 21.4 27.5 35.3 43.4 45.2 46.6 52.4 56 ...
#> ..$ TT_sowing : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TT_LAI : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TT_root : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TT_LAI_norm : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ alpha : num [1:159] 1 1 1 1 1 1 1 1 1 1 ...
#> ..$ LT : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ LAIp : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ LAI : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ degred : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ zrootp : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ Tcrit : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ B : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ Bp : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ B_root : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ Bp_root : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_sowing : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_harvest : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_emerg : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ iday_root_install : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ iday_crop_dev : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ stress_ES : num [1:159] 1 0 0 0 0 0 0 0 0 1 ...
#> ..$ stress_TP : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ stress_ETR : num [1:159] 1 0 0 0 0 0 0 0 0 1 ...
#> ..$ stress_T : num [1:159] 1 1 1 1 1 1 1 1 1 1 ...
#> ..$ stress_LAI : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ stress_B : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ stress_noci_LAI : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ stress_noci_B : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ taum : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ crac : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ ks_root : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ dzrootp : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ dzroot : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ zroot : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ case : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ Kc : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ IR : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ IRp : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ PAR : num [1:159] 1130 1072 1202 1058 1107 ...
#> ..$ Cp : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ sugar_accumulation: num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ Y_sugar : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ Yp_sugar : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_strategy : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_constraints : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_no_irrig : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ iday_waterturn : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ iday_tbis : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ W_need : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ RU_daily_perc : num [1:159] NA NA NA NA NA NA NA NA NA NA ...
#> ..$ W_eff : num [1:159] 2.72 2.72 2.72 2.72 2.72 ...
#> ..$ ET0 : num [1:159] 2.9 3.2 3.8 3.1 3.6 3.9 3.2 2.7 2.1 0.2 ...
#> ..$ ES0 : num [1:159] 0.87 0.96 1.14 0.93 1.08 1.17 0.96 0.81 0.63 0.06 ...
#> ..$ TP0 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ ETM : num [1:159] 0.87 0.96 1.14 0.93 1.08 1.17 0.96 0.81 0.63 0.06 ...
#> ..$ theta1_init : num [1:159] 0.00226 0 0 0 0 ...
#> ..$ theta2_init : num [1:159] 0.00226 0.00226 0.00226 0.00226 0.00226 ...
#> ..$ theta3_init : num [1:159] 0.00226 0.00226 0.00226 0.00226 0.00226 ...
#> ..$ z1_init : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ z2_init : num [1:159] 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 ...
#> ..$ z3_init : num [1:159] 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 ...
#> ..$ z1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ z2 : num [1:159] 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 0.15 ...
#> ..$ z3 : num [1:159] 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 1.05 ...
#> ..$ R1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ R2 : num [1:159] 0.339 0.339 0.339 0.339 0.339 ...
#> ..$ R3 : num [1:159] 2.38 2.38 2.38 2.38 2.38 ...
#> ..$ theta1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ theta2 : num [1:159] 0.00226 0.00226 0.00226 0.00226 0.00226 ...
#> ..$ theta3 : num [1:159] 0.00226 0.00226 0.00226 0.00226 0.00226 ...
#> ..$ I1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ Kd1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ Kd2 : num [1:159] 4.4e-48 4.4e-48 4.4e-48 4.4e-48 4.4e-48 ...
#> ..$ Kd3 : num [1:159] 4.4e-48 4.4e-48 4.4e-48 4.4e-48 4.4e-48 ...
#> ..$ Rgravity1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ Rgravity2 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ Rgravity3 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ d1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ d2 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ d3 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ RU1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ RU2 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ RU3 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ KES1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ KES2 : num [1:159] 0 0 0 0 0 0 0 0 1 1 ...
#> ..$ ES1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ ES2 : num [1:159] 0 0 0 0 0 0 0 0 0.63 0.06 ...
#> ..$ ES : num [1:159] 0 0 0 0 0 0 0 0 0.63 0.06 ...
#> ..$ KTP1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ KTP2 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TP1 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TP2 : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> ..$ TP : num [1:159] 0 0 0 0 0 0 0 0 0 0 ...
#> .. [list output truncated]
#> - attr(*, "class")= chr [1:2] "model_outputs" "list"✅ Tip: for batch runs, you can either add more rows to a parameter table or pass a named list of scenario objects in a single call.
Simulation results are regular R objects, so any plotting stack
works. The chunk below builds a simple ggplot2 dashboard
showing leaf area, biomass, yield, soil water reserve, and
irrigation.
library(ggplot2)
library(patchwork)
df <- as.data.frame(outputs[[1]])
df$Scenario <- names(outputs)[1]
b_max <- max(df$Bp, na.rm = TRUE)
y_max <- max(df$Yp, na.rm = TRUE)
df$Biomass_rel <- if (b_max > 0) 100 * df$B / b_max else NA_real_
df$Biomass_pot_rel <- if (b_max > 0) 100 * df$Bp / b_max else NA_real_
df$Yield_rel <- if (y_max > 0) 100 * df$Y / y_max else NA_real_
df$Yield_pot_rel <- if (y_max > 0) 100 * df$Yp / y_max else NA_real_
df$RU_rel <- if ("RU_daily_perc" %in% names(df) && !all(is.na(df$RU_daily_perc))) {
df$RU_daily_perc
} else {
df$RU
}
df$CumDose <- cumsum(pmax(df$I1, 0))
dose_scale <- if (max(df$CumDose, na.rm = TRUE) > 0 && max(df$I1, na.rm = TRUE) > 0) {
max(df$I1, na.rm = TRUE) / max(df$CumDose, na.rm = TRUE)
} else {
1
}
base_theme <- theme_minimal(base_size = 11) +
theme(
legend.position = "top",
panel.grid.minor = element_blank()
)
p_lai <- ggplot(df, aes(Date)) +
geom_line(aes(y = LAI, colour = "Observed"), linewidth = 0.8) +
geom_line(aes(y = LAIp, colour = "Potential"), linewidth = 0.7, linetype = 2) +
scale_colour_manual(values = c("Observed" = "#245b69", "Potential" = "#333333")) +
labs(title = "Leaf Area Index", y = "LAI", x = NULL, colour = NULL) +
base_theme
p_biomass <- ggplot(df, aes(Date, Biomass_rel)) +
geom_line(colour = "#245b69", linewidth = 0.8) +
geom_line(aes(y = Biomass_pot_rel), colour = "#333333", linewidth = 0.7, linetype = 2) +
labs(title = "Biomass (relative to max potential)", y = "% of max potential", x = NULL) +
base_theme
p_ru <- ggplot(df, aes(Date, RU_rel)) +
geom_line(colour = "#111111", linewidth = 0.8) +
labs(title = "Useful Water Reserve (%)", y = "RU (%)", x = NULL) +
base_theme
p_yield <- ggplot(df, aes(Date, Yield_rel)) +
geom_line(colour = "#245b69", linewidth = 0.8) +
geom_line(aes(y = Yield_pot_rel), colour = "#333333", linewidth = 0.7, linetype = 2) +
labs(title = "Yield (relative to max potential)", y = "% of max potential", x = NULL) +
base_theme
p_irrig <- ggplot(df, aes(Date)) +
geom_point(aes(y = I1), colour = "#245b69", size = 1.8, alpha = 0.8) +
geom_line(aes(y = CumDose * dose_scale), colour = "#111111", linewidth = 0.8) +
scale_y_continuous(
name = "Irrigation (mm)",
sec.axis = sec_axis(~ . / dose_scale, name = "Cumulative dose (mm)")
) +
labs(title = "Irrigation & Cumulative Dose", x = "Date") +
base_theme
p_lai / ((p_biomass | p_ru) / (p_yield | p_irrig))
#> Warning: Removed 32 rows containing missing values or values outside the scale range
#> (`geom_line()`).
#> Warning: Removed 19 rows containing missing values or values outside the scale range
#> (`geom_line()`).
#> Warning: Removed 32 rows containing missing values or values outside the scale range
#> (`geom_line()`).
#> Warning: Removed 19 rows containing missing values or values outside the scale range
#> (`geom_line()`).
#> Warning: Removed 32 rows containing missing values or values outside the scale range
#> (`geom_line()`).
#> Warning: Removed 19 rows containing missing values or values outside the scale range
#> (`geom_line()`).With less than thirty lines of code you installed OptirrigCORE, described a scenario, ran the model, and visualized the main state variables.
Where to go next?
irrig_mod = 1).inst/config.soil.*.