Skip to content

PlantSimEngine โ€‹

Build Status Coverage ColPrac: Contributor's Guide on Collaborative Practices for Community Packages Aqua QA DOI JOSS

Overview โ€‹

PlantSimEngine is a comprehensive package for simulating and modelling plants, soil and atmosphere. It provides tools to prototype, evaluate, test, and deploy plant/crop models at any scale. At its core, PlantSimEngine is designed with a strong emphasis on performance and efficiency.

The package defines a framework for declaring processes and implementing associated models for their simulation.

It focuses on key aspects of simulation and modeling such as:

  • Easy definition of new processes, such as light interception, photosynthesis, growth, soil water transfer...

  • Fast, interactive prototyping of models, with constraints to help users avoid errors, but sensible defaults to avoid over-complicating the model writing process

  • No hassle, the package manages automatically input and output variables, time-steps, objects, soft and hard coupling of models with a dependency graph

  • Switch between models without changing any code, with a simple syntax to define the model to use for a given process

  • Reduce the degrees of freedom by fixing variables, passing measurements, or using a simpler model for a given process

  • ๐Ÿš€(very) fast computation ๐Ÿš€, think of 100th of nanoseconds for one model, two coupled models (see this benchmark script), or the full energy balance of a leaf using PlantBiophysics.jl that uses PlantSimEngine

  • Out of the box Sequential, Parallel (Multi-threaded) or Distributed (Multi-Process) computations over objects, time-steps and independent processes (thanks to Floops.jl)

  • Easily scalable, with methods for computing over objects, time-steps and even Multi-Scale Tree Graphs

  • Composable, allowing the use of any types as inputs such as Unitful to propagate units, or MonteCarloMeasurements.jl to propagate measurement error

Installation โ€‹

To install the package, enter the Julia package manager mode by pressing ] in the REPL, and execute the following command:

julia
add PlantSimEngine

To use the package, execute this command from the Julia REPL:

julia
using PlantSimEngine

Example usage โ€‹

The package is designed to be easy to use, and to help users avoid errors when implementing, coupling and simulating models.

Simple example โ€‹

Here's a simple example of a model that simulates the growth of a plant, using a simple exponential growth model:

julia
# ] add PlantSimEngine
using PlantSimEngine

# Import the examples defined in the `Examples` sub-module
using PlantSimEngine.Examples

# Define the model:
model = ModelList(
    ToyLAIModel(),
    status=(TT_cu=1.0:2000.0,), # Pass the cumulated degree-days as input to the model
)

run!(model) # run the model

status(model) # extract the status, i.e. the output of the model
TimeStepTable{Status{(:TT_cu, :LAI), Tuple{...}(2000 x 2):
โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Row โ”‚   TT_cu โ”‚        LAI โ”‚
โ”‚     โ”‚ Float64 โ”‚    Float64 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚   1 โ”‚     1.0 โ”‚ 0.00560052 โ”‚
โ”‚   2 โ”‚     2.0 โ”‚ 0.00565163 โ”‚
โ”‚   3 โ”‚     3.0 โ”‚ 0.00570321 โ”‚
โ”‚   4 โ”‚     4.0 โ”‚ 0.00575526 โ”‚
โ”‚   5 โ”‚     5.0 โ”‚ 0.00580778 โ”‚
โ”‚   6 โ”‚     6.0 โ”‚ 0.00586078 โ”‚
โ”‚   7 โ”‚     7.0 โ”‚ 0.00591426 โ”‚
โ”‚   8 โ”‚     8.0 โ”‚ 0.00596823 โ”‚
โ”‚   9 โ”‚     9.0 โ”‚ 0.00602269 โ”‚
โ”‚  10 โ”‚    10.0 โ”‚ 0.00607765 โ”‚
โ”‚  11 โ”‚    11.0 โ”‚ 0.00613311 โ”‚
โ”‚  12 โ”‚    12.0 โ”‚ 0.00618908 โ”‚
โ”‚  13 โ”‚    13.0 โ”‚ 0.00624556 โ”‚
โ”‚  14 โ”‚    14.0 โ”‚ 0.00630255 โ”‚
โ”‚  15 โ”‚    15.0 โ”‚ 0.00636006 โ”‚
โ”‚  โ‹ฎ  โ”‚    โ‹ฎ    โ”‚     โ‹ฎ      โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
             1985 rows omitted

Note The ToyLAIModel is available from the examples folder, and is a simple exponential growth model. It is used here for the sake of simplicity, but you can use any model you want, as long as it follows PlantSimEngine interface.

Of course you can plot the outputs quite easily:

julia
# ] add CairoMakie
using CairoMakie

lines(model[:TT_cu], model[:LAI], color=:green, axis=(ylabel="LAI (mยฒ mโปยฒ)", xlabel="Cumulated growing degree days since sowing (ยฐC)"))

Model coupling โ€‹

Model coupling is done automatically by the package, and is based on the dependency graph between the models. To couple models, we just have to add them to the ModelList. For example, let's couple the ToyLAIModel with a model for light interception based on Beer's law:

julia
# ] add PlantSimEngine, DataFrames, CSV
using PlantSimEngine, PlantMeteo, DataFrames, CSV

# Import the examples defined in the `Examples` sub-module
using PlantSimEngine.Examples

# Import the example meteorological data:
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)

# Define the list of models for coupling:
model2 = ModelList(
    ToyLAIModel(),
    Beer(0.6),
    status=(TT_cu=cumsum(meteo_day[:, :TT]),),  # Pass the cumulated degree-days as input to `ToyLAIModel`, this could also be done using another model
)

# Run the simulation:
run!(model2, meteo_day)

status(model2)
TimeStepTable{Status{(:TT_cu, :LAI, :aPPFD)...}(365 x 3):
โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Row โ”‚    TT_cu โ”‚        LAI โ”‚     aPPFD โ”‚
โ”‚     โ”‚  Float64 โ”‚    Float64 โ”‚   Float64 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚   1 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0476221 โ”‚
โ”‚   2 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0260688 โ”‚
โ”‚   3 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0377774 โ”‚
โ”‚   4 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0468871 โ”‚
โ”‚   5 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0545266 โ”‚
โ”‚   6 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0567055 โ”‚
โ”‚   7 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0521376 โ”‚
โ”‚   8 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0563642 โ”‚
โ”‚   9 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0349947 โ”‚
โ”‚  10 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0168016 โ”‚
โ”‚  11 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0606171 โ”‚
โ”‚  12 โ”‚      0.0 โ”‚ 0.00554988 โ”‚ 0.0486197 โ”‚
โ”‚  13 โ”‚   0.5625 โ”‚ 0.00557831 โ”‚ 0.0357278 โ”‚
โ”‚  14 โ”‚ 0.945833 โ”‚ 0.00559777 โ”‚ 0.0519777 โ”‚
โ”‚  15 โ”‚ 0.979167 โ”‚ 0.00559946 โ”‚ 0.0564167 โ”‚
โ”‚  โ‹ฎ  โ”‚    โ‹ฎ     โ”‚     โ‹ฎ      โ”‚     โ‹ฎ     โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
                           350 rows omitted

The ModelList couples the models by automatically computing the dependency graph of the models. The resulting dependency graph is:

โ•ญโ”€โ”€โ”€โ”€ Dependency graph โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚  โ•ญโ”€โ”€โ”€โ”€ LAI_Dynamic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ  โ”‚
โ”‚  โ”‚  โ•ญโ”€โ”€โ”€โ”€ Main model โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ                              โ”‚  โ”‚
โ”‚  โ”‚  โ”‚  Process: LAI_Dynamic  โ”‚                              โ”‚  โ”‚
โ”‚  โ”‚  โ”‚  Model: ToyLAIModel    โ”‚                              โ”‚  โ”‚
โ”‚  โ”‚  โ”‚  Dep: nothing          โ”‚                              โ”‚  โ”‚
โ”‚  โ”‚  โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ                              โ”‚  โ”‚
โ”‚  โ”‚                  โ”‚  โ•ญโ”€โ”€โ”€โ”€ Soft-coupled model โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ  โ”‚  โ”‚
โ”‚  โ”‚                  โ”‚  โ”‚  Process: light_interception    โ”‚  โ”‚  โ”‚
โ”‚  โ”‚                  โ””โ”€โ”€โ”‚  Model: Beer                    โ”‚  โ”‚  โ”‚
โ”‚  โ”‚                     โ”‚  Dep: (LAI_Dynamic = (:LAI,),)  โ”‚  โ”‚  โ”‚
โ”‚  โ”‚                     โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ  โ”‚  โ”‚
โ”‚  โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

We can plot the results by indexing the model with the variable name (e.g. model2[:LAI]):

julia
using CairoMakie

fig = Figure(resolution=(800, 600))
ax = Axis(fig[1, 1], ylabel="LAI (mยฒ mโปยฒ)")
lines!(ax, model2[:TT_cu], model2[:LAI], color=:mediumseagreen)

ax2 = Axis(fig[2, 1], xlabel="Cumulated growing degree days since sowing (ยฐC)", ylabel="aPPFD (mol mโปยฒ dโปยน)")
lines!(ax2, model2[:TT_cu], model2[:aPPFD], color=:firebrick1)

fig

Multiscale modelling โ€‹

See the Multi-scale modeling section for more details.

The package is designed to be easily scalable, and can be used to simulate models at different scales. For example, you can simulate a model at the leaf scale, and then couple it with models at any other scale, e.g. internode, plant, soil, scene scales. Here's an example of a simple model that simulates plant growth using sub-models operating at different scales:

julia
mapping = Dict(
    "Scene" => ToyDegreeDaysCumulModel(),
    "Plant" => (
        MultiScaleModel(
            model=ToyLAIModel(),
            mapping=[
                :TT_cu => "Scene",
            ],
        ),
        Beer(0.6),
        MultiScaleModel(
            model=ToyAssimModel(),
            mapping=[:soil_water_content => "Soil"],
        ),
        MultiScaleModel(
            model=ToyCAllocationModel(),
            mapping=[
                :carbon_demand => ["Leaf", "Internode"],
                :carbon_allocation => ["Leaf", "Internode"]
            ],
        ),
        MultiScaleModel(
            model=ToyPlantRmModel(),
            mapping=[:Rm_organs => ["Leaf" => :Rm, "Internode" => :Rm],],
        ),
    ),
    "Internode" => (
        MultiScaleModel(
            model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0),
            mapping=[:TT => "Scene",],
        ),
        MultiScaleModel(
            model=ToyInternodeEmergence(TT_emergence=20.0),
            mapping=[:TT_cu => "Scene"],
        ),
        ToyMaintenanceRespirationModel(1.5, 0.06, 25.0, 0.6, 0.004),
        Status(carbon_biomass=1.0)
    ),
    "Leaf" => (
        MultiScaleModel(
            model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0),
            mapping=[:TT => "Scene",],
        ),
        ToyMaintenanceRespirationModel(2.1, 0.06, 25.0, 1.0, 0.025),
        Status(carbon_biomass=1.0)
    ),
    "Soil" => (
        ToySoilWaterModel(),
    ),
);

We can import an example plant from the package:

julia
mtg = import_mtg_example()
/ 1: Scene
โ”œโ”€ / 2: Soil
โ””โ”€ + 3: Plant
   โ””โ”€ / 4: Internode
      โ”œโ”€ + 5: Leaf
      โ””โ”€ < 6: Internode
         โ””โ”€ + 7: Leaf

Make a fake meteorological data:

julia
meteo = Weather(
    [
    Atmosphere(T=20.0, Wind=1.0, Rh=0.65, Ri_PAR_f=300.0),
    Atmosphere(T=25.0, Wind=0.5, Rh=0.8, Ri_PAR_f=500.0)
]
);

And run the simulation:

julia
out_vars = Dict(
    "Scene" => (:TT_cu,),
    "Plant" => (:carbon_allocation, :carbon_assimilation, :soil_water_content, :aPPFD, :TT_cu, :LAI),
    "Leaf" => (:carbon_demand, :carbon_allocation),
    "Internode" => (:carbon_demand, :carbon_allocation),
    "Soil" => (:soil_water_content,),
)

out = run!(mtg, mapping, meteo, outputs=out_vars, executor=SequentialEx());

We can then extract the outputs in a DataFrame and sort them:

julia
using DataFrames
df_out = outputs(out, DataFrame)
sort!(df_out, [:timestep, :node])

An example output of a multiscale simulation is shown in the documentation of PlantBiophysics.jl:

Projects that use PlantSimEngine โ€‹

Take a look at these projects that use PlantSimEngine:

Make it yours โ€‹

The package is developed so anyone can easily implement plant/crop models, use it freely and as you want thanks to its MIT license.

If you develop such tools and it is not on the list yet, please make a PR or contact me so we can add it! ๐Ÿ˜ƒ