Model switching
One of the main objective of PlantSimEngine is allowing users to switch between model implementations for a given process without making any change to the PlantSimEngine codebase.
The package was designed around this idea to make easy changes easy and efficient. Switch models in the ModelList, and call the run! function again. No other changes are required if no new variables are introduced.
A first simulation as a starting point
With a working environment, let's create a ModelList with several models from the example scripts in the examples folder:
Importing the models from the scripts:
using PlantSimEngine
# Import the examples defined in the `Examples` sub-module:
using PlantSimEngine.ExamplesCoupling the models in a ModelList:
models = ModelList(
ToyLAIModel(),
Beer(0.5),
ToyRUEGrowthModel(0.2),
status=(TT_cu=cumsum(meteo_day.TT),),
)We can the simulation by calling the run! function with meteorology data. Here we use an example data set:
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)We can now run the simulation:
output_initial = run!(models, meteo_day)TimeStepTable{Status{(:TT_cu, :LAI, :aPPFD,...}(365 x 5):
╭─────┬──────────┬────────────┬───────────┬────────────┬───────────────────╮
│ Row │ TT_cu │ LAI │ aPPFD │ biomass │ biomass_increment │
│ │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │
├─────┼──────────┼────────────┼───────────┼────────────┼───────────────────┤
│ 1 │ 0.0 │ 0.00554988 │ 0.0396961 │ 0.00793922 │ 0.00793922 │
│ 2 │ 0.0 │ 0.00554988 │ 0.02173 │ 0.0122852 │ 0.004346 │
│ 3 │ 0.0 │ 0.00554988 │ 0.0314899 │ 0.0185832 │ 0.00629798 │
│ 4 │ 0.0 │ 0.00554988 │ 0.0390834 │ 0.0263999 │ 0.00781668 │
│ 5 │ 0.0 │ 0.00554988 │ 0.0454514 │ 0.0354902 │ 0.00909028 │
│ 6 │ 0.0 │ 0.00554988 │ 0.0472677 │ 0.0449437 │ 0.00945354 │
│ 7 │ 0.0 │ 0.00554988 │ 0.04346 │ 0.0536357 │ 0.00869201 │
│ 8 │ 0.0 │ 0.00554988 │ 0.0469832 │ 0.0630324 │ 0.00939665 │
│ 9 │ 0.0 │ 0.00554988 │ 0.0291703 │ 0.0688664 │ 0.00583406 │
│ 10 │ 0.0 │ 0.00554988 │ 0.0140052 │ 0.0716675 │ 0.00280105 │
│ 11 │ 0.0 │ 0.00554988 │ 0.0505283 │ 0.0817731 │ 0.0101057 │
│ 12 │ 0.0 │ 0.00554988 │ 0.0405277 │ 0.0898787 │ 0.00810554 │
│ 13 │ 0.5625 │ 0.00557831 │ 0.0297814 │ 0.095835 │ 0.00595629 │
│ 14 │ 0.945833 │ 0.00559777 │ 0.0433269 │ 0.1045 │ 0.00866538 │
│ 15 │ 0.979167 │ 0.00559946 │ 0.0470271 │ 0.113906 │ 0.00940542 │
│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │
╰─────┴──────────┴────────────┴───────────┴────────────┴───────────────────╯
350 rows omitted
Switching one model in the simulation
Now what if we want to switch the model that computes growth ? We can do this by simply replacing the model in the ModelList, and PlantSimEngine will automatically update the dependency graph, and adapt the simulation to the new model.
Let's switch ToyRUEGrowthModel with ToyAssimGrowthModel:
models2 = ModelList(
ToyLAIModel(),
Beer(0.5),
ToyAssimGrowthModel(), # This was `ToyRUEGrowthModel(0.2)` before
status=(TT_cu=cumsum(meteo_day.TT),),
)ToyAssimGrowthModel is a little bit more complex than ToyRUEGrowthModel](@ref), as it also computes the maintenance and growth respiration of the plant, so it has more parameters (we use the default values here).
We can run a new simulation and see that the simulation's results are different from the previous simulation:
output_updated = run!(models2, meteo_day)TimeStepTable{Status{(:TT_cu, :LAI, :aPPFD,...}(365 x 8):
╭─────┬──────────┬────────────┬───────────┬─────────────────────┬────────────┬──
│ Row │ TT_cu │ LAI │ aPPFD │ carbon_assimilation │ Rm │ ⋯
│ │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ ⋯
├─────┼──────────┼────────────┼───────────┼─────────────────────┼────────────┼──
│ 1 │ 0.0 │ 0.00554988 │ 0.0396961 │ 0.00793922 │ 0.00396961 │ ⋯
│ 2 │ 0.0 │ 0.00554988 │ 0.02173 │ 0.004346 │ 0.002173 │ ⋯
│ 3 │ 0.0 │ 0.00554988 │ 0.0314899 │ 0.00629798 │ 0.00314899 │ ⋯
│ 4 │ 0.0 │ 0.00554988 │ 0.0390834 │ 0.00781668 │ 0.00390834 │ ⋯
│ 5 │ 0.0 │ 0.00554988 │ 0.0454514 │ 0.00909028 │ 0.00454514 │ ⋯
│ 6 │ 0.0 │ 0.00554988 │ 0.0472677 │ 0.00945354 │ 0.00472677 │ ⋯
│ 7 │ 0.0 │ 0.00554988 │ 0.04346 │ 0.00869201 │ 0.004346 │ ⋯
│ 8 │ 0.0 │ 0.00554988 │ 0.0469832 │ 0.00939665 │ 0.00469832 │ ⋯
│ 9 │ 0.0 │ 0.00554988 │ 0.0291703 │ 0.00583406 │ 0.00291703 │ ⋯
│ 10 │ 0.0 │ 0.00554988 │ 0.0140052 │ 0.00280105 │ 0.00140052 │ ⋯
│ 11 │ 0.0 │ 0.00554988 │ 0.0505283 │ 0.0101057 │ 0.00505283 │ ⋯
│ 12 │ 0.0 │ 0.00554988 │ 0.0405277 │ 0.00810554 │ 0.00405277 │ ⋯
│ 13 │ 0.5625 │ 0.00557831 │ 0.0297814 │ 0.00595629 │ 0.00297814 │ ⋯
│ 14 │ 0.945833 │ 0.00559777 │ 0.0433269 │ 0.00866538 │ 0.00433269 │ ⋯
│ 15 │ 0.979167 │ 0.00559946 │ 0.0470271 │ 0.00940542 │ 0.00470271 │ ⋯
│ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋮ │ ⋱
╰─────┴──────────┴────────────┴───────────┴─────────────────────┴────────────┴──
3 columns and 350 rows omitted
And that's it! We can switch between models without changing the code, and without having to recompute the dependency graph manually. This is a very powerful feature of PlantSimEngine!💪
This was a very standard but straightforward example. Sometimes other models will require to add other models to the ModelList. For example ToyAssimGrowthModel could have required a maintenance respiration model. In this case PlantSimEngine will indicate what kind of model is required for the simulation.
In our example we replaced what we call a soft-dependency coupling, but the same principle applies to hard-dependencies. Hard and Soft dependencies are concepts related to model coupling, and are discussed in more detail in Standard model coupling and Coupling more complex models.