Skip to content

Commit 7d05d16

Browse files
authored
Merge pull request #122 from VirtualPlantLab/add-modellist-to-mapping-tests-+-automated-model-generation-from-vector
Working version of a modellist to mapping conversion function, add a way to pass in vectors to a status in MultiScale, doc page for tips and workarounds
2 parents 659562d + 81d2572 commit 7d05d16

9 files changed

Lines changed: 623 additions & 2 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ FLoops = "cc61a311-1640-44b5-9fba-1b764f453329"
1212
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1313
MultiScaleTreeGraph = "dd4a991b-8a45-4075-bede-262ee62d5583"
1414
PlantMeteo = "4630fe09-e0fb-4da5-a846-781cb73437b6"
15+
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
1516
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1617
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1718
Term = "22787eb5-b846-44ae-b979-8e399b8463ab"
@@ -25,6 +26,7 @@ FLoops = "0.2"
2526
Markdown = "1.9"
2627
MultiScaleTreeGraph = "0.13, 0.14"
2728
PlantMeteo = "0.6"
29+
SHA = "0.7.0"
2830
Statistics = "1.9"
2931
Tables = "1"
3032
Term = "1, 2"
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Tips and workarounds
2+
3+
## PlantSimEngine is actively being developed
4+
5+
PlantSimEngine, despite the somewhat abstract codebase and generic simulation ambitions, is quite grounded in reality. There IS a desire to accomodate for a wide range of possible simulations, without constraining the user too much, but most features are developed on an as-needed basis, and grow out of necessity, partly from the requirements of an increasingly complex and refined implementation of an oil palm model, [XPalm](https://github.com/PalmStudio/XPalm.jl).
6+
7+
Since the oil palm model is actively being developed, and some features aren't ready in PlantSimEngine, or require a lot of rewriting that we're not certain would be worth it (especially if it ends up constraining the codebase or what the user can do), some workarounds and shortcuts are occasionally used to circumvent a limitation.
8+
9+
There are also a couple of features that are quick hacks or that are meant for quick and dirty prototyping, not for production.
10+
11+
We'll list a few of them here, and will likely add some entry in the future listing some built-in limitations or implicit expectations of the package.
12+
13+
## Making use of past states in multi-scale simulations
14+
15+
It is possible to make use of the value of a variable in the past simulation timestep via the `PreviousTimeStep` mechanism in the mapping API (In fact, as mentioned elsewhere, it is the default way to break undesirable cyclic dependencies that can come up when coupling models, see : [Avoiding cyclic dependencies](@ref)).
16+
17+
However, it is not possible to go beyond that through the mapping API. Something like `PreviousTimeStep(PreviousTimeStep(PreviousTimeStep(:carbon_biomass)))` is not supported. Don't do that.
18+
19+
One way to access prior variable states is simply to write an ad hoc model that stores a few values into an array or however many variables you might need, which you can then feed into other models that might need it.
20+
21+
## Having a variable simultaneously as input and output of a model
22+
23+
One current limitation of `PlantSimEngine` that can be occasionally awkward is that using the same variable name as input and output in a single model is unsupported.
24+
25+
(On a related note : it is not possible to have two variables with the same name *in the same scale*. They are considered as the same variable.)
26+
27+
The reason being that it is usually impossible to automatically determine how the coupling is supposed to work out, when other dependencies latch onto such a model. The user would have to explicitely declare some order of simulation between several models, and some amount of programmer work would also be necessary to implement that extra API feature into `PlantSimEngine`.
28+
29+
We haven't found an approach that was fully satisfactory from both a code simplicity and an API convenience POV. Especially when prototyping and adding in new models, as that might require redeclaring the simulation order for those specific variables. Ideas that came to mind felt like they might constrain the codebase for a more complex API, without enough benefit to justify it.
30+
31+
The current workaround, while a little annoying, is very simple : *rename one of the variables*. It is not ideal, of course, as it means you might not be able to use a predefined model 'out of the box', but it does not have any of the tradeoffs and constrained mentioned above.
32+
33+
## Passing in a vector in a mapping status at a specific scale
34+
35+
You may have noticed that sometimes a vector (1-dimensional array) variable is passed into the `status` component of a `ModelList` in documentation examples (An example here with cumulative thermal time : [Model switching](@ref)).
36+
37+
This is practical for simple simulations, or when quickly prototyping, to avoid having to write a model specifically for it. Whatever models make use of that variable are provided with one element corresponding to the current timestep every iteration.
38+
39+
In multi-scale simulations, this feature is also supported, though not part of the main API. The way outputs and statuses work is a little different, so that little convenience feature is not as straightforward.
40+
41+
It is more brittle, makes use of not-recommended Julia metaprogramming features (`eval()`), fiddles with global variables, might not work outside of a REPL environment and is not tested for more complex interactions, so it may interact badly with variables that are mapped to different scales or in bizarre dependency couplings.
42+
43+
Due to, uh, implementation quirks, the way to use this is as follows :
44+
45+
Call the function `replace_mapping_status_vectors_with_generated_models(mapping_with_vectors_in_status, timestep_model_organ_level, nsteps)`on your mapping.
46+
47+
It will parse your mapping, generate custom models to store and feed the vector values each timestep, and return the new mapping you can then use for your simulation. It also slips in a couple of internal models that provide the timestep index to these models (so note that symbols `:current_timestep` and `:next_timestep` will be declared for that mapping). You can decide which scale/organ level you want those models to be in via the `timestep_model_organ_level`parameter. `nsteps`is used as a sanity check, and expects you to provide the amount of simulation timesteps.
48+
49+
!!! note
50+
Only subtypes of AbstractVector present in statuses will be affected. In some cases, meteo values might need a small conversion. For instance :
51+
```
52+
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
53+
status(TT_cu=cumsum(meteo_day.TT),)```
54+
55+
cumsum(meteo_day.TT) actually returns a CSV.SentinelArray.ChainedVectors{T, Vector{T}}, which is not a subtype of AbstractVector.
56+
Replacing it with Vector(cumsum(meteo_day.TT)) will provide an adequate type.
57+
58+
This feature is likely to break in simulations that make use of planned future features (such as mixing models with different timesteps), without guarantee of a fix on a short notice. Again, bear in mind it is mostly a convenient shortcut for prototyping, when doing multi-scale simulations.

src/PlantSimEngine.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import MultiScaleTreeGraph: symbol, node_id
2222
# To compute mean:
2323
import Statistics
2424

25+
# For avoiding name conflicts when generating models from status vectors
26+
import SHA: sha1
27+
2528
using PlantMeteo
2629

2730
# UninitializedVar + PreviousTimeStep:
@@ -89,6 +92,9 @@ include("run.jl")
8992
# Fitting
9093
include("evaluation/fit.jl")
9194

95+
# Utilities for mapping initialisation
96+
include("mtg/mapping/model_generation_from_status_vectors.jl")
97+
9298
# Examples
9399
include("examples_import.jl")
94100

src/mtg/initialisation.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,13 @@ automatically passed as is.
307307
"""
308308
function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false)
309309

310+
# Ensure the user called the model generation function to handle vectors passed into a status
311+
# before we keep going
312+
(organ_with_vector, no_vectors_found) = (check_statuses_contain_no_remaining_vectors(mapping))
313+
if !no_vectors_found
314+
@assert false "Error : Mapping status at $organ_with_vector level contains a vector. If this was intentional, call the function generate_models_from_status_vectors on your mapping before calling run!. And bear in mind this is not meant for production. If this wasn't intentional, then it's likely an issue on the mapping definition, or an unusual model."
315+
end
316+
310317
# Get the status of each node by node type, pre-initialised considering multi-scale variables:
311318
statuses, status_templates, reverse_multiscale_mapping, vars_need_init =
312319
init_statuses(mtg, mapping, first(hard_dependencies(mapping; verbose=false)); type_promotion=type_promotion, verbose=verbose, check=check)

0 commit comments

Comments
 (0)