Well Capsis is also developed in my lab so we have some things in common, but also a lot that differ.
A common thing is that it provides a standard workflow for implementing new models.
The main difference is that in PlantSimEngine, you provide a list of models (yours or those from the community), and the package automatically builds the dependency graph for you. This seems quite simple but it is very powerful, because it allows you to prototype and test hypothesis very quickly by switching sub-models on the fly, without having to change anything in the code. The difference in other models is that if you change a sub-model (say a model for photosynthesis), you have to manage the inputs and outputs in the larger code-base by hand (e.g. the coupling with an energy balance and a stomatal conductance model), often also by adding an option with an if/else by hand. A second thing is that it allows you to reduce the degree of freedom in your model by forcing some parts to observations (i.e. switch off parts of your model).
A second difference is that it is multiscale if you need it. This means you can simulate processes at any scale, and the package handles most of the complexities for you. For example, one of our use cases is carbon allocation in the plant. Depending on the plant, the first hypothesis is that all assimilate from photosynthesis go through a common pull and are then redistributed to the organs for growth depending on their demand. A second hypothesis is that organs close to the leaf are served first. The first hypothesis is simulated at the plant scale, the second at a finer scale (phytomer, growth unit or axis). With PlantSimEngine you can test both hypotheses very quickly like so (pseudo-code):
mapping = Dict(
"Plant" => (
MultiScaleModel(
model=CommonPullCAllocationModel([parameters goes here...]),
mapping=[
:carbon_assimilation => ["Leaf"],
:carbon_demand => ["Leaf", "Internode"],
:carbon_allocation => ["Leaf", "Internode"]
],
),
),
"Internode" => (
CDemandModel(...),
),
"Leaf" => (
PhotosynthesisModel(...),
CDemandModel(...),
),
)
- Spatialized pull (e.g. phytomer):
mapping = Dict(
"Phytomer" => (
SpatializedCAllocationModel([parameters goes here...]),
# multiscale variables are managed by the model directly by traversing the multiscale tree graph
),
),
"Internode" => (
CDemandModel(...),
),
"Leaf" => (
PhotosynthesisModel(...),
CDemandModel(...),
),
)
And PlantSimEngine will resolve the dependency graph of your sub-models for you, and return the full model ready for simulation. It also manages the parallelization of the models for you.