API
Index
PlantSimEngine.Examples
PlantSimEngine.AbstractModel
PlantSimEngine.AbstractNodeMapping
PlantSimEngine.DataFormat
PlantSimEngine.DependencyGraph
PlantSimEngine.DependencyTrait
PlantSimEngine.GraphSimulation
PlantSimEngine.MappedVar
PlantSimEngine.ModelList
PlantSimEngine.MultiNodeMapping
PlantSimEngine.MultiScaleModel
PlantSimEngine.ObjectDependencyTrait
PlantSimEngine.PreviousTimeStep
PlantSimEngine.RefVariable
PlantSimEngine.RefVector
PlantSimEngine.SelfNodeMapping
PlantSimEngine.SingleNodeMapping
PlantSimEngine.Status
PlantSimEngine.TimeStepDependencyTrait
PlantSimEngine.UninitializedVar
PlantSimEngine.EF
PlantSimEngine.NRMSE
PlantSimEngine.RMSE
PlantSimEngine.add_mapped_variables_with_outputs_as_inputs!
PlantSimEngine.add_model_vars
PlantSimEngine.add_organ!
PlantSimEngine.check_dimensions
PlantSimEngine.convert_reference_values!
PlantSimEngine.convert_vars
PlantSimEngine.convert_vars!
PlantSimEngine.convert_vars!
PlantSimEngine.default_variables_from_mapping
PlantSimEngine.dep
PlantSimEngine.diff_vars
PlantSimEngine.dr
PlantSimEngine.draw_guide
PlantSimEngine.draw_panel
PlantSimEngine.drop_process
PlantSimEngine.fit
PlantSimEngine.fit
PlantSimEngine.flatten_vars
PlantSimEngine.get_mapping
PlantSimEngine.get_model_nodes
PlantSimEngine.get_models
PlantSimEngine.get_multiscale_default_value
PlantSimEngine.get_nsteps
PlantSimEngine.get_status
PlantSimEngine.get_vars_not_propagated
PlantSimEngine.hard_dependencies
PlantSimEngine.homogeneous_ts_kwargs
PlantSimEngine.homogeneous_ts_kwargs
PlantSimEngine.init_node_status!
PlantSimEngine.init_simulation
PlantSimEngine.init_status!
PlantSimEngine.init_statuses
PlantSimEngine.init_variables
PlantSimEngine.init_variables_manual
PlantSimEngine.initialise_all_as_hard_dependency_node
PlantSimEngine.inputs
PlantSimEngine.inputs
PlantSimEngine.is_graph_cyclic
PlantSimEngine.is_initialized
PlantSimEngine.mapped_variables
PlantSimEngine.mapped_variables_no_outputs_from_other_scale
PlantSimEngine.model_
PlantSimEngine.object_parallelizable
PlantSimEngine.outputs
PlantSimEngine.outputs
PlantSimEngine.outputs
PlantSimEngine.parallelizable
PlantSimEngine.pre_allocate_outputs
PlantSimEngine.propagate_values!
PlantSimEngine.ref_var
PlantSimEngine.reverse_mapping
PlantSimEngine.run!
PlantSimEngine.run!
PlantSimEngine.save_results!
PlantSimEngine.search_inputs_in_multiscale_output
PlantSimEngine.search_inputs_in_output
PlantSimEngine.soft_dependencies
PlantSimEngine.status
PlantSimEngine.status_from_template
PlantSimEngine.timestep_parallelizable
PlantSimEngine.to_initialize
PlantSimEngine.to_initialize
PlantSimEngine.transform_single_node_mapped_variables_as_self_node_output!
PlantSimEngine.traverse_dependency_graph
PlantSimEngine.traverse_dependency_graph!
PlantSimEngine.traverse_dependency_graph!
PlantSimEngine.variables
PlantSimEngine.variables
PlantSimEngine.variables
PlantSimEngine.variables
PlantSimEngine.variables_multiscale
PlantSimEngine.variables_outputs_from_other_scale
PlantSimEngine.variables_typed
PlantSimEngine.vars_not_init_
PlantSimEngine.@process
API documentation
PlantMeteo.TimeStepTable
— MethodTimeStepTable{Status}(df::DataFrame)
Method to build a TimeStepTable
(from PlantMeteo.jl) from a DataFrame
, but with each row being a Status
.
Note
ModelList
uses TimeStepTable{Status}
by default (see examples below).
Examples
using PlantSimEngine, DataFrames
# A TimeStepTable from a DataFrame:
df = DataFrame(
Tₗ=[25.0, 26.0],
aPPFD=[1000.0, 1200.0],
Cₛ=[400.0, 400.0],
Dₗ=[1.0, 1.2],
)
TimeStepTable{Status}(df)
# A leaf with several values for at least one of its variable will automatically use
# TimeStepTable{Status} with the time steps:
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status=(var1=15.0, var2=0.3)
)
# The status of the leaf is a TimeStepTable:
status(models)
# Of course we can also create a TimeStepTable with Status manually:
TimeStepTable(
[
Status(Tₗ=25.0, aPPFD=1000.0, Cₛ=400.0, Dₗ=1.0),
Status(Tₗ=26.0, aPPFD=1200.0, Cₛ=400.0, Dₗ=1.2),
]
)
PlantSimEngine.AbstractModel
— TypeAbstract model type. All models are subtypes of this one.
PlantSimEngine.ModelList
— TypeModelList(models::M, status::S)
ModelList(;
status=nothing,
init_fun::Function=init_fun_default,
type_promotion=nothing,
variables_check=true,
kwargs...
)
List the models for a simulation (models
), and does all boilerplate for variable initialization, type promotion, time steps handling.
Arguments
models
: a list of models. Usually given as aNamedTuple
, but can be any other structure that
implements getproperty
.
status
: a structure containing the initializations for the variables of the models. Usually a NamedTuple
when given as a kwarg, or any structure that implements the Tables interface from Tables.jl
(e.g. DataFrame, see details).
nsteps=nothing
: the number of time steps to pre-allocated. Ifnothing
, the number of time steps is deduced from the status (or 1 if no status is given).init_fun
: a function that initializes the status based on a vector of NamedTuples (see details).type_promotion
: optional type conversion for the variables with default values.
nothing
by default, i.e. no conversion. Note that conversion is not applied to the variables input by the user as kwargs
(need to do it manually). Should be provided as a Dict with current type as keys and new type as values.
variables_check=true
: check that all needed variables are initialized by the user.kwargs
: the models, named after the process they simulate.
Details
The argument init_fun
is set by default to init_fun_default
which initializes the status with a TimeStepTable
of Status
structures.
If you change init_fun
by another function, make sure the type you are using (i.e. in place of TimeStepTable
) implements the Tables.jl
interface (e.g. DataFrame does). And if you still use TimeStepTable
but only change Status
, make sure the type you give is indexable using the dot synthax (e.g. x.var
).
If you need to input a custom Type for the status and make your users able to only partially initialize the status
field in the input, you'll have to implement a method for add_model_vars!
, a function that adds the models variables to the type in case it is not fully initialized. The default method is compatible with any type that implements the Tables.jl
interface (e.g. DataFrame), and NamedTuples
.
Note that ModelList
makes a copy of the input status
if it does not list all needed variables.
Examples
We'll use the dummy models from the dummy.jl
in the examples folder of the package. It implements three dummy processes: Process1Model
, Process2Model
and Process3Model
, with one model implementation each: Process1Model
, Process2Model
and Process3Model
.
julia> using PlantSimEngine;
Including example processes and models:
julia> using PlantSimEngine.Examples;
julia> models = ModelList(process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model());
[ Info: Some variables must be initialized before simulation: (process1 = (:var1, :var2), process2 = (:var1,)) (see `to_initialize()`)
julia> typeof(models)
ModelList{@NamedTuple{process1::Process1Model, process2::Process2Model, process3::Process3Model}, TimeStepTable{Status{(:var5, :var4, :var6, :var1, :var3, :var2), NTuple{6, Base.RefValue{Float64}}}}, Tuple{}}
No variables were given as keyword arguments, that means that the status of the ModelList is not set yet, and all variables are initialized to their default values given in the inputs and outputs (usually typemin(Type)
, i.e. -Inf
for floating point numbers). This component cannot be simulated yet.
To know which variables we need to initialize for a simulation, we use to_initialize
:
julia> to_initialize(models)
(process1 = (:var1, :var2), process2 = (:var1,))
We can now provide values for these variables in the status
field, and simulate the ModelList
, e.g. for process3
(coupled with process1
and process2
):
julia> models = ModelList(process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model(), status=(var1=15.0, var2=0.3));
julia> meteo = Atmosphere(T = 22.0, Wind = 0.8333, P = 101.325, Rh = 0.4490995);
julia> run!(models,meteo)
julia> models[:var6]
1-element Vector{Float64}:
58.0138985
If we want to use special types for the variables, we can use the type_promotion
argument:
julia> models = ModelList(process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model(), status=(var1=15.0, var2=0.3), type_promotion = Dict(Float64 => Float32));
We used type_promotion
to force the status into Float32:
julia> [typeof(models[i][1]) for i in keys(status(models))]
6-element Vector{DataType}:
Float32
Float32
Float32
Float64
Float64
Float32
But we see that only the default variables (the ones that are not given in the status arguments) were converted to Float32, the two other variables that we gave were not converted. This is because we want to give the ability to users to give any type for the variables they provide in the status. If we want all variables to be converted to Float32, we can pass them as Float32:
julia> models = ModelList(process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model(), status=(var1=15.0f0, var2=0.3f0), type_promotion = Dict(Float64 => Float32));
We used type_promotion
to force the status into Float32:
julia> [typeof(models[i][1]) for i in keys(status(models))]
6-element Vector{DataType}:
Float32
Float32
Float32
Float32
Float32
Float32
We can also use DataFrame as the status type:
julia> using DataFrames;
julia> df = DataFrame(:var1 => [13.747, 13.8], :var2 => [1.0, 1.0]);
julia> m = ModelList(process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model(), status=df, init_fun=x -> DataFrame(x));
Note that we use init_fun
to force the status into a DataFrame
, otherwise it would be automatically converted into a TimeStepTable{Status}
.
julia> status(m)
2×6 DataFrame
Row │ var5 var4 var6 var1 var3 var2
│ Float64 Float64 Float64 Float64 Float64 Float64
─────┼──────────────────────────────────────────────────────
1 │ -Inf -Inf -Inf 13.747 -Inf 1.0
2 │ -Inf -Inf -Inf 13.8 -Inf 1.0
Note that computations will be slower using DataFrame, so if performance is an issue, use TimeStepTable instead (or a NamedTuple as shown in the example).
PlantSimEngine.MultiScaleModel
— TypeMultiScaleModel(model, mapping)
A structure to make a model multi-scale. It defines a mapping between the variables of a model and the nodes symbols from which the values are taken from.
Arguments
model<:AbstractModel
: the model to make multi-scalemapping<:Vector{Pair{Symbol,Union{AbstractString,Vector{AbstractString}}}}
: a vector of pairs of symbols and strings or vectors of strings
The mapping can be of the form:
[:variable_name => "Plant"]
: We take one value from the Plant node[:variable_name => ["Leaf"]]
: We take a vector of values from the Leaf nodes[:variable_name => ["Leaf", "Internode"]]
: We take a vector of values from the Leaf and Internode nodes[:variable_name => "Plant" => :variable_name_in_plant_scale]
: We take one value from another variable name in the Plant node[:variable_name => ["Leaf" => :variable_name_1, "Internode" => :variable_name_2]]
: We take a vector of values from the Leaf and Internode nodes with different names[PreviousTimeStep(:variable_name) => ...]
: We flag the variable to be initialized with the value from the previous time step, and we do not use it to build the dep graph[:variable_name => :variable_name_from_another_model]
: We take the value from another model at the same scale but rename it[PreviousTimeStep(:variable_name),]
: We just flag the variable as a PreviousTimeStep to not use it to build the dep graph
Details about the different forms:
- The variable
variable_name
of the model will be taken from thePlant
node, assuming only one node has thePlant
symbol.
In this case the value available from the status will be a scalar, and so the user must guaranty that only one node of type Plant
is available in the MTG.
- The variable
variable_name
of the model will be taken from theLeaf
nodes. Notice it is given as a vector, indicating that the values will be taken
from all the nodes of type Leaf
. The model should be able to handle a vector of values. Note that even if there is only one node of type Leaf
, the value will be taken as a vector of one element.
- The variable
variable_name
of the model will be taken from theLeaf
andInternode
nodes. The values will be taken from all the nodes of typeLeaf
and Internode
.
- The variable
variable_name
of the model will be taken from the variable calledvariable_name_in_plant_scale
in thePlant
node. This is useful
when the variable name in the model is different from the variable name in the scale it is taken from.
The variable
variable_name
of the model will be taken from the variable calledvariable_name_1
in theLeaf
node andvariable_name_2
in theInternode
node.The variable
variable_name
of the model uses the value computed on the previous time-step. This implies that the variable is not used to build the dependency graph
because the dependency graph only applies on the current time-step. This is used to avoid circular dependencies when a variable depends on itself. The value can be initialized in the Status if needed.
The variable
variable_name
of the model will be taken from another model at the same scale, but with another variable name.The variable
variable_name
of the model is just flagged as a PreviousTimeStep variable, so it is not used to build the dependency graph.
Note that the mapping does not make any copy of the values, it only references them. This means that if the values are updated in the status of one node, they will be updated in the other nodes.
Examples
julia> using PlantSimEngine;
Including example processes and models:
julia> using PlantSimEngine.Examples;
Let's take a model:
julia> model = ToyCAllocationModel()
ToyCAllocationModel()
We can make it multi-scale by defining a mapping between the variables of the model and the nodes symbols from which the values are taken from:
For example, if the carbon_allocation
comes from the Leaf
and Internode
nodes, we can define the mapping as follows:
julia> mapping = [:carbon_allocation => ["Leaf", "Internode"]]
1-element Vector{Pair{Symbol, Vector{String}}}:
:carbon_allocation => ["Leaf", "Internode"]
The mapping is a vector of pairs of symbols and strings or vectors of strings. In this case, we have only one pair to define the mapping between the carbon_allocation
variable and the Leaf
and Internode
nodes.
We can now make the model multi-scale by passing the model and the mapping to the MultiScaleModel
constructor :
julia> multiscale_model = PlantSimEngine.MultiScaleModel(model, mapping)
MultiScaleModel{ToyCAllocationModel, Vector{Pair{Union{Symbol, PreviousTimeStep}, Union{Pair{String, Symbol}, Vector{Pair{String, Symbol}}}}}}(ToyCAllocationModel(), Pair{Union{Symbol, PreviousTimeStep}, Union{Pair{String, Symbol}, Vector{Pair{String, Symbol}}}}[:carbon_allocation => ["Leaf" => :carbon_allocation, "Internode" => :carbon_allocation]])
We can access the mapping and the model:
julia> PlantSimEngine.mapping_(multiscale_model)
1-element Vector{Pair{Union{Symbol, PreviousTimeStep}, Union{Pair{String, Symbol}, Vector{Pair{String, Symbol}}}}}:
:carbon_allocation => ["Leaf" => :carbon_allocation, "Internode" => :carbon_allocation]
julia> PlantSimEngine.model_(multiscale_model)
ToyCAllocationModel()
PlantSimEngine.PreviousTimeStep
— TypePreviousTimeStep(variable)
A structure to manually flag a variable in a model to use the value computed on the previous time-step. This implies that the variable is not used to build the dependency graph because the dependency graph only applies on the current time-step. This is used to avoid circular dependencies when a variable depends on itself. The value can be initialized in the Status if needed.
The process is added when building the MultiScaleModel, to avoid conflicts between processes with the same variable name. For exemple one process can define a variable :carbon_biomass
as a PreviousTimeStep
, but the othe process would use the variable as a dependency for the current time-step (and it would be fine because theyr don't share the same issue of cyclic dependency).
PlantSimEngine.Status
— TypeStatus(vars)
Status type used to store the values of the variables during simulation. It is mainly used as the structure to store the variables in the TimeStepRow
of a TimeStepTable
(see PlantMeteo.jl
docs) of a ModelList
.
Most of the code is taken from MasonProtter/MutableNamedTuples.jl, so Status
is a MutableNamedTuples with a few modifications, so in essence, it is a stuct that stores a NamedTuple
of the references to the values of the variables, which makes it mutable.
Examples
A leaf with one value for all variables will make a status with one time step:
julia> using PlantSimEngine
julia> st = PlantSimEngine.Status(Rₛ=13.747, sky_fraction=1.0, d=0.03, aPPFD=1500.0);
All these indexing methods are valid:
julia> st[:Rₛ]
13.747
julia> st.Rₛ
13.747
julia> st[1]
13.747
Setting a Status variable is very easy:
julia> st[:Rₛ] = 20.0
20.0
julia> st.Rₛ = 21.0
21.0
julia> st[1] = 22.0
22.0
PlantSimEngine.EF
— MethodEF(obs,sim)
Returns the Efficiency Factor between observations obs
and simulations sim
using NSE (Nash-Sutcliffe efficiency) model. More information can be found at https://en.wikipedia.org/wiki/Nash%E2%80%93Sutcliffemodelefficiency_coefficient.
The closer to 1 the better.
Examples
using PlantSimEngine
obs = [1.0, 2.0, 3.0]
sim = [1.1, 2.1, 3.1]
EF(obs, sim)
PlantSimEngine.NRMSE
— MethodNRMSE(obs,sim)
Returns the Normalized Root Mean Squared Error between observations obs
and simulations sim
. Normalization is performed using division by observations range (max-min).
Examples
using PlantSimEngine
obs = [1.0, 2.0, 3.0]
sim = [1.1, 2.1, 3.1]
NRMSE(obs, sim)
PlantSimEngine.RMSE
— MethodRMSE(obs,sim)
Returns the Root Mean Squared Error between observations obs
and simulations sim
.
The closer to 0 the better.
Examples
using PlantSimEngine
obs = [1.0, 2.0, 3.0]
sim = [1.1, 2.1, 3.1]
RMSE(obs, sim)
PlantSimEngine.add_organ!
— Methodadd_organ!(node::MultiScaleTreeGraph.Node, sim_object, link, symbol, scale; index=0, id=MultiScaleTreeGraph.new_id(MultiScaleTreeGraph.get_root(node)), attributes=Dict{Symbol,Any}(), check=true)
Add an organ to the graph, automatically taking care of initialising the status of the organ (multiscale-)variables.
This function should be called from a model that implements organ emergence, for example in function of thermal time.
Arguments
node
: the node to which the organ is added (the parent organ of the new organ)sim_object
: the simulation object, e.g. theGraphSimulation
object from theextra
argument of a model.link
: the link type between the new node and the organ:"<"
: the new node is following the parent organ"+"
: the new node is branching the parent organ"/"
: the new node is decomposing the parent organ, i.e. we change scale
symbol
: the symbol of the organ, e.g."Leaf"
scale
: the scale of the organ, e.g.2
.index
: the index of the organ, e.g.1
. The index may be used to easily identify branching order, or growth unit index on the axis. It is different from the nodeid
that is unique.id
: the unique id of the new node. If not provided, a new id is generated.attributes
: the attributes of the new node. If not provided, an empty dictionary is used.check
: a boolean indicating if variables initialisation should be checked. Passed toinit_node_status!
.
Returns
status
: the status of the new node
Examples
See the ToyInternodeEmergence
example model from the Examples
module (also found in the examples
folder), or the test-mtg-dynamic.jl
test file for an example usage.
PlantSimEngine.dep
— Functiondep(m::ModelList, nsteps=1; verbose::Bool=true)
dep(mapping::Dict{String,T}; verbose=true)
Get the model dependency graph given a ModelList or a multiscale model mapping. If one graph is returned, then all models are coupled. If several graphs are returned, then only the models inside each graph are coupled, and the models in different graphs are not coupled. nsteps
is the number of steps the dependency graph will be used over. It is used to determine the length of the simulation_id
argument for each soft dependencies in the graph. It is set to 1
in the case of a multiscale mapping.
Details
The dependency graph is computed by searching the inputs of each process in the outputs of its own scale, or the other scales. There are five cases for every model (one model simulates one process):
- The process has no inputs. It is completely independent, and is placed as one of the roots of the dependency graph.
- The process needs inputs from models at its own scale. We put it as a child of this other process.
- The process needs inputs from another scale. We put it as a child of this process at another scale.
- The process needs inputs from its own scale and another scale. We put it as a child of both.
- The process is a hard dependency of another process (only possible at the same scale). In this case, the process is set as a hard-dependency of the
other process, and its simulation is handled directly from this process.
For the 4th case, the process have two parent processes. This is OK because the process will only be computed once during simulation as we check if both parents were run before running the process.
Note that in the 5th case, we still need to check if a variable is needed from another scale. In this case, the parent node is used as a child of the process at the other scale. Note there can be several levels of hard dependency graph, so this is done recursively.
How do we do all that? We identify the hard dependencies first. Then we link the inputs/outputs of the hard dependencies roots to other scales if needed. Then we transform all these nodes into soft dependencies, that we put into a Dict of Scale => Dict(process => SoftDependencyNode). Then we traverse all these and we set nodes that need outputs from other nodes as inputs as children/parents. If a node has no dependency, it is set as a root node and pushed into a new Dict (independantprocessroot). This Dict is the returned dependency graph. And it presents root nodes as independent starting points for the sub-graphs, which are the models that are coupled together. We can then traverse each of these graphs independently to retrieve the models that are coupled together, in the right order of execution.
Examples
using PlantSimEngine
# Including example processes and models:
using PlantSimEngine.Examples;
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status=(var1=15.0, var2=0.3)
)
dep(models)
# or directly with the processes:
models = (
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
process4=Process4Model(),
process5=Process5Model(),
process6=Process6Model(),
process7=Process7Model(),
)
dep(;models...)
PlantSimEngine.dr
— Methoddr(obs,sim)
Returns the Willmott’s refined index of agreement dᵣ. Willmot et al. 2011. A refined index of model performance. https://rmets.onlinelibrary.wiley.com/doi/10.1002/joc.2419
The closer to 1 the better.
Examples
using PlantSimEngine
obs = [1.0, 2.0, 3.0]
sim = [1.1, 2.1, 3.1]
dr(obs, sim)
PlantSimEngine.fit
— Functionfit()
Optimize the parameters of a model using measurements and (potentially) initialisation values.
Modellers should implement a method to fit
for their model, with the following design pattern:
The call to the function should take the model type as the first argument (T::Type{<:AbstractModel}), the data as the second argument (as a Table.jl
compatible type, such as DataFrame
), and the parameters initializations as keyword arguments (with default values when necessary).
For example the method for fitting the Beer
model from the example script (see src/examples/Beer.jl
) looks like this:
function PlantSimEngine.fit(::Type{Beer}, df; J_to_umol=PlantMeteo.Constants().J_to_umol)
k = Statistics.mean(log.(df.Ri_PAR_f ./ (df.PPFD ./ J_to_umol)) ./ df.LAI)
return (k=k,)
end
The function should return the optimized parameters as a NamedTuple
of the form (parameter_name=parameter_value,)
.
Here is an example usage with the Beer
model, where we fit the k
parameter from "measurements" of PPFD
, LAI
and Ri_PAR_f
.
# Including example processes and models:
using PlantSimEngine.Examples;
m = ModelList(Beer(0.6), status=(LAI=2.0,))
meteo = Atmosphere(T=20.0, Wind=1.0, P=101.3, Rh=0.65, Ri_PAR_f=300.0)
run!(m, meteo)
df = DataFrame(aPPFD=m[:aPPFD][1], LAI=m.status.LAI[1], Ri_PAR_f=meteo.Ri_PAR_f[1])
fit(Beer, df)
Note that this is a dummy example to show that the fitting method works, as we simulate the PPFD using the Beer-Lambert law with a value of k=0.6
, and then use the simulated PPFD to fit the k
parameter again, which gives the same value as the one used on the simulation.
PlantSimEngine.init_status!
— Methodinit_status!(object::Dict{String,ModelList};vars...)
init_status!(component::ModelList;vars...)
Initialise model variables for components with user input.
Examples
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
models = Dict(
"Leaf" => ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
),
"InterNode" => ModelList(
process1=Process1Model(1.0),
)
)
init_status!(models, var1=1.0 , var2=2.0)
status(models["Leaf"])
PlantSimEngine.init_variables
— Methodinit_variables(models...)
Initialized model variables with their default values. The variables are taken from the inputs and outputs of the models.
Examples
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
init_variables(Process1Model(2.0))
init_variables(process1=Process1Model(2.0), process2=Process2Model())
PlantSimEngine.inputs
— Methodinputs(model::AbstractModel)
inputs(...)
Get the inputs of one or several models.
Returns an empty tuple by default for AbstractModel
s (no inputs) or Missing
models.
Examples
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
inputs(Process1Model(1.0))
# output
(:var1, :var2)
PlantSimEngine.inputs
— Methodinputs(mapping::Dict{String,T})
Get the inputs of the models in a mapping, for each process and organ type.
PlantSimEngine.is_initialized
— Methodis_initialized(m::T) where T <: ModelList
is_initialized(m::T, models...) where T <: ModelList
Check if the variables that must be initialized are, and return true
if so, and false
and an information message if not.
Note
There is no way to know before-hand which process will be simulated by the user, so if you have a component with a model for each process, the variables to initialize are always the smallest subset of all, meaning it is considered the user will simulate the variables needed for other models.
Examples
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
)
is_initialized(models)
PlantSimEngine.outputs
— Methodoutputs(sim::GraphSimulation, sink)
Get the outputs from a simulation made on a plant graph.
Details
The first method returns a vector of NamedTuple
, the second formats it sing the sink function, for exemple a DataFrame
.
Arguments
sim::GraphSimulation
: the simulation object, typically returned byrun!
.sink
: a sink compatible with the Tables.jl interface (e.g. aDataFrame
)refvectors
: iffalse
(default), the function will remove the RefVector values, otherwise it will keep themno_value
: the value to replacenothing
values. Default isnothing
. Usually used to replacenothing
values
by missing
in DataFrames.
Examples
using PlantSimEngine, MultiScaleTreeGraph, DataFrames, PlantSimEngine.Examples
Import example models (can be found in the examples
folder of the package, or in the Examples
sub-modules):
julia> using PlantSimEngine.Examples;
mapping = Dict( "Plant" => ( MultiScaleModel( model=ToyCAllocationModel(), mapping=[ :carbon_assimilation => ["Leaf"], :carbon_demand => ["Leaf", "Internode"], :carbon_allocation => ["Leaf", "Internode"] ], ),
MultiScaleModel( model=ToyPlantRmModel(), mapping=[:Rm_organs => ["Leaf" => :Rm, "Internode" => :Rm],] ), ),"Internode" => ( ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), ToyMaintenanceRespirationModel(1.5, 0.06, 25.0, 0.6, 0.004), Status(TT=10.0) ), "Leaf" => ( MultiScaleModel( model=ToyAssimModel(), mapping=[:soil_water_content => "Soil",], ), ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), ToyMaintenanceRespirationModel(2.1, 0.06, 25.0, 1.0, 0.025), Status(aPPFD=1300.0, TT=10.0), ), "Soil" => ( ToySoilWaterModel(), ), )
mtg = import_mtg_example();
sim = run!(mtg, mapping, meteo, outputs = Dict(
"Leaf" => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation),
"Internode" => (:carbon_allocation,),
"Plant" => (:carbon_allocation,),
"Soil" => (:soil_water_content,),
));
outputs(sim, DataFrames)
PlantSimEngine.outputs
— Methodoutputs(model::AbstractModel)
outputs(...)
Get the outputs of one or several models.
Returns an empty tuple by default for AbstractModel
s (no outputs) or Missing
models.
Examples
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
outputs(Process1Model(1.0))
# output
(:var3,)
PlantSimEngine.outputs
— Methodoutputs(mapping::Dict{String,T})
Get the outputs of the models in a mapping, for each process and organ type.
PlantSimEngine.run!
— Functionrun!(object, meteo, constants, extra=nothing; check=true, executor=Floops.ThreadedEx())
run!(object, mapping, meteo, constants, extra; nsteps, outputs, check, executor)
Run the simulation for each model in the model list in the correct order, i.e. respecting the dependency graph.
If several time-steps are given, the models are run sequentially for each time-step.
Arguments
object
: aModelList
, an array or dict ofModelList
, or a plant graph (MTG).meteo
: aPlantMeteo.TimeStepTable
of
PlantMeteo.Atmosphere
or a single PlantMeteo.Atmosphere
.
constants
: aPlantMeteo.Constants
object, or aNamedTuple
of constant keys and values.extra
: extra parameters, not available for simulation of plant graphs (the simulation object is passed using this).check
: iftrue
, check the validity of the model list before running the simulation (takes a little bit of time), and return more information while running.executor
: theFloops
executor used to run the simulation either in sequential (executor=SequentialEx()
), in a
multi-threaded way (executor=ThreadedEx()
, the default), or in a distributed way (executor=DistributedEx()
).
mapping
: a mapping between the MTG and the model list.nsteps
: the number of time-steps to run, only needed if no meteo is given (else it is infered from it).outputs
: the outputs to get in dynamic for each node type of the MTG.
Returns
Modifies the status of the object in-place. Users may retrieve the results from the object using the status
function (see examples).
Details
Model execution
The models are run according to the dependency graph. If a model has a soft dependency on another model (i.e. its inputs are computed by another model), the other model is run first. If a model has several soft dependencies, the parents (the soft dependencies) are always computed first.
Parallel execution
Users can ask for parallel execution by providing a compatible executor to the executor
argument. The package will also automatically check if the execution can be parallelized. If it is not the case and the user asked for a parallel computation, it return a warning and run the simulation sequentially. We use the Floops
package to run the simulation in parallel. That means that you can provide any compatible executor to the executor
argument. You can take a look at FoldsThreads.jl for extra thread-based executors, FoldsDagger.jl for Transducers.jl-compatible parallel fold implemented using the Dagger.jl framework, and soon FoldsCUDA.jl for GPU computations (see this issue) and FoldsKernelAbstractions.jl. You can also take a look at ParallelMagics.jl to check if automatic parallelization is possible.
Example
Import the packages:
julia> using PlantSimEngine, PlantMeteo;
Load the dummy models given as example in the Examples
sub-module:
julia> using PlantSimEngine.Examples;
Create a model list:
julia> models = ModelList(Process1Model(1.0), Process2Model(), Process3Model(), status = (var1=1.0, var2=2.0));
Create meteo data:
julia> meteo = Atmosphere(T=20.0, Wind=1.0, P=101.3, Rh=0.65, Ri_PAR_f=300.0);
Run the simulation:
julia> run!(models, meteo);
Get the results:
julia> (models[:var4],models[:var6])
([12.0], [41.95])
PlantSimEngine.status
— Methodstatus(m)
status(m::AbstractArray{<:ModelList})
status(m::AbstractDict{T,<:ModelList})
Get a ModelList status, i.e. the state of the input (and output) variables.
See also is_initialized
and to_initialize
Examples
using PlantSimEngine
# Including example models and processes:
using PlantSimEngine.Examples;
# Create a ModelList
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status = (var1=[15.0, 16.0], var2=0.3)
);
status(models)
# Or just one variable:
status(models,:var1)
# Or the status at the ith time-step:
status(models, 2)
# Or even more simply:
models[:var1]
# output
2-element Vector{Float64}:
15.0
16.0
PlantSimEngine.to_initialize
— Methodto_initialize(; verbose=true, vars...)
to_initialize(m::T) where T <: ModelList
to_initialize(m::DependencyGraph)
to_initialize(mapping::Dict{String,T}, graph=nothing)
Return the variables that must be initialized providing a set of models and processes. The function takes into account model coupling and only returns the variables that are needed considering that some variables that are outputs of some models are used as inputs of others.
Arguments
verbose
: iftrue
, print information messages.vars...
: the models and processes to consider.m::T
: aModelList
.m::DependencyGraph
: aDependencyGraph
.mapping::Dict{String,T}
: a mapping that associates models to organs.graph
: a graph representing a plant or a scene, e.g. a multiscale tree graph. The graph is used to check if variables that are not initialized can be found in the graph nodes attributes.
Examples
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
to_initialize(process1=Process1Model(1.0), process2=Process2Model())
# Or using a component directly:
models = ModelList(process1=Process1Model(1.0), process2=Process2Model())
to_initialize(models)
m = ModelList(
(
process1=Process1Model(1.0),
process2=Process2Model()
),
Status(var1 = 5.0, var2 = -Inf, var3 = -Inf, var4 = -Inf, var5 = -Inf)
)
to_initialize(m)
Or with a mapping:
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
mapping = Dict(
"Leaf" => ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
),
"Internode" => ModelList(
process1=Process1Model(1.0),
)
)
to_initialize(mapping)
PlantSimEngine.to_initialize
— Methodto_initialize(m::AbstractDependencyNode)
Return the variables that must be initialized providing a set of models and processes. The function just returns the inputs and outputs of each model, with their default values. To take into account model coupling, use the function at an upper-level instead, i.e. to_initialize(m::ModelList)
or to_initialize(m::DependencyGraph)
.
PlantSimEngine.variables
— Methodvariables(pkg::Module)
Returns a dataframe of all variables, their description and units in a package that has PlantSimEngine as a dependency (if implemented by the authors).
Note to developers
Developers of a package that depends on PlantSimEngine should put a csv file in "data/variables.csv", then this file will be returned by the function.
Examples
Here is an example with the PlantBiophysics package:
#] add PlantBiophysics
using PlantBiophysics
variables(PlantBiophysics)
PlantSimEngine.variables
— Methodvariables(m::AbstractDependencyNode)
Returns a tuple with the name of the inputs and outputs variables needed by a model in a dependency graph.
PlantSimEngine.variables
— Methodvariables(mapping::Dict{String,T})
Get the variables (inputs and outputs) of the models in a mapping, for each process and organ type.
PlantSimEngine.variables
— Methodvariables(model)
variables(model, models...)
Returns a tuple with the name of the variables needed by a model, or a union of those variables for several models.
Note
Each model can (and should) have a method for this function.
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
variables(Process1Model(1.0))
variables(Process1Model(1.0), Process2Model())
# output
(var1 = -Inf, var2 = -Inf, var3 = -Inf, var4 = -Inf, var5 = -Inf)
See also
inputs
, outputs
and variables_typed
PlantSimEngine.@process
— Macro@process(process::String, doc::String=""; verbose::Bool=true)
This macro generate the abstract type and some boilerplate code for the simulation of a process, along with its documentation. It also prints out a short tutorial for implementing a model if verbose=true
.
The abstract process type is then used as a supertype of all models implementations for the process, and is named "Abstract<ProcessName>Model", e.g. AbstractGrowthModel
for a process called growth.
The first argument to @process
is the new process name, the second is any additional documentation that should be added to the Abstract<ProcessName>Model
type, and the third determines whether the short tutorial should be printed or not.
Newcomers are encouraged to use this macro because it explains in detail what to do next with the process. But more experienced users may want to directly define their process without printing the tutorial. To do so, you can just define a new abstract type and define it as a subtype of AbstractModel
:
abstract type MyNewProcess <: AbstractModel end
Examples
@process "dummy_process" "This is a dummy process that shall not be used"
Un-exported
Private functions, types or constants from PlantSimEngine
. These are not exported, so you need to use PlantSimEngine.
to access them (e.g. PlantSimEngine.DataFormat
).
DataFrames.DataFrame
— MethodDataFrame(components <: AbstractArray{<:ModelList})
DataFrame(components <: AbstractDict{N,<:ModelList})
Fetch the data from a ModelList
(or an Array/Dict of) status into a DataFrame.
Examples
using PlantSimEngine
using DataFrames
# Creating a ModelList
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status=(var1=15.0, var2=0.3)
)
# Converting to a DataFrame
df = DataFrame(models)
# Converting to a Dict of ModelLists
models = Dict(
"Leaf" => ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
),
"InterNode" => ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
)
)
# Converting to a DataFrame
df = DataFrame(models)
DataFrames.DataFrame
— MethodDataFrame(components::ModelList{T,S,V}) where {T,S<:Status,V}
Implementation of DataFrame
for a ModelList
model with one time step.
DataFrames.DataFrame
— MethodDataFrame(components::ModelList{T,<:TimeStepTable})
Implementation of DataFrame
for a ModelList
model with several time steps.
PlantSimEngine.AbstractNodeMapping
— TypeAbstractNodeMapping
Abstract type for the type of node mapping, e.g. single node mapping or multiple node mapping.
PlantSimEngine.DataFormat
— MethodDataFormat(T::Type)
Returns the data format of the type T
. The data format is used to determine how to iterate over the data. The following data formats are supported:
TableAlike
: The data is a table-like object, e.g. aDataFrame
or aTimeStepTable
. The data is iterated over by rows using theTables.jl
interface.SingletonAlike
: The data is a singleton-like object, e.g. aNamedTuple
or aTimeStepRow
. The data is iterated over by columns.TreeAlike
: The data is a tree-like object, e.g. aNode
.
The default implementation returns TableAlike
for AbstractDataFrame
, TimeStepTable
, AbstractVector
and Dict
, TreeAlike
for GraphSimulation
, SingletonAlike
for Status
, ModelList
, NamedTuple
and TimeStepRow
.
The default implementation for Any
throws an error. Users that want to use another input should define this trait for the new data format, e.g.:
PlantSimEngine.DataFormat(::Type{<:MyType}) = TableAlike()
Examples
julia> using PlantSimEngine, PlantMeteo, DataFrames
julia> PlantSimEngine.DataFormat(DataFrame)
PlantSimEngine.TableAlike()
julia> PlantSimEngine.DataFormat(TimeStepTable([Status(a = 1, b = 2, c = 3)]))
PlantSimEngine.TableAlike()
julia> PlantSimEngine.DataFormat([1, 2, 3])
PlantSimEngine.TableAlike()
julia> PlantSimEngine.DataFormat(Dict(:a => 1, :b => 2))
PlantSimEngine.TableAlike()
julia> PlantSimEngine.DataFormat(Status(a = 1, b = 2, c = 3))
PlantSimEngine.SingletonAlike()
PlantSimEngine.DependencyGraph
— TypeDependencyGraph{T}(roots::T, not_found::Dict{Symbol,DataType})
A graph of dependencies between models.
Arguments
roots::T
: the root nodes of the graph.not_found::Dict{Symbol,DataType}
: the models that were not found in the graph.
PlantSimEngine.DependencyTrait
— TypeDependencyTrait(T::Type)
Returns information about the eventual dependence of a model T
to other time-steps or objects for its computation. The dependence trait is used to determine if a model is parallelizable or not.
The following dependence traits are supported:
TimeStepDependencyTrait
: Trait that defines whether a model can be parallelizable over time-steps for its computation.ObjectDependencyTrait
: Trait that defines whether a model can be parallelizable over objects for its computation.
PlantSimEngine.GraphSimulation
— TypeGraphSimulation(graph, mapping)
GraphSimulation(graph, statuses, dependency_graph, models, outputs)
A type that holds all information for a simulation over a graph.
Arguments
graph
: an graph, such as an MTGmapping
: a dictionary of model mappingstatuses
: a structure that defines the status of each node in the graphstatus_templates
: a dictionary of status templatesreverse_multiscale_mapping
: a dictionary of mapping for other scalesvar_need_init
: a dictionary indicating if a variable needs to be initializeddependency_graph
: the dependency graph of the models applied to the graphmodels
: a dictionary of modelsoutputs
: a dictionary of outputs
PlantSimEngine.MappedVar
— TypeMappedVar(source_organ, variable, source_variable, source_default)
A variable mapped to another scale.
Arguments
source_organ
: the organ(s) that are targeted by the mappingvariable
: the name of the variable that is mappedsource_variable
: the name of the variable from the source organ (the one that computes the variable)source_default
: the default value of the variable
Examples
julia> using PlantSimEngine
julia> PlantSimEngine.MappedVar(PlantSimEngine.SingleNodeMapping("Leaf"), :carbon_assimilation, :carbon_assimilation, 1.0)
PlantSimEngine.MappedVar{PlantSimEngine.SingleNodeMapping, Symbol, Symbol, Float64}(PlantSimEngine.SingleNodeMapping("Leaf"), :carbon_assimilation, :carbon_assimilation, 1.0)
PlantSimEngine.MultiNodeMapping
— TypeMultiNodeMapping(scale)
Type for the multiple node mapping, e.g. [:carbon_assimilation => ["Leaf"],]
. Note that "Leaf" is given as a vector, which means :carbon_assimilation
will be a vector of values taken from each "Leaf" in the plant graph.
PlantSimEngine.ObjectDependencyTrait
— TypeObjectDependencyTrait(::Type{T})
Defines the trait about the eventual dependence of a model T
to other objects for its computation. This dependency trait is used to determine if a model is parallelizable over objects or not.
The following dependency traits are supported:
IsObjectDependent
: The model depends on other objects for its computation, it cannot be run in parallel.IsObjectIndependent
: The model does not depend on other objects for its computation, it can be run in parallel.
All models are object dependent by default (i.e. IsObjectDependent
). This is probably not right for the majority of models, but:
- It is the safest default, as it will not lead to incorrect results if the user forgets to override this trait
which is not the case for the opposite (i.e. IsObjectIndependent
)
- It is easy to override this trait for models that are object independent
See also
timestep_parallelizable
: Returnstrue
if the model is parallelizable over time-steps, andfalse
otherwise.object_parallelizable
: Returnstrue
if the model is parallelizable over objects, andfalse
otherwise.parallelizable
: Returnstrue
if the model is parallelizable, andfalse
otherwise.TimeStepDependencyTrait
: Defines the trait about the eventual dependence of a model to other time-steps for its computation.
Examples
Define a dummy process:
using PlantSimEngine
# Define a test process:
@process "TestProcess"
Define a model that is object independent:
struct MyModel <: AbstractTestprocessModel end
# Override the object dependency trait:
PlantSimEngine.ObjectDependencyTrait(::Type{MyModel}) = IsObjectIndependent()
Check if the model is parallelizable over objects:
object_parallelizable(MyModel()) # false
Define a model that is object dependent:
struct MyModel2 <: AbstractTestprocessModel end
# Override the object dependency trait:
PlantSimEngine.ObjectDependencyTrait(::Type{MyModel2}) = IsObjectDependent()
Check if the model is parallelizable over objects:
object_parallelizable(MyModel()) # true
PlantSimEngine.RefVariable
— TypeRefVariable(reference_variable)
A structure to manually flag a variable in a model to use the value of another variable at the same scale. This is used for variable renaming, when a variable is computed by a model but is used by another model with a different name.
Note: we don't really rename the variable in the status (we need it for the other models), but we create a new one that is a reference to the first one.
PlantSimEngine.RefVector
— TypeRefVector(field::Symbol, sts...)
RefVector(field::Symbol, sts::Vector{<:Status})
RefVector(v::Vector{Base.RefValue{T}})
A vector of references to a field of a vector of structs. This is used to efficiently pass the values between scales.
Arguments
field
: the field of the struct to referencests...
: the structs to referencests::Vector{<:Status}
: a vector of structs to reference
Examples
julia> using PlantSimEngine
Let's take two Status structs:
julia> status1 = Status(a = 1.0, b = 2.0, c = 3.0);
julia> status2 = Status(a = 2.0, b = 3.0, c = 4.0);
We can make a RefVector of the field a
of the structs st1
and st2
:
julia> rv = PlantSimEngine.RefVector(:a, status1, status2)
2-element PlantSimEngine.RefVector{Float64}:
1.0
2.0
Which is equivalent to:
julia> rv = PlantSimEngine.RefVector(:a, [status1, status2])
2-element PlantSimEngine.RefVector{Float64}:
1.0
2.0
We can access the values of the RefVector:
julia> rv[1]
1.0
Updating the value in the RefVector will update the value in the original struct:
julia> rv[1] = 10.0
10.0
julia> status1.a
10.0
We can also make a RefVector from a vector of references:
julia> vec = [Ref(1.0), Ref(2.0), Ref(3.0)]
3-element Vector{Base.RefValue{Float64}}:
Base.RefValue{Float64}(1.0)
Base.RefValue{Float64}(2.0)
Base.RefValue{Float64}(3.0)
julia> rv = PlantSimEngine.RefVector(vec)
3-element PlantSimEngine.RefVector{Float64}:
1.0
2.0
3.0
julia> rv[1]
1.0
PlantSimEngine.SelfNodeMapping
— TypeSelfNodeMapping()
Type for the self node mapping, i.e. a node that maps onto itself. This is used to flag variables that will be referenced as a scalar value by other models. It can happen in two conditions: - the variable is computed by another scale, so we need this variable to exist as an input to this scale (it is not computed at this scale otherwise) - the variable is used as input to another scale but as a single value (scalar), so we need to reference it as a scalar.
PlantSimEngine.SingleNodeMapping
— TypeSingleNodeMapping(scale)
Type for the single node mapping, e.g. [:soil_water_content => "Soil",]
. Note that "Soil" is given as a scalar, which means that :soil_water_content
will be a scalar value taken from the unique "Soil" node in the plant graph.
PlantSimEngine.TimeStepDependencyTrait
— MethodTimeStepDependencyTrait(::Type{T})
Defines the trait about the eventual dependence of a model T
to other time-steps for its computation. This dependency trait is used to determine if a model is parallelizable over time-steps or not.
The following dependency traits are supported:
IsTimeStepDependent
: The model depends on other time-steps for its computation, it cannot be run in parallel.IsTimeStepIndependent
: The model does not depend on other time-steps for its computation, it can be run in parallel.
All models are time-step dependent by default (i.e. IsTimeStepDependent
). This is probably not right for the majority of models, but:
- It is the safest default, as it will not lead to incorrect results if the user forgets to override this trait
which is not the case for the opposite (i.e. IsTimeStepIndependent
)
- It is easy to override this trait for models that are time-step independent
See also
timestep_parallelizable
: Returnstrue
if the model is parallelizable over time-steps, andfalse
otherwise.object_parallelizable
: Returnstrue
if the model is parallelizable over objects, andfalse
otherwise.parallelizable
: Returnstrue
if the model is parallelizable, andfalse
otherwise.ObjectDependencyTrait
: Defines the trait about the eventual dependence of a model to other objects for its computation.
Examples
Define a dummy process:
using PlantSimEngine
# Define a test process:
@process "TestProcess"
Define a model that is time-step independent:
struct MyModel <: AbstractTestprocessModel end
# Override the time-step dependency trait:
PlantSimEngine.TimeStepDependencyTrait(::Type{MyModel}) = IsTimeStepIndependent()
Check if the model is parallelizable over time-steps:
timestep_parallelizable(MyModel()) # false
Define a model that is time-step dependent:
struct MyModel2 <: AbstractTestprocessModel end
# Override the time-step dependency trait:
PlantSimEngine.TimeStepDependencyTrait(::Type{MyModel2}) = IsTimeStepDependent()
Check if the model is parallelizable over time-steps:
timestep_parallelizable(MyModel()) # true
PlantSimEngine.UninitializedVar
— TypeUninitializedVar(variable, value)
A variable that is not initialized yet, it is given a name and a default value.
Base.copy
— MethodBase.copy(l::AbstractArray{<:ModelList})
Copy an array-alike of ModelList
Base.copy
— MethodBase.copy(l::AbstractDict{N,<:ModelList} where N)
Copy a Dict-alike ModelList
Base.copy
— MethodBase.copy(l::ModelList)
Base.copy(l::ModelList, status)
Copy a ModelList
, eventually with new values for the status.
Examples
using PlantSimEngine
# Including example processes and models:
using PlantSimEngine.Examples;
# Create a model list:
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status=(var1=15.0, var2=0.3)
)
# Copy the model list:
ml2 = copy(models)
# Copy the model list with new status:
ml3 = copy(models, TimeStepTable([Status(var1=20.0, var2=0.5))])
Base.getindex
— Methodgetindex(component<:ModelList, key::Symbol)
getindex(component<:ModelList, key)
Indexing a component models structure: - with an integer, will return the status at the ith time-step - with anything else (Symbol, String) will return the required variable from the status
Examples
using PlantSimEngine
lm = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status = (var1=[15.0, 16.0], var2=0.3)
);
lm[:var1] # Returns the value of the Tₗ variable
lm[2] # Returns the status at the second time-step
lm[2][:var1] # Returns the value of Tₗ at the second time-step
lm[:var1][2] # Equivalent of the above
# output
16.0
PlantSimEngine.add_mapped_variables_with_outputs_as_inputs!
— Methodadd_mapped_variables_with_outputs_as_inputs!(mapped_vars)
Add the variables that are computed at a scale and written to another scale into the mapping.
PlantSimEngine.add_model_vars
— Methodadd_model_vars(x, models, type_promotion; init_fun=init_fun_default)
Check which variables in x
are not initialized considering a set of models
and the variables needed for their simulation. If some variables are uninitialized, initialize them to their default values.
This function needs to be implemented for each type of x
. The default method works for any Tables.jl-compatible x
and for NamedTuples.
Careful, the function makes a copy of the input x
if it does not list all needed variables.
PlantSimEngine.check_dimensions
— Methodcheck_dimensions(component,weather)
check_dimensions(status,weather)
Checks if a component status (or a status directly) and the weather have the same length, or if they can be recycled (length 1 for one of them).
Examples
using PlantSimEngine, PlantMeteo
# Including an example script that implements dummy processes and models:
using PlantSimEngine.Examples
# Creating a dummy weather:
w = Atmosphere(T = 20.0, Rh = 0.5, Wind = 1.0)
# Creating a dummy component:
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
status=(var1=[15.0, 16.0], var2=0.3)
)
# Checking that the number of time-steps are compatible (here, they are, it returns nothing):
PlantSimEngine.check_dimensions(models, w)
# Creating a dummy weather with 3 time-steps:
w = Weather([
Atmosphere(T = 20.0, Rh = 0.5, Wind = 1.0),
Atmosphere(T = 25.0, Rh = 0.5, Wind = 1.0),
Atmosphere(T = 30.0, Rh = 0.5, Wind = 1.0)
])
# Checking that the number of time-steps are compatible (here, they are not, it throws an error):
PlantSimEngine.check_dimensions(models, w)
# output
ERROR: DimensionMismatch: Component status should have the same number of time-steps (2) than weather data (3).
PlantSimEngine.convert_reference_values!
— Methodconvert_reference_values!(mapped_vars::Dict{String,Dict{Symbol,Any}})
Convert the variables that are MappedVar{SelfNodeMapping}
or MappedVar{SingleNodeMapping}
to RefValues that reference a common value for the variable; and convert MappedVar{MultiNodeMapping}
to RefVectors that reference the values for the variable in the source organs.
PlantSimEngine.convert_vars
— Functionconvert_vars(ref_vars, type_promotion::Dict{DataType,DataType})
convert_vars(ref_vars, type_promotion::Nothing)
convert_vars!(ref_vars::Dict{Symbol}, type_promotion::Dict{DataType,DataType})
convert_vars!(ref_vars::Dict{Symbol}, type_promotion::Nothing)
Convert the status variables to the type specified in the type promotion dictionary. Note: the mutating version only works with a dictionary of variables.
Examples
If we want all the variables that are Reals to be Float32, we can use:
using PlantSimEngine
# Including example processes and models:
using PlantSimEngine.Examples;
ref_vars = init_variables(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
)
type_promotion = Dict(Real => Float32)
PlantSimEngine.convert_vars(type_promotion, ref_vars.process3)
PlantSimEngine.convert_vars!
— Functionconvert_vars(ref_vars, type_promotion::Dict{DataType,DataType})
convert_vars(ref_vars, type_promotion::Nothing)
convert_vars!(ref_vars::Dict{Symbol}, type_promotion::Dict{DataType,DataType})
convert_vars!(ref_vars::Dict{Symbol}, type_promotion::Nothing)
Convert the status variables to the type specified in the type promotion dictionary. Note: the mutating version only works with a dictionary of variables.
Examples
If we want all the variables that are Reals to be Float32, we can use:
using PlantSimEngine
# Including example processes and models:
using PlantSimEngine.Examples;
ref_vars = init_variables(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
)
type_promotion = Dict(Real => Float32)
PlantSimEngine.convert_vars(type_promotion, ref_vars.process3)
PlantSimEngine.convert_vars!
— Methodconvert_vars!(mapped_vars::Dict{String,Dict{String,Any}}, type_promotion)
Converts the types of the variables in a mapping (mapped_vars
) using the type_promotion
dictionary.
The mapping should be a dictionary with organ name as keys and a dictionary of variables as values, with variable names as symbols and variable value as value.
PlantSimEngine.default_variables_from_mapping
— Functiondefault_variables_from_mapping(mapped_vars, verbose=true)
Get the default values for the mapped variables by recursively searching from the mapping to find the original mapped value.
Arguments
mapped_vars::Dict{String,Dict{Symbol,Any}}
: the variables mapped to each organ.verbose::Bool
: whether to print the stacktrace of the search for the default value in the mapping.
PlantSimEngine.diff_vars
— Methoddiff_vars(x, y)
Returns the names of variables that have different values in x and y.
PlantSimEngine.draw_guide
— Methoddraw_guide(h, w, prefix, isleaf, guides)
Draw the line guide for one node of the dependency graph.
PlantSimEngine.draw_panel
— Methoddraw_panel(node, graph, prefix, dep_graph_guides, parent; title="Soft-coupled model")
Draw the panels for all dependencies
PlantSimEngine.drop_process
— Methoddrop_process(proc_vars, process)
Return a new NamedTuple
with the process process
removed from the NamedTuple
proc_vars
.
Arguments
proc_vars::NamedTuple
: theNamedTuple
from which we want to remove the processprocess
.process::Symbol
: the process we want to remove from theNamedTuple
proc_vars
.
Returns
A new NamedTuple
with the process process
removed from the NamedTuple
proc_vars
.
Example
julia> drop_process((a = 1, b = 2, c = 3), :b)
(a = 1, c = 3)
julia> drop_process((a = 1, b = 2, c = 3), (:a, :c))
(b = 2,)
PlantSimEngine.flatten_vars
— Methodflatten_vars(vars)
Return a set of the variables in the vars
dictionary.
Arguments
vars::Dict{Symbol, Tuple{Symbol, Vararg{Symbol}}}
: a dict of process => namedtuple of variables => value.
Returns
A set of the variables in the vars
dictionary.
Example
julia> flatten_vars(Dict(:process1 => (:var1, :var2), :process2 => (:var3, :var4)))
Set{Symbol} with 4 elements:
:var4
:var3
:var2
:var1
julia> flatten_vars([:process1 => (var1 = -Inf, var2 = -Inf), :process2 => (var3 = -Inf, var4 = -Inf)])
(var2 = -Inf, var4 = -Inf, var3 = -Inf, var1 = -Inf)
PlantSimEngine.get_mapping
— Methodget_mapping(m)
Get the mapping of a dictionary of model mapping.
Arguments
m::Dict{String,Any}
: a dictionary of model mapping
Returns a vector of pairs of symbols and strings or vectors of strings
Examples
See get_models
for examples.
PlantSimEngine.get_model_nodes
— Methodget_model_nodes(dep_graph::DependencyGraph, model)
Get the nodes in the dependency graph implementing a type of model.
Arguments
dep_graph::DependencyGraph
: the dependency graph.model
: the model type to look for.
Returns
- An array of nodes implementing the model type.
Examples
PlantSimEngine.get_model_nodes(dependency_graph, Beer)
PlantSimEngine.get_models
— Methodget_models(m)
Get the models of a dictionary of model mapping.
Arguments
m::Dict{String,Any}
: a dictionary of model mapping
Returns a vector of models
Examples
julia> using PlantSimEngine;
Import example models (can be found in the examples
folder of the package, or in the Examples
sub-modules):
julia> using PlantSimEngine.Examples;
If we just give a MultiScaleModel, we get its model as a one-element vector:
julia> models = MultiScaleModel( model=ToyCAllocationModel(), mapping=[ :carbon_assimilation => ["Leaf"], :carbon_demand => ["Leaf", "Internode"], :carbon_allocation => ["Leaf", "Internode"] ], );
julia> PlantSimEngine.get_models(models)
1-element Vector{ToyCAllocationModel}:
ToyCAllocationModel()
If we give a tuple of models, we get each model in a vector:
julia> models2 = ( MultiScaleModel( model=ToyAssimModel(), mapping=[:soil_water_content => "Soil",], ), ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), Status(aPPFD=1300.0, TT=10.0), );
Notice that we provide "Soil", not ["Soil"] in the mapping because a single value is expected for the mapping here.
julia> PlantSimEngine.get_models(models2)
2-element Vector{AbstractModel}:
ToyAssimModel{Float64}(0.2)
ToyCDemandModel{Float64}(10.0, 200.0)
PlantSimEngine.get_multiscale_default_value
— Functionget_multiscale_default_value(mapped_vars, val, mapping_stacktrace=[])
Get the default value of a variable from a mapping.
Arguments
mapped_vars::Dict{String,Dict{Symbol,Any}}
: the variables mapped to each organ.val::Any
: the variable to get the default value of.mapping_stacktrace::Vector{Any}
: the stacktrace of the search for the value in ascendind the mapping.
PlantSimEngine.get_nsteps
— Methodget_nsteps(t)
Get the number of steps in the object.
PlantSimEngine.get_status
— Methodget_status(m)
Get the status of a dictionary of model mapping.
Arguments
m::Dict{String,Any}
: a dictionary of model mapping
Returns a Status
or nothing
.
Examples
See get_models
for examples.
PlantSimEngine.get_vars_not_propagated
— Methodget_vars_not_propagated(status)
Returns all variables that are given for several time-steps in the status.
PlantSimEngine.hard_dependencies
— Methodhard_dependencies(models; verbose::Bool=true)
hard_dependencies(mapping::Dict{String,T}; verbose::Bool=true)
Compute the hard dependencies between models.
PlantSimEngine.homogeneous_ts_kwargs
— Methodhomogeneous_ts_kwargs(kwargs)
By default, the function returns its argument.
PlantSimEngine.homogeneous_ts_kwargs
— Methodkwargs_to_timestep(kwargs::NamedTuple{N,T}) where {N,T}
Takes a NamedTuple with optionnaly vector of values for each variable, and makes a vector of NamedTuple, with each being a time step. It is used to be able to e.g. give constant values for all time-steps for one variable.
Examples
PlantSimEngine.homogeneous_ts_kwargs((Tₗ=[25.0, 26.0], aPPFD=1000.0))
PlantSimEngine.init_node_status!
— Functioninit_node_status!(
node,
statuses,
mapped_vars,
reverse_multiscale_mapping,
vars_need_init=Dict{String,Any}(),
type_promotion=nothing;
check=true,
attribute_name=:plantsimengine_status)
)
Initialise the status of a plant graph node, taking into account the multiscale mapping, and add it to the statuses dictionary.
Arguments
node
: the node to initialisestatuses
: the dictionary of statuses by node typemapped_vars
: the template of status for each node typereverse_multiscale_mapping
: the variables that are mapped to other scalesvar_need_init
: the variables that are not initialised or computed by other modelsnodes_with_models
: the nodes that have a model defined for their symboltype_promotion
: the type promotion to use for the variablescheck
: whether to check the mapping for errors (see details)attribute_name
: the name of the attribute to store the status in the node, by default::plantsimengine_status
Details
Most arguments can be computed from the graph and the mapping:
statuses
is given by the first initialisation:statuses = Dict(i => Status[] for i in nodes_with_models)
mapped_vars
is computed usingmapped_variables()
, see code ininit_statuses
vars_need_init
is computed using `varsneedinit = Dict(org => filter(x -> isa(last(x), UninitializedVar), vars) |> keys for (org, vars) in mapped_vars) |>
filter(x -> length(last(x)) > 0)`
The check
argument is a boolean indicating if variables initialisation should be checked. In the case that some variables need initialisation (partially initialized mapping), we check if the value can be found in the node attributes (using the variable name). If true
, the function returns an error if the attribute is missing, otherwise it uses the default value from the model.
PlantSimEngine.init_simulation
— Methodinit_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=true)
Initialise the simulation. Returns:
- the mtg
- a status for each node by organ type, considering multi-scale variables
- the dependency graph of the models
- the models parsed as a Dict of organ type => NamedTuple of process => model mapping
- the pre-allocated outputs
Arguments
mtg
: the MTGmapping::Dict{String,Any}
: a dictionary of model mappingnsteps
: the number of steps of the simulationoutputs
: the dynamic outputs needed for the simulationtype_promotion
: the type promotion to use for the variablescheck
: whether to check the mapping for errors. Passed toinit_node_status!
.verbose
: print information about errors in the mapping
Details
The function first computes a template of status for each organ type that has a model in the mapping. This template is used to initialise the status of each node of the MTG, taking into account the user-defined initialisation, and the (multiscale) mapping. The mapping is used to make references to the variables that are defined at another scale, so that the values are automatically updated when the variable is changed at the other scale. Two types of multiscale variables are available: RefVector
and MappedVar
. The first one is used when the variable is mapped to a vector of nodes, and the second one when it is mapped to a single node. This is given by the user through the mapping, using a string for a single node (e.g. => "Leaf"
), and a vector of strings for a vector of nodes (e.g. => ["Leaf"]
for one type of node or => ["Leaf", "Internode"]
for several).
The function also computes the dependency graph of the models, i.e. the order in which the models should be called, considering the dependencies between them. The dependency graph is used to call the models in the right order when the simulation is run.
Note that if a variable is not computed by models or initialised from the mapping, it is searched in the MTG attributes. The value is not a reference to the one in the attribute of the MTG, but a copy of it. This is because we can't reference a value in a Dict. If you need a reference, you can use a Ref
for your variable in the MTG directly, and it will be automatically passed as is.
PlantSimEngine.init_statuses
— Functioninit_statuses(mtg, mapping, dependency_graph=dep(mapping); type_promotion=nothing, verbose=true, check=true)
Get the status of each node in the MTG by node type, pre-initialised considering multi-scale variables.
Arguments
mtg
: the plant graphmapping
: a dictionary of model mappingdependency_graph::DependencyGraph
: the first-order dependency graph where each model in the mapping is assigned a node.
However, models that are identified as hard-dependencies are not given individual nodes. Instead, they are nested as child nodes under other models.
type_promotion
: the type promotion to use for the variablesverbose
: print information when compiling the mappingcheck
: whether to check the mapping for errors. Passed toinit_node_status!
.
Return
A NamedTuple of status by node type, a dictionary of status templates by node type, a dictionary of variables mapped to other scales, a dictionary of variables that need to be initialised or computed by other models, and a vector of nodes that have a model defined for their symbol:
(;statuses, status_templates, reverse_multiscale_mapping, vars_need_init, nodes_with_models)
PlantSimEngine.init_variables_manual
— Methodinit_variables_manual(models...;vars...)
Return an initialisation of the model variables with given values.
Examples
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model()
)
PlantSimEngine.init_variables_manual(status(models), (var1=20.0,))
PlantSimEngine.initialise_all_as_hard_dependency_node
— Methodinitialise_all_as_hard_dependency_node(models)
Take a set of models and initialise them all as a hard dependency node, and return a dictionary of :process => HardDependencyNode
.
PlantSimEngine.is_graph_cyclic
— Methodis_graph_cyclic(dependency_graph::DependencyGraph; full_stack=false, verbose=true)
Check if the dependency graph is cyclic.
Arguments
dependency_graph::DependencyGraph
: the dependency graph to check.full_stack::Bool=false
: iftrue
, return the full stack of nodes that makes the cycle, otherwise return only the cycle.warn::Bool=true
: iftrue
, print a stylised warning message when a cycle is detected.
Return a boolean indicating if the graph is cyclic, and the stack of nodes as a vector.
PlantSimEngine.mapped_variables
— Functionmapped_variables(mapping, dependency_graph=first(hard_dependencies(mapping; verbose=false)); verbose=false)
Get the variables for each organ type from a dependency graph, with MappedVar
s for the multiscale mapping.
Arguments
mapping::Dict{String,T}
: the mapping between models and scales.dependency_graph::DependencyGraph
: the first-order dependency graph where each model in the mapping is assigned a node.
However, models that are identified as hard-dependencies are not given individual nodes. Instead, they are nested as child nodes under other models.
verbose::Bool
: whether to print the stacktrace of the search for the default value in the mapping.
PlantSimEngine.mapped_variables_no_outputs_from_other_scale
— Functionmapped_variables_no_outputs_from_other_scale(mapping, dependency_graph=first(hard_dependencies(mapping; verbose=false)))
Get the variables for each organ type from a dependency graph, without the variables that are outputs from another scale.
Arguments
mapping::Dict{String,T}
: the mapping between models and scales.dependency_graph::DependencyGraph
: the first-order dependency graph where each model in the mapping is assigned a node.
However, models that are identified as hard-dependencies are not given individual nodes. Instead, they are nested as child nodes under other models.
Details
This function returns a dictionary with the (multiscale-) inputs and outputs variables for each organ type.
Note that this function does not include the variables that are outputs from another scale and not computed by this scale, see mapped_variables_with_outputs_as_inputs
for that.
PlantSimEngine.model_
— Methodmodel_(m::AbstractModel)
Get the model of an AbstractModel (it is the model itself if it is not a MultiScaleModel).
PlantSimEngine.object_parallelizable
— Methodobject_parallelizable(x::T)
object_parallelizable(x::DependencyGraph)
Returns true
if the model x
is parallelizable, i.e. if the model can be computed in parallel for different objects, or false
otherwise.
The default implementation returns false
for all models. If you develop a model that is parallelizable over objects, you should add a method to ObjectDependencyTrait
for your model.
Note that this method can also be applied on a DependencyGraph
directly, in which case it returns true
if all models in the graph are parallelizable, and false
otherwise.
See also
timestep_parallelizable
: Returnstrue
if the model is parallelizable over time-steps, andfalse
otherwise.parallelizable
: Returnstrue
if the model is parallelizable, andfalse
otherwise.ObjectDependencyTrait
: Defines the trait about the eventual dependence of a model to other objects for its computation.
Examples
Define a dummy process:
using PlantSimEngine
# Define a test process:
@process "TestProcess"
Define a model that is object independent:
struct MyModel <: AbstractTestprocessModel end
# Override the object dependency trait:
PlantSimEngine.ObjectDependencyTrait(::Type{MyModel}) = IsObjectIndependent()
Check if the model is parallelizable over objects:
object_parallelizable(MyModel()) # true
PlantSimEngine.parallelizable
— Methodparallelizable(::T)
object_parallelizable(x::DependencyGraph)
Returns true
if the model T
or the whole dependency graph is parallelizable, i.e. if the model can be computed in parallel for different time-steps or objects. The default implementation returns false
for all models.
See also
timestep_parallelizable
: Returnstrue
if the model is parallelizable over time-steps, andfalse
otherwise.object_parallelizable
: Returnstrue
if the model is parallelizable over objects, andfalse
otherwise.TimeStepDependencyTrait
: Defines the trait about the eventual dependence of a model to other time-steps for its computation.
Examples
Define a dummy process:
using PlantSimEngine
# Define a test process:
@process "TestProcess"
Define a model that is parallelizable:
struct MyModel <: AbstractTestprocessModel end
# Override the time-step dependency trait:
PlantSimEngine.TimeStepDependencyTrait(::Type{MyModel}) = IsTimeStepIndependent()
# Override the object dependency trait:
PlantSimEngine.ObjectDependencyTrait(::Type{MyModel}) = IsObjectIndependent()
Check if the model is parallelizable:
parallelizable(MyModel()) # true
Or if we want to be more explicit:
timestep_parallelizable(MyModel())
object_parallelizable(MyModel())
PlantSimEngine.pre_allocate_outputs
— Methodpre_allocate_outputs(statuses, outs, nsteps; check=true)
Pre-allocate the outputs of needed variable for each node type in vectors of vectors. The first level vectors have length nsteps, and the second level vectors have length n_nodes of this type.
Note that we pre-allocate the vectors for the time-steps, but not for each organ, because we don't know how many nodes will be in each organ in the future (organs can appear or disapear).
Arguments
statuses
: a dictionary of status by node typeouts
: a dictionary of outputs by node typensteps
: the number of time-stepscheck
: whether to check the mapping for errors. Default (true
) returns an error if some variables do not exist.
If false and some variables are missing, return an info, remove the unknown variables and continue.
Returns
- A dictionary of pre-allocated output of vector of time-step and vector of node of that type.
Examples
julia> using PlantSimEngine, MultiScaleTreeGraph, PlantSimEngine.Examples
Import example models (can be found in the examples
folder of the package, or in the Examples
sub-modules):
julia> using PlantSimEngine.Examples;
Define the models mapping:
julia> mapping = Dict( "Plant" => ( MultiScaleModel( model=ToyCAllocationModel(), mapping=[ :carbon_assimilation => ["Leaf"], :carbon_demand => ["Leaf", "Internode"], :carbon_allocation => ["Leaf", "Internode"] ], ),
MultiScaleModel( model=ToyPlantRmModel(), mapping=[:Rm_organs => ["Leaf" => :Rm, "Internode" => :Rm],] ), ),"Internode" => ( ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), ToyMaintenanceRespirationModel(1.5, 0.06, 25.0, 0.6, 0.004), Status(TT=10.0, carbon_biomass=1.0) ), "Leaf" => ( MultiScaleModel( model=ToyAssimModel(), mapping=[:soil_water_content => "Soil",], ), ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), ToyMaintenanceRespirationModel(2.1, 0.06, 25.0, 1.0, 0.025), Status(aPPFD=1300.0, TT=10.0, carbon_biomass=1.0), ), "Soil" => ( ToySoilWaterModel(), ), );
Importing an example MTG provided by the package:
julia> mtg = import_mtg_example();
julia> statuses, status_templates, reverse_multiscale_mapping, vars_need_init = PlantSimEngine.init_statuses(mtg, mapping);
julia> outs = Dict("Leaf" => (:carbon_assimilation, :carbon_demand), "Soil" => (:soil_water_content,));
Pre-allocate the outputs as a dictionary:
julia> preallocated_vars = PlantSimEngine.pre_allocate_outputs(statuses, status_templates, reverse_multiscale_mapping, vars_need_init, outs, 2);
The dictionary has a key for each organ from which we want outputs:
julia> collect(keys(preallocated_vars))
2-element Vector{String}:
"Soil"
"Leaf"
Each organ has a dictionary of variables for which we want outputs from, with the pre-allocated empty vectors (one per time-step that will be filled with one value per node):
julia> collect(keys(preallocated_vars["Leaf"]))
3-element Vector{Symbol}:
:carbon_assimilation
:node
:carbon_demand
PlantSimEngine.propagate_values!
— Methodpropagate_values!(status1::Dict, status2::Dict, vars_not_propagated::Set)
Propagates the values of all variables in status1
to status2
, except for vars in vars_not_propagated
.
Arguments
status1::Dict
: A dictionary containing the current values of variables.status2::Dict
: A dictionary to which the values of variables will be propagated.vars_not_propagated::Set
: A set of variables whose values should not be propagated.
Examples
julia> status1 = Status(var1 = 15.0, var2 = 0.3);
julia> status2 = Status(var1 = 16.0, var2 = -Inf);
julia> vars_not_propagated = (:var1,);
jldoctest st1 julia> PlantSimEngine.propagatevalues!(status1, status2, varsnot_propagated);
jldoctest st1 julia> status2.var2 == status1.var2 true
jldoctest st1 julia> status2.var1 == status1.var1 false ```
PlantSimEngine.ref_var
— Methodref_var(v)
Create a reference to a variable. If the variable is already a Base.RefValue
, it is returned as is, else it is returned as a Ref to the copy of the value, or a Ref to the RefVector
(in case v
is a RefVector
).
Examples
julia> using PlantSimEngine;
julia> PlantSimEngine.ref_var(1.0)
Base.RefValue{Float64}(1.0)
julia> PlantSimEngine.ref_var([1.0])
Base.RefValue{Vector{Float64}}([1.0])
julia> PlantSimEngine.ref_var(Base.RefValue(1.0))
Base.RefValue{Float64}(1.0)
julia> PlantSimEngine.ref_var(Base.RefValue([1.0]))
Base.RefValue{Vector{Float64}}([1.0])
julia> PlantSimEngine.ref_var(PlantSimEngine.RefVector([Ref(1.0), Ref(2.0), Ref(3.0)]))
Base.RefValue{PlantSimEngine.RefVector{Float64}}(RefVector{Float64}[1.0, 2.0, 3.0])
PlantSimEngine.reverse_mapping
— Methodreverse_mapping(mapping::Dict{String,Tuple{Any,Vararg{Any}}}; all=true)
reverse_mapping(mapped_vars::Dict{String,Dict{Symbol,Any}})
Get the reverse mapping of a dictionary of model mapping, i.e. the variables that are mapped to other scales, or in other words, what variables are given to other scales from a given scale. This is used for e.g. knowing which scales are needed to add values to others.
Arguments
mapping::Dict{String,Any}
: A dictionary of model mapping.all::Bool
: Whether to get all the variables that are mapped to other scales, including the ones that are mapped as single values.
Returns
A dictionary of organs (keys) with a dictionary of organs => vector of pair of variables. You can read the output as: "for each organ (source organ), to which other organ (target organ) it is giving values for its own variables. Then for each of these source organs, which variable it is giving to the target organ (first symbol in the pair), and to which variable it is mapping the value into the target organ (second symbol in the pair)".
Examples
julia> using PlantSimEngine
Import example models (can be found in the examples
folder of the package, or in the Examples
sub-modules):
julia> using PlantSimEngine.Examples;
julia> mapping = Dict( "Plant" => MultiScaleModel( model=ToyCAllocationModel(), mapping=[ :carbon_assimilation => ["Leaf"], :carbon_demand => ["Leaf", "Internode"], :carbon_allocation => ["Leaf", "Internode"] ], ), "Internode" => ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), "Leaf" => ( MultiScaleModel( model=ToyAssimModel(), mapping=[:soil_water_content => "Soil",], ), ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), Status(aPPFD=1300.0, TT=10.0), ), "Soil" => ( ToySoilWaterModel(), ), );
Notice we provide "Soil", not ["Soil"] in the mapping of the ToyAssimModel
for the Leaf
. This is because we expect a single value for the soil_water_content
to be mapped here (there is only one soil). This allows to get the value as a singleton instead of a vector of values.
julia> PlantSimEngine.reverse_mapping(mapping)
Dict{String, Dict{String, Dict{Symbol, Any}}} with 3 entries:
"Soil" => Dict("Leaf"=>Dict(:soil_water_content=>:soil_water_content))
"Internode" => Dict("Plant"=>Dict(:carbon_allocation=>:carbon_allocation, :ca…
"Leaf" => Dict("Plant"=>Dict(:carbon_allocation=>:carbon_allocation, :ca…
PlantSimEngine.save_results!
— Methodsave_results!(object::GraphSimulation, i)
Save the results of the simulation for time-step i
into the object. For a GraphSimulation
object, this will save the results from the status(object)
in the outputs(object)
.
PlantSimEngine.search_inputs_in_multiscale_output
— Methodsearch_inputs_in_multiscale_output(process, organ, inputs, soft_dep_graphs)
Arguments
process::Symbol
: the process for which we want to find the soft dependencies at other scales.organ::String
: the organ for which we want to find the soft dependencies.inputs::Dict{Symbol, Vector{Pair{Symbol}, Tuple{Symbol, Vararg{Symbol}}}}
: a dict of process => [:subprocess => (:var1, :var2)].soft_dep_graphs::Dict{String, ...}
: a dict of organ => (softdepgraph, inputs, outputs).rev_mapping::Dict{Symbol, Symbol}
: a dict of mapped variable => source variable (this is the reverse mapping).- 'harddependenciesfromotherscale' : a vector of HardDependencyNode to provide access to the hard dependencies without traversing the whole graph
Details
The inputs (and similarly, outputs) give the inputs of each process, classified by the process it comes from. It can come from itself (its own inputs), or from another process that is a hard-dependency.
Returns
A dictionary with the soft dependencies variables found in outputs of other scales for each process, e.g.:
Dict{String, Dict{Symbol, Vector{Symbol}}} with 2 entries:
"Internode" => Dict(:carbon_demand=>[:carbon_demand])
"Leaf" => Dict(:carbon_assimilation=>[:carbon_assimilation], :carbon_demand=>[:carbon_demand])
This means that the variable :carbon_demand
is computed by the process :carbon_demand
at the scale "Internode", and the variable :carbon_assimilation
is computed by the process :carbon_assimilation
at the scale "Leaf". Those variables are used as inputs for the process that we just passed.
PlantSimEngine.search_inputs_in_output
— Methodsearch_inputs_in_output(process, inputs, outputs)
Return a dictionary with the soft dependencies of the processes in the dependency graph d
. A soft dependency is a dependency that is not explicitely defined in the model, but that can be inferred from the inputs and outputs of the processes.
Arguments
process::Symbol
: the process for which we want to find the soft dependencies.inputs::Dict{Symbol, Vector{Pair{Symbol}, Tuple{Symbol, Vararg{Symbol}}}}
: a dict of process => symbols of inputs per process.outputs::Dict{Symbol, Tuple{Symbol, Vararg{Symbol}}}
: a dict of process => symbols of outputs per process.
Details
The inputs (and similarly, outputs) give the inputs of each process, classified by the process it comes from. It can come from itself (its own inputs), or from another process that is a hard-dependency.
Returns
A dictionary with the soft dependencies for the processes.
Example
in_ = Dict(
:process3 => [:process3=>(:var4, :var5), :process2=>(:var1, :var3), :process1=>(:var1, :var2)],
:process4 => [:process4=>(:var0,)],
:process6 => [:process6=>(:var7, :var9)],
:process5 => [:process5=>(:var5, :var6)],
)
out_ = Dict(
:process3 => Pair{Symbol}[:process3=>(:var4, :var6), :process2=>(:var4, :var5), :process1=>(:var3,)],
:process4 => [:process4=>(:var1, :var2)],
:process6 => [:process6=>(:var8,)],
:process5 => [:process5=>(:var7,)],
)
search_inputs_in_output(:process3, in_, out_)
(process4 = (:var1, :var2),)
PlantSimEngine.soft_dependencies
— Functionsoft_dependencies(d::DependencyGraph)
Return a DependencyGraph
with the soft dependencies of the processes in the dependency graph d
. A soft dependency is a dependency that is not explicitely defined in the model, but that can be inferred from the inputs and outputs of the processes.
Arguments
d::DependencyGraph
: the hard-dependency graph.
Example
using PlantSimEngine
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples
# Create a model list:
models = ModelList(
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
process4=Process4Model(),
process5=Process5Model(),
process6=Process6Model(),
)
# Create the hard-dependency graph:
hard_dep = hard_dependencies(models.models, verbose=true)
# Get the soft dependencies graph:
soft_dep = soft_dependencies(hard_dep)
PlantSimEngine.status_from_template
— Methodstatus_from_template(d::Dict{Symbol,Any})
Create a status from a template dictionary of variables and values. If the values are already RefValues or RefVectors, they are used as is, else they are converted to Refs.
Arguments
d::Dict{Symbol,Any}
: A dictionary of variables and values.
Returns
- A
Status
.
Examples
julia> using PlantSimEngine
julia> a, b = PlantSimEngine.status_from_template(Dict(:a => 1.0, :b => 2.0));
julia> a
1.0
julia> b
2.0
PlantSimEngine.timestep_parallelizable
— Methodtimestep_parallelizable(x::T)
timestep_parallelizable(x::DependencyGraph)
Returns true
if the model x
is parallelizable, i.e. if the model can be computed in parallel over time-steps, or false
otherwise.
The default implementation returns false
for all models. If you develop a model that is parallelizable over time-steps, you should add a method to ObjectDependencyTrait
for your model.
Note that this method can also be applied on a DependencyGraph
directly, in which case it returns true
if all models in the graph are parallelizable, and false
otherwise.
See also
object_parallelizable
: Returnstrue
if the model is parallelizable over time-steps, andfalse
otherwise.parallelizable
: Returnstrue
if the model is parallelizable, andfalse
otherwise.TimeStepDependencyTrait
: Defines the trait about the eventual dependence of a model to other time-steps for its computation.
Examples
Define a dummy process:
using PlantSimEngine
# Define a test process:
@process "TestProcess"
Define a model that is time-step independent:
struct MyModel <: AbstractTestprocessModel end
# Override the time-step dependency trait:
PlantSimEngine.TimeStepDependencyTrait(::Type{MyModel}) = IsTimeStepIndependent()
Check if the model is parallelizable over objects:
timestep_parallelizable(MyModel()) # true
PlantSimEngine.transform_single_node_mapped_variables_as_self_node_output!
— Methodtransform_single_node_mapped_variables_as_self_node_output!(mapped_vars)
Find variables that are inputs to other scales as a SingleNodeMapping
and declare them as MappedVar from themselves in the source scale. This helps us declare it as a reference when we create the template status objects.
These node are found in the mapping as [:variable_name => "Plant"]
(notice that "Plant" is a scalar value).
PlantSimEngine.traverse_dependency_graph!
— Methodtraverse_dependency_graph(node::HardDependencyNode, f::Function, var::Vector)
Apply function f
to node
, and then its children (hard-dependency nodes).
Mutate the vector var
by pushing a pair of the node process name and the result of the function f
.
PlantSimEngine.traverse_dependency_graph!
— Methodtraverse_dependency_graph(node::SoftDependencyNode, f::Function, var::Vector; visit_hard_dep=true)
Apply function f
to node
, visit its hard dependency nodes (if visit_hard_dep=true
), and then its soft dependency children.
Mutate the vector var
by pushing a pair of the node process name and the result of the function f
.
PlantSimEngine.traverse_dependency_graph
— Methodtraverse_dependency_graph(graph::DependencyGraph, f::Function; visit_hard_dep=true)
Traverse the dependency graph
and apply the function f
to each node. The first-level soft-dependencies are traversed first, then their hard-dependencies (if visit_hard_dep=true
), and then the children of the soft-dependencies.
Return a vector of pairs of the node and the result of the function f
.
Example
using PlantSimEngine
# Including example processes and models:
using PlantSimEngine.Examples;
function f(node)
node.value
end
vars = (
process1=Process1Model(1.0),
process2=Process2Model(),
process3=Process3Model(),
process4=Process4Model(),
process5=Process5Model(),
process6=Process6Model(),
process7=Process7Model(),
)
graph = dep(vars)
traverse_dependency_graph(graph, f)
PlantSimEngine.variables_multiscale
— Functionvariables_multiscale(node, organ, mapping, st=NamedTuple())
Get the variables of a HardDependencyNode, taking into account the multiscale mapping, i.e. defining variables as MappedVar
if they are mapped to another scale. The default values are taken from the model if not given by the user (st
), and are marked as UninitializedVar
if they are inputs of the node.
Return a NamedTuple with the variables and their default values.
Arguments
node::HardDependencyNode
: the node to get the variables from.organ::String
: the organ type, e.g. "Leaf".vars_mapping::Dict{String,T}
: the mapping of the models (see details below).st::NamedTuple
: an optional named tuple with default values for the variables.
Details
The vars_mapping
is a dictionary with the organ type as key and a dictionary as value. It is computed from the user mapping like so:
PlantSimEngine.variables_outputs_from_other_scale
— Methodvariables_outputs_from_other_scale(mapped_vars)
For each organ in the mapped_vars
, find the variables that are outputs from another scale and not computed at this scale otherwise. This function is used with mapped_variables
PlantSimEngine.variables_typed
— Methodvariables_typed(model)
variables_typed(model, models...)
Returns a named tuple with the name and the types of the variables needed by a model, or a union of those for several models.
Examples
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
PlantSimEngine.variables_typed(Process1Model(1.0))
(var1 = Float64, var2 = Float64, var3 = Float64)
PlantSimEngine.variables_typed(Process1Model(1.0), Process2Model())
# output
(var4 = Float64, var5 = Float64, var1 = Float64, var2 = Float64, var3 = Float64)
See also
PlantSimEngine.vars_not_init_
— Methodvars_not_init_(st<:Status, var_names)
Get which variable is not properly initialized in the status struct.
Example models
PlantSimEngine provides example processes and models to users. They are available from a sub-module called Examples
. To get access to these models, you can simply use this sub-module:
using PlantSimEngine.Examples
The models are detailed below.
PlantSimEngine.Examples
— ModuleA sub-module with example models.
Examples used in the documentation for a set of multiscale models. The models can be found in the examples
folder of the package, and are stored in the following files:
ToyAssimModel.jl
ToyCDemandModel.jl
ToyCAllocationModel.jl
ToySoilModel.jl
Examples
using PlantSimEngine
using PlantSimEngine.Examples
ToyAssimModel()
PlantSimEngine.Examples.AbstractCarbon_AllocationModel
— Typecarbon_allocation
process abstract model.
All models implemented to simulate the carbon_allocation
process must be a subtype of this type, e.g. struct MyCarbon_AllocationModel <: AbstractCarbon_AllocationModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractCarbon_AllocationModel)
PlantSimEngine.Examples.AbstractCarbon_AssimilationModel
— Typecarbon_assimilation
process abstract model.
All models implemented to simulate the carbon_assimilation
process must be a subtype of this type, e.g. struct MyCarbon_AssimilationModel <: AbstractCarbon_AssimilationModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractCarbon_AssimilationModel)
PlantSimEngine.Examples.AbstractCarbon_BiomassModel
— Typecarbon_biomass
process abstract model.
All models implemented to simulate the carbon_biomass
process must be a subtype of this type, e.g. struct MyCarbon_BiomassModel <: AbstractCarbon_BiomassModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractCarbon_BiomassModel)
PlantSimEngine.Examples.AbstractCarbon_DemandModel
— Typecarbon_demand
process abstract model.
All models implemented to simulate the carbon_demand
process must be a subtype of this type, e.g. struct MyCarbon_DemandModel <: AbstractCarbon_DemandModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractCarbon_DemandModel)
PlantSimEngine.Examples.AbstractDegreedaysModel
— TypeDegreedays
process abstract model.
All models implemented to simulate the Degreedays
process must be a subtype of this type, e.g. struct MyDegreedaysModel <: AbstractDegreedaysModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractDegreedaysModel)
PlantSimEngine.Examples.AbstractGrowthModel
— Typegrowth
process abstract model.
All models implemented to simulate the growth
process must be a subtype of this type, e.g. struct MyGrowthModel <: AbstractGrowthModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractGrowthModel)
PlantSimEngine.Examples.AbstractLai_DynamicModel
— TypeLAI_Dynamic
process abstract model.
All models implemented to simulate the LAI_Dynamic
process must be a subtype of this type, e.g. struct MyLai_DynamicModel <: AbstractLai_DynamicModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractLai_DynamicModel)
PlantSimEngine.Examples.AbstractLeaf_SurfaceModel
— Typeleaf_surface
process abstract model.
All models implemented to simulate the leaf_surface
process must be a subtype of this type, e.g. struct MyLeaf_SurfaceModel <: AbstractLeaf_SurfaceModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractLeaf_SurfaceModel)
PlantSimEngine.Examples.AbstractLight_InterceptionModel
— Typelight_interception
process abstract model.
All models implemented to simulate the light_interception
process must be a subtype of this type, e.g. struct MyLight_InterceptionModel <: AbstractLight_InterceptionModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractLight_InterceptionModel)
PlantSimEngine.Examples.AbstractLight_PartitioningModel
— Typelight_partitioning
process abstract model.
All models implemented to simulate the light_partitioning
process must be a subtype of this type, e.g. struct MyLight_PartitioningModel <: AbstractLight_PartitioningModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractLight_PartitioningModel)
PlantSimEngine.Examples.AbstractMaintenance_RespirationModel
— Typemaintenance_respiration
process abstract model.
All models implemented to simulate the maintenance_respiration
process must be a subtype of this type, e.g. struct MyMaintenance_RespirationModel <: AbstractMaintenance_RespirationModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractMaintenance_RespirationModel)
PlantSimEngine.Examples.AbstractOrgan_EmergenceModel
— Typeorgan_emergence
process abstract model.
All models implemented to simulate the organ_emergence
process must be a subtype of this type, e.g. struct MyOrgan_EmergenceModel <: AbstractOrgan_EmergenceModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractOrgan_EmergenceModel)
PlantSimEngine.Examples.AbstractProcess1Model
— Typeprocess1
process abstract model.
All models implemented to simulate the process1
process must be a subtype of this type, e.g. struct MyProcess1Model <: AbstractProcess1Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess1Model)
PlantSimEngine.Examples.AbstractProcess2Model
— Typeprocess2
process abstract model.
All models implemented to simulate the process2
process must be a subtype of this type, e.g. struct MyProcess2Model <: AbstractProcess2Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess2Model)
PlantSimEngine.Examples.AbstractProcess3Model
— Typeprocess3
process abstract model.
All models implemented to simulate the process3
process must be a subtype of this type, e.g. struct MyProcess3Model <: AbstractProcess3Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess3Model)
PlantSimEngine.Examples.AbstractProcess4Model
— Typeprocess4
process abstract model.
All models implemented to simulate the process4
process must be a subtype of this type, e.g. struct MyProcess4Model <: AbstractProcess4Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess4Model)
PlantSimEngine.Examples.AbstractProcess5Model
— Typeprocess5
process abstract model.
All models implemented to simulate the process5
process must be a subtype of this type, e.g. struct MyProcess5Model <: AbstractProcess5Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess5Model)
PlantSimEngine.Examples.AbstractProcess6Model
— Typeprocess6
process abstract model.
All models implemented to simulate the process6
process must be a subtype of this type, e.g. struct MyProcess6Model <: AbstractProcess6Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess6Model)
PlantSimEngine.Examples.AbstractProcess7Model
— Typeprocess7
process abstract model.
All models implemented to simulate the process7
process must be a subtype of this type, e.g. struct MyProcess7Model <: AbstractProcess7Model end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractProcess7Model)
PlantSimEngine.Examples.AbstractSoil_WaterModel
— Typesoil_water
process abstract model.
All models implemented to simulate the soil_water
process must be a subtype of this type, e.g. struct MySoil_WaterModel <: AbstractSoil_WaterModel end
.
You can list all models implementing this process using subtypes
:
Examples
subtypes(AbstractSoil_WaterModel)
PlantSimEngine.Examples.Beer
— TypeBeer(k)
Beer-Lambert law for light interception.
Required inputs: LAI
in m² m⁻². Required meteorology data: Ri_PAR_f
, the incident flux of atmospheric radiation in the PAR, in W m[soil]⁻² (== J m[soil]⁻² s⁻¹).
Output: aPPFD, the absorbed Photosynthetic Photon Flux Density in μmol[PAR] m[leaf]⁻² s⁻¹.
PlantSimEngine.Examples.Process1Model
— TypeProcess1Model(a)
A dummy model implementing a "process1" process for testing purposes.
PlantSimEngine.Examples.Process2Model
— TypeProcess2Model()
A dummy model implementing a "process2" process for testing purposes.
PlantSimEngine.Examples.Process3Model
— TypeProcess3Model()
A dummy model implementing a "process3" process for testing purposes.
PlantSimEngine.Examples.Process4Model
— TypeProcess4Model()
A dummy model implementing a "process4" process for testing purposes. It computes the inputs needed for the coupled processes 1-2-3.
PlantSimEngine.Examples.Process5Model
— TypeProcess5Model()
A dummy model implementing a "process5" process for testing purposes. It needs the outputs from the coupled processes 1-2-3.
PlantSimEngine.Examples.Process6Model
— TypeProcess6Model()
A dummy model implementing a "process6" process for testing purposes. It needs the outputs from the coupled processes 1-2-3, but also from process 7 that is itself independant.
PlantSimEngine.Examples.Process7Model
— TypeProcess7Model()
A dummy model implementing a "process7" process for testing purposes. It is independent (needs :var0 only as for Process4Model), but its outputs are used by Process6Model, so it is a soft-coupling.
PlantSimEngine.Examples.ToyAssimGrowthModel
— TypeToyAssimGrowthModel(Rm_factor, Rg_cost)
ToyAssimGrowthModel(; LUE=0.2, Rm_factor = 0.5, Rg_cost = 1.2)
Computes the biomass growth of a plant.
Arguments
LUE=0.2
: the light use efficiency, in gC mol[PAR]⁻¹Rm_factor=0.5
: the fraction of assimilation that goes into maintenance respirationRg_cost=1.2
: the cost of growth maintenance, in gram of carbon biomass per gram of assimilate
Inputs
aPPFD
: the absorbed photosynthetic photon flux density, in mol[PAR] m⁻² time-step⁻¹
Outputs
carbon_assimilation
: the assimilation, in gC m⁻² time-step⁻¹Rm
: the maintenance respiration, in gC m⁻² time-step⁻¹Rg
: the growth respiration, in gC m⁻² time-step⁻¹biomass_increment
: the daily biomass increment, in gC m⁻² time-step⁻¹biomass
: the plant biomass, in gC m⁻² time-step⁻¹
PlantSimEngine.Examples.ToyAssimModel
— TypeToyAssimModel(LUE)
Computes the assimilation of a plant (= photosynthesis).
Arguments
LUE=0.2
: the light use efficiency, in gC mol[PAR]⁻¹
Inputs
aPPFD
: the absorbed photosynthetic photon flux density, in mol[PAR] m⁻² time-step⁻¹soil_water_content
: the soil water content, in %
Outputs
carbon_assimilation
: the assimilation or photosynthesis, also sometimes denotedA
, in gC m⁻² time-step⁻¹
Details
The assimilation is computed as the product of the absorbed photosynthetic photon flux density (aPPFD) and the light use efficiency (LUE), so the units of the assimilation usually are in gC m⁻² time-step⁻¹, but they could be in another spatial or temporal unit depending on the unit of aPPFD
, e.g. if aPPFD
is in mol[PAR] plant⁻¹ time-step⁻¹, the assimilation will be in gC plant⁻¹ time-step⁻¹.
PlantSimEngine.Examples.ToyCAllocationModel
— TypeToyCAllocationModel()
Computes the carbon allocation to each organ of a plant based on the plant total carbon offer and individual organ demand. This model should be used at the plant scale, because it first computes the carbon availaible for allocation as the minimum between the total demand (sum of organs' demand) and total carbon offer (sum of organs' assimilation - total maintenance respiration), and then allocates the carbon relative to each organ's demand.
Inputs
carbon_assimilation
: a vector of the assimilation of all photosynthetic organs, usually in gC m⁻² time-step⁻¹Rm
: the maintenance respiration of the plant, usually in gC m⁻² time-step⁻¹carbon_demand
: a vector of the carbon demand of the organs, usually in gC m⁻² time-step⁻¹
Outputs
carbon_assimilation
: the carbon assimilation, usually in gC m⁻² time-step⁻¹
Details
The units usually are in gC m⁻² time-step⁻¹, but they could be in another spatial or temporal unit depending on the unit of the inputs, e.g. in gC plant⁻¹ time-step⁻¹.
PlantSimEngine.Examples.ToyCBiomassModel
— TypeToyCBiomassModel(construction_cost)
Computes the carbon biomass of an organ based on the carbon allocation and construction cost.
Arguments
construction_cost
: the construction cost of the organ, usually in gC gC⁻¹. Should be understood as the amount of carbon needed to build 1g of carbon biomass.
Inputs
carbon_allocation
: the carbon allocation to the organ for the time-step, usually in gC m⁻² time-step⁻¹
Outputs
carbon_biomass_increment
: the increment of carbon biomass, usually in gC time-step⁻¹carbon_biomass
: the carbon biomass, usually in gCgrowth_respiration
: the growth respiration, usually in gC time-step⁻¹
PlantSimEngine.Examples.ToyCDemandModel
— TypeToyCDemandModel(optimal_biomass, development_duration)
ToyCDemandModel(; optimal_biomass, development_duration)
Computes the carbon demand of an organ depending on its biomass under optimal conditions and the duration of its development in degree days. The model assumes that the carbon demand is linear througout the duration of the development.
Arguments
optimal_biomass
: the biomass of the organ under optimal conditions, in gCdevelopment_duration
: the duration of the development of the organ, in degree days
Inputs
TT
: the thermal time, in degree days
Outputs
carbon_demand
: the carbon demand, in gC
PlantSimEngine.Examples.ToyDegreeDaysCumulModel
— TypeToyDegreeDaysCumulModel(;init_TT=0.0, T_base=10.0, T_max=43.0)
Computes the thermal time in degree days and cumulated degree-days based on the average daily temperature (T
), the initial cumulated degree days, the base temperature below which there is no growth, and the maximum temperature for growh.
PlantSimEngine.Examples.ToyInternodeEmergence
— TypeToyInternodeEmergence(;init_TT=0.0, TT_emergence = 300)
Computes the organ emergence based on cumulated thermal time since last event.
PlantSimEngine.Examples.ToyLAIModel
— TypeToyLAIModel(;max_lai=8.0, dd_incslope=800, inc_slope=110, dd_decslope=1500, dec_slope=20)
Computes the Leaf Area Index (LAI) based on a sigmoid function of thermal time.
Arguments
max_lai
: the maximum LAI valuedd_incslope
: the thermal time at which the LAI starts to increaseinc_slope
: the slope of the increasedd_decslope
: the thermal time at which the LAI starts to decreasedec_slope
: the slope of the decrease
Inputs
TT_cu
: the cumulated thermal time since the beginning of the simulation, usually in °C days
Outputs
LAI
: the Leaf Area Index, usually in m² m⁻²
PlantSimEngine.Examples.ToyLAIfromLeafAreaModel
— TypeToyLAIfromLeafAreaModel()
Computes the Leaf Area Index (LAI) of the scene based on the plants leaf area.
Arguments
scene_area
: the area of the scene, usually in m²
Inputs
surface
: a vector of plant leaf surfaces, usually in m²
Outputs
LAI
: the Leaf Area Index of the scene, usually in m² m⁻²total_surface
: the total surface of the plants, usually in m²
PlantSimEngine.Examples.ToyLeafSurfaceModel
— TypeToyLeafSurfaceModel(SLA)
Computes the individual leaf surface from its biomass using the SLA.
Arguments
SLA
: the specific leaf area, usually in m² gC⁻¹. Should be understood as the surface area of a leaf per unit of carbon biomass.
Values typically range from 0.002 to 0.027 m² gC⁻¹.
Inputs
carbon_biomass
: the carbon biomass of the leaf, usually in gC
Outputs
surface
: the leaf surface, usually in m²
PlantSimEngine.Examples.ToyLightPartitioningModel
— TypeToyLightPartitioningModel()
Computes the light partitioning based on relative surface.
Inputs
aPPFD
: the absorbed photosynthetic photon flux density at the larger scale (e.g. scene), in mol[PAR] m⁻² time-step⁻¹
Outputs
aPPFD
: the assimilation or photosynthesis, also sometimes denotedA
, in gC time-step⁻¹
Details
PlantSimEngine.Examples.ToyMaintenanceRespirationModel
— TypeRmQ10FixedN(Q10, Rm_base, T_ref, P_alive, nitrogen_content)
Maintenance respiration based on a Q10 computation with fixed nitrogen values and proportion of living cells in the organs.
Arguments
Q10
: Q10 factor (values should usually range between: 1.5 - 2.5, with 2.1 being the most common value)Rm_base
: Base maintenance respiration (gC gDM⁻¹ time-step⁻¹). Should be around 0.06.T_ref
: Reference temperature at which Q10 was measured (usually around 25.0°C)P_alive
: proportion of living cells in the organnitrogen_content
: nitrogen content of the organ (gN gC⁻¹)
Inputs
carbon_biomass
: the carbon biomass of the organ in gC
PlantSimEngine.Examples.ToyPlantLeafSurfaceModel
— TypeToyPlantLeafSurfaceModel()
Computes the leaf surface at plant scale by summing the individual leaf surfaces.
Inputs
leaf_surfaces
: a vector of leaf surfaces, usually in m²
Outputs
surface
: the leaf surface at plant scale, usually in m²
PlantSimEngine.Examples.ToyPlantRmModel
— TypeToyPlantRmModel()
Total plant maintenance respiration based on the sum of Rm_organs
, the maintenance respiration of the organs.
Intputs
Rm_organs
: a vector of maintenance respiration from all organs in the plant in gC time-step⁻¹
Outputs
Rm
: the total plant maintenance respiration in gC time-step⁻¹
PlantSimEngine.Examples.ToyRUEGrowthModel
— TypeToyRUEGrowthModel(efficiency)
Computes the carbon biomass increment of a plant based on the radiation use efficiency principle.
Arguments
efficiency
: the radiation use efficiency, in gC[biomass] mol[PAR]⁻¹
Inputs
aPPFD
: the absorbed photosynthetic photon flux density, in mol[PAR] m⁻² time-step⁻¹
Outputs
biomass_increment
: the daily biomass increment, in gC[biomass] m⁻² time-step⁻¹biomass
: the plant biomass, in gC[biomass] m⁻² time-step⁻¹
PlantSimEngine.Examples.ToySoilWaterModel
— TypeToySoilWaterModel(values=[0.5])
A toy model to compute the soil water content. The model simply take a random value in the values
range using rand
.
Outputs
soil_water_content
: the soil water content (%).
Arguments
values
: a range ofsoil_water_content
values to sample from. Can be a vector of values[0.5,0.6]
or a range0.1:0.1:1.0
. Default is[0.5]
.
PlantSimEngine.Examples.import_mtg_example
— Methodimport_mtg_example()
Returns an example multiscale tree graph (MTG) with a scene, a soil, and a plant with two internodes and two leaves.
Examples
julia> using PlantSimEngine.Examples
julia> import_mtg_example()
/ 1: Scene
├─ / 2: Soil
└─ + 3: Plant
└─ / 4: Internode
├─ + 5: Leaf
└─ < 6: Internode
└─ + 7: Leaf
PlantSimEngine.fit
— Methodfit(::Type{Beer}, df; J_to_umol=PlantMeteo.Constants().J_to_umol)
Compute the k
parameter of the Beer-Lambert law from measurements.
Arguments
::Type{Beer}
: the model typedf
: aDataFrame
with the following columns:aPPFD
: the measured absorbed Photosynthetic Photon Flux Density in μmol[PAR] m[leaf]⁻² s⁻¹LAI
: the measured leaf area index in m² m⁻²Ri_PAR_f
: the measured incident flux of atmospheric radiation in the PAR, in W m[soil]⁻² (== J m[soil]⁻² s⁻¹)
Examples
Import the example models defined in the Examples
sub-module:
using PlantSimEngine
using PlantSimEngine.Examples
Create a model list with a Beer model, and fit it to the data:
m = ModelList(Beer(0.6), status=(LAI=2.0,))
meteo = Atmosphere(T=20.0, Wind=1.0, P=101.3, Rh=0.65, Ri_PAR_f=300.0)
run!(m, meteo)
df = DataFrame(aPPFD=m[:aPPFD][1], LAI=m.status.LAI[1], Ri_PAR_f=meteo.Ri_PAR_f[1])
fit(Beer, df)
PlantSimEngine.run!
— Functionrun!(::Beer, object, meteo, constants=Constants(), extra=nothing)
Computes the photosynthetic photon flux density (aPPFD
, µmol m⁻² s⁻¹) absorbed by an object using the incoming PAR radiation flux (Ri_PAR_f
, W m⁻²) and the Beer-Lambert law of light extinction.
Arguments
::Beer
: a Beer model, from the model list (i.e. m.light_interception)models
: AModelList
struct holding the parameters for the model with
initialisations for LAI
(m² m⁻²): the leaf area index.
status
: the status of the model, usually the model list status (i.e. m.status)meteo
: meteorology structure, seeAtmosphere
constants = PlantMeteo.Constants()
: physical constants. SeePlantMeteo.Constants
for more detailsextra = nothing
: extra arguments, not used here.
Examples
m = ModelList(Beer(0.5), status=(LAI=2.0,))
meteo = Atmosphere(T=20.0, Wind=1.0, P=101.3, Rh=0.65, Ri_PAR_q=300.0)
run!(m, meteo)
m[:aPPFD]