API

Index

API documentation

PlantMeteo.TimeStepTableMethod
TimeStepTable{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),
    ]
)
source
PlantSimEngine.ModelListType
ModelList(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.

Note

The status field depends on the input models. You can get the variables needed by a model using variables on the instantiation of a model. You can also use inputs and outputs instead.

Arguments

  • models: a list of models. Usually given as a NamedTuple, 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. If nothing, 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 ModelListmakes 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).

source
PlantSimEngine.MultiScaleModelType
MultiScaleModel(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-scale
  • mapping<: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:

  1. [:variable_name => "Plant"]: We take one value from the Plant node
  2. [:variable_name => ["Leaf"]]: We take a vector of values from the Leaf nodes
  3. [:variable_name => ["Leaf", "Internode"]]: We take a vector of values from the Leaf and Internode nodes
  4. [:variable_name => "Plant" => :variable_name_in_plant_scale]: We take one value from another variable name in the Plant node
  5. [: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
  6. [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
  7. [:variable_name => :variable_name_from_another_model]: We take the value from another model at the same scale but rename it
  8. [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:

  1. The variable variable_name of the model will be taken from the Plant node, assuming only one node has the Plant 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.

  1. The variable variable_name of the model will be taken from the Leaf 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.

  1. The variable variable_name of the model will be taken from the Leaf and Internode nodes. The values will be taken from all the nodes of type Leaf

and Internode.

  1. The variable variable_name of the model will be taken from the variable called variable_name_in_plant_scale in the Plant node. This is useful

when the variable name in the model is different from the variable name in the scale it is taken from.

  1. The variable variable_name of the model will be taken from the variable called variable_name_1 in the Leaf node and variable_name_2 in the Internode node.

  2. 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.

  1. The variable variable_name of the model will be taken from another model at the same scale, but with another variable name.

  2. 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()
source
PlantSimEngine.PreviousTimeStepType
PreviousTimeStep(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).

source
PlantSimEngine.StatusType
Status(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
source
PlantSimEngine.EFMethod
EF(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)
source
PlantSimEngine.NRMSEMethod
NRMSE(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)
source
PlantSimEngine.RMSEMethod
RMSE(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)
source
PlantSimEngine.add_organ!Method
add_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. the GraphSimulation object from the extra 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 node id 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 to init_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.

source
PlantSimEngine.depFunction
dep(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):

  1. The process has no inputs. It is completely independent, and is placed as one of the roots of the dependency graph.
  2. The process needs inputs from models at its own scale. We put it as a child of this other process.
  3. The process needs inputs from another scale. We put it as a child of this process at another scale.
  4. The process needs inputs from its own scale and another scale. We put it as a child of both.
  5. 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...)
source
PlantSimEngine.drMethod
dr(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)
source
PlantSimEngine.fitFunction
fit()

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.

source
PlantSimEngine.init_status!Method
init_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"])
source
PlantSimEngine.init_variablesMethod
init_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())
source
PlantSimEngine.inputsMethod
inputs(model::AbstractModel)
inputs(...)

Get the inputs of one or several models.

Returns an empty tuple by default for AbstractModels (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)
source
PlantSimEngine.inputsMethod
inputs(mapping::Dict{String,T})

Get the inputs of the models in a mapping, for each process and organ type.

source
PlantSimEngine.is_initializedMethod
is_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)
source
PlantSimEngine.outputsMethod
outputs(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 by run!.
  • sink: a sink compatible with the Tables.jl interface (e.g. a DataFrame)
  • refvectors: if false (default), the function will remove the RefVector values, otherwise it will keep them
  • no_value: the value to replace nothing values. Default is nothing. Usually used to replace nothing 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)
source
PlantSimEngine.outputsMethod
outputs(model::AbstractModel)
outputs(...)

Get the outputs of one or several models.

Returns an empty tuple by default for AbstractModels (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,)
source
PlantSimEngine.outputsMethod
outputs(mapping::Dict{String,T})

Get the outputs of the models in a mapping, for each process and organ type.

source
PlantSimEngine.run!Function
run!(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

PlantMeteo.Atmosphere or a single PlantMeteo.Atmosphere.

  • constants: a PlantMeteo.Constants object, or a NamedTuple of constant keys and values.
  • extra: extra parameters, not available for simulation of plant graphs (the simulation object is passed using this).
  • check: if true, check the validity of the model list before running the simulation (takes a little bit of time), and return more information while running.
  • executor: the Floops 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])
source
PlantSimEngine.statusMethod
status(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
source
PlantSimEngine.to_initializeMethod
to_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: if true, print information messages.
  • vars...: the models and processes to consider.
  • m::T: a ModelList.
  • m::DependencyGraph: a DependencyGraph.
  • 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)
source
PlantSimEngine.to_initializeMethod
to_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).

source
PlantSimEngine.variablesMethod
variables(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)
source
PlantSimEngine.variablesMethod
variables(m::AbstractDependencyNode)

Returns a tuple with the name of the inputs and outputs variables needed by a model in a dependency graph.

source
PlantSimEngine.variablesMethod
variables(mapping::Dict{String,T})

Get the variables (inputs and outputs) of the models in a mapping, for each process and organ type.

source
PlantSimEngine.variablesMethod
variables(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

source
PlantSimEngine.@processMacro
@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"
source

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.DataFrameMethod
DataFrame(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)
source
DataFrames.DataFrameMethod
DataFrame(components::ModelList{T,S,V}) where {T,S<:Status,V}

Implementation of DataFrame for a ModelList model with one time step.

source
DataFrames.DataFrameMethod
DataFrame(components::ModelList{T,<:TimeStepTable})

Implementation of DataFrame for a ModelList model with several time steps.

source
PlantSimEngine.DataFormatMethod
DataFormat(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. a DataFrame or a TimeStepTable. The data is iterated over by rows using the Tables.jl interface.
  • SingletonAlike: The data is a singleton-like object, e.g. a NamedTuple or a TimeStepRow. The data is iterated over by columns.
  • TreeAlike: The data is a tree-like object, e.g. a Node.

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()
source
PlantSimEngine.DependencyGraphType
DependencyGraph{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.
source
PlantSimEngine.DependencyTraitType
DependencyTrait(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.
source
PlantSimEngine.GraphSimulationType
GraphSimulation(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 MTG
  • mapping: a dictionary of model mapping
  • statuses: a structure that defines the status of each node in the graph
  • status_templates: a dictionary of status templates
  • reverse_multiscale_mapping: a dictionary of mapping for other scales
  • var_need_init: a dictionary indicating if a variable needs to be initialized
  • dependency_graph: the dependency graph of the models applied to the graph
  • models: a dictionary of models
  • outputs: a dictionary of outputs
source
PlantSimEngine.MappedVarType
MappedVar(source_organ, variable, source_variable, source_default)

A variable mapped to another scale.

Arguments

  • source_organ: the organ(s) that are targeted by the mapping
  • variable: the name of the variable that is mapped
  • source_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)
source
PlantSimEngine.MultiNodeMappingType
MultiNodeMapping(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.

source
PlantSimEngine.ObjectDependencyTraitType
ObjectDependencyTrait(::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:

  1. 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)

  1. It is easy to override this trait for models that are object independent

See also

  • timestep_parallelizable: Returns true if the model is parallelizable over time-steps, and false otherwise.
  • object_parallelizable: Returns true if the model is parallelizable over objects, and false otherwise.
  • parallelizable: Returns true if the model is parallelizable, and false 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
source
PlantSimEngine.RefVariableType
RefVariable(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.

source
PlantSimEngine.RefVectorType
RefVector(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 reference
  • sts...: the structs to reference
  • sts::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
source
PlantSimEngine.SelfNodeMappingType
SelfNodeMapping()

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.

source
PlantSimEngine.SingleNodeMappingType
SingleNodeMapping(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.

source
PlantSimEngine.TimeStepDependencyTraitMethod
TimeStepDependencyTrait(::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:

  1. 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)

  1. It is easy to override this trait for models that are time-step independent

See also

  • timestep_parallelizable: Returns true if the model is parallelizable over time-steps, and false otherwise.
  • object_parallelizable: Returns true if the model is parallelizable over objects, and false otherwise.
  • parallelizable: Returns true if the model is parallelizable, and false 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
source
Base.copyMethod
Base.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))])
source
Base.getindexMethod
getindex(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
source
PlantSimEngine.add_model_varsMethod
add_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.

source
PlantSimEngine.check_dimensionsMethod
check_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).
source
PlantSimEngine.convert_reference_values!Method
convert_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.

source
PlantSimEngine.convert_varsFunction
convert_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)
source
PlantSimEngine.convert_vars!Function
convert_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)
source
PlantSimEngine.convert_vars!Method
convert_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.

source
PlantSimEngine.default_variables_from_mappingFunction
default_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.
source
PlantSimEngine.draw_panelMethod
draw_panel(node, graph, prefix, dep_graph_guides, parent; title="Soft-coupled model")

Draw the panels for all dependencies

source
PlantSimEngine.drop_processMethod
drop_process(proc_vars, process)

Return a new NamedTuple with the process process removed from the NamedTuple proc_vars.

Arguments

  • proc_vars::NamedTuple: the NamedTuple from which we want to remove the process process.
  • process::Symbol: the process we want to remove from the NamedTuple 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,)
source
PlantSimEngine.flatten_varsMethod
flatten_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)
source
PlantSimEngine.get_mappingMethod
get_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.

source
PlantSimEngine.get_modelsMethod
get_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)
source
PlantSimEngine.get_multiscale_default_valueFunction
get_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.
source
PlantSimEngine.hard_dependenciesMethod
hard_dependencies(models; verbose::Bool=true)
hard_dependencies(mapping::Dict{String,T}; verbose::Bool=true)

Compute the hard dependencies between models.

source
PlantSimEngine.homogeneous_ts_kwargsMethod
kwargs_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))
source
PlantSimEngine.init_node_status!Function
init_node_status!(
    node, 
    statuses, 
    mapped_vars, 
    reverse_multiscale_mapping,
    vars_need_init=Dict{String,Any}(),
    type_promotion=nothing;
    check=true
)

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 initialise
  • statuses: the dictionary of statuses by node type
  • mapped_vars: the template of status for each node type
  • reverse_multiscale_mapping: the variables that are mapped to other scales
  • var_need_init: the variables that are not initialised or computed by other models
  • nodes_with_models: the nodes that have a model defined for their symbol
  • type_promotion: the type promotion to use for the variables
  • check: whether to check the mapping for errors (see details)

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 using mapped_variables(), see code in init_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.

source
PlantSimEngine.init_simulationMethod
init_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 MTG
  • mapping::Dict{String,Any}: a dictionary of model mapping
  • nsteps: the number of steps of the simulation
  • outputs: the dynamic outputs needed for the simulation
  • type_promotion: the type promotion to use for the variables
  • check: whether to check the mapping for errors. Passed to init_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.

source
PlantSimEngine.init_statusesFunction
init_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 graph
  • mapping: a dictionary of model mapping
  • 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.

  • type_promotion: the type promotion to use for the variables
  • verbose: print information when compiling the mapping
  • check: whether to check the mapping for errors. Passed to init_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)

source
PlantSimEngine.init_variables_manualMethod
init_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,))
source
PlantSimEngine.is_graph_cyclicMethod
is_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: if true, return the full stack of nodes that makes the cycle, otherwise return only the cycle.
  • warn::Bool=true: if true, 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.

source
PlantSimEngine.mapped_variablesFunction
mapped_variables(mapping, dependency_graph=hard_dependencies(mapping; verbose=false); verbose=false)

Get the variables for each organ type from a dependency graph, with MappedVars 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.
source
PlantSimEngine.mapped_variables_no_outputs_from_other_scaleFunction
mapped_variables_no_outputs_from_other_scale(mapping, dependency_graph=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.

source
PlantSimEngine.model_Method
model_(m::AbstractModel)

Get the model of an AbstractModel (it is the model itself if it is not a MultiScaleModel).

source
PlantSimEngine.object_parallelizableMethod
object_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: Returns true if the model is parallelizable over time-steps, and false otherwise.
  • parallelizable: Returns true if the model is parallelizable, and false 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
source
PlantSimEngine.parallelizableMethod
parallelizable(::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

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())
source
PlantSimEngine.pre_allocate_outputsMethod
pre_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 type
  • outs: a dictionary of outputs by node type
  • nsteps: the number of time-steps
  • check: 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, = 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, 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
source
PlantSimEngine.propagate_values!Method
propagate_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 ```

source
PlantSimEngine.ref_varMethod
ref_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])
source
PlantSimEngine.reverse_mappingMethod
reverse_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…
source
PlantSimEngine.save_results!Method
save_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).

source
PlantSimEngine.search_inputs_in_multiscale_outputMethod
search_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).

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.

source
PlantSimEngine.search_inputs_in_outputMethod
search_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),)
source
PlantSimEngine.soft_dependenciesFunction
soft_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)
source
PlantSimEngine.status_from_templateMethod
status_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

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
source
PlantSimEngine.timestep_parallelizableMethod
timestep_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: Returns true if the model is parallelizable over time-steps, and false otherwise.
  • parallelizable: Returns true if the model is parallelizable, and false 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
source
PlantSimEngine.transform_single_node_mapped_variables_as_self_node_output!Method
transform_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).

source
PlantSimEngine.traverse_dependency_graph!Method
traverse_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.

source
PlantSimEngine.traverse_dependency_graph!Method
traverse_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.

source
PlantSimEngine.traverse_dependency_graphMethod
traverse_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)
source
PlantSimEngine.variables_multiscaleFunction
variables_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:

full_vars_mapping = Dict(first(mod) => Dict(get_mapping(last(mod))) for mod in mapping)
source
PlantSimEngine.variables_outputs_from_other_scaleMethod
variables_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

source
PlantSimEngine.variables_typedMethod
variables_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

inputs, outputs and variables

source

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.ExamplesModule

A 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()
source
PlantSimEngine.Examples.AbstractCarbon_AllocationModelType

carbon_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)
source
PlantSimEngine.Examples.AbstractCarbon_AssimilationModelType

carbon_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)
source
PlantSimEngine.Examples.AbstractCarbon_BiomassModelType

carbon_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)
source
PlantSimEngine.Examples.AbstractCarbon_DemandModelType

carbon_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)
source
PlantSimEngine.Examples.AbstractDegreedaysModelType

Degreedays 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)
source
PlantSimEngine.Examples.AbstractGrowthModelType

growth 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)
source
PlantSimEngine.Examples.AbstractLai_DynamicModelType

LAI_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)
source
PlantSimEngine.Examples.AbstractLeaf_SurfaceModelType

leaf_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)
source
PlantSimEngine.Examples.AbstractLight_InterceptionModelType

light_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)
source
PlantSimEngine.Examples.AbstractLight_PartitioningModelType

light_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)
source
PlantSimEngine.Examples.AbstractMaintenance_RespirationModelType

maintenance_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)
source
PlantSimEngine.Examples.AbstractOrgan_EmergenceModelType

organ_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)
source
PlantSimEngine.Examples.AbstractProcess1ModelType

process1 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)
source
PlantSimEngine.Examples.AbstractProcess2ModelType

process2 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)
source
PlantSimEngine.Examples.AbstractProcess3ModelType

process3 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)
source
PlantSimEngine.Examples.AbstractProcess4ModelType

process4 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)
source
PlantSimEngine.Examples.AbstractProcess5ModelType

process5 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)
source
PlantSimEngine.Examples.AbstractProcess6ModelType

process6 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)
source
PlantSimEngine.Examples.AbstractProcess7ModelType

process7 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)
source
PlantSimEngine.Examples.AbstractSoil_WaterModelType

soil_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)
source
PlantSimEngine.Examples.BeerType
Beer(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⁻¹.

source
PlantSimEngine.Examples.Process6ModelType
Process6Model()

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.

source
PlantSimEngine.Examples.Process7ModelType
Process7Model()

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.

source
PlantSimEngine.Examples.ToyAssimGrowthModelType
ToyAssimGrowthModel(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 respiration
  • Rg_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⁻¹
source
PlantSimEngine.Examples.ToyAssimModelType
ToyAssimModel(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 denoted A, 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⁻¹.

source
PlantSimEngine.Examples.ToyCAllocationModelType
ToyCAllocationModel()

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⁻¹.

source
PlantSimEngine.Examples.ToyCBiomassModelType
ToyCBiomassModel(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 gC
  • growth_respiration: the growth respiration, usually in gC time-step⁻¹
source
PlantSimEngine.Examples.ToyCDemandModelType
ToyCDemandModel(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 gC
  • development_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
source
PlantSimEngine.Examples.ToyDegreeDaysCumulModelType
ToyDegreeDaysCumulModel(;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.

source
PlantSimEngine.Examples.ToyLAIModelType
ToyLAIModel(;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 value
  • dd_incslope: the thermal time at which the LAI starts to increase
  • inc_slope: the slope of the increase
  • dd_decslope: the thermal time at which the LAI starts to decrease
  • dec_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⁻²
source
PlantSimEngine.Examples.ToyLAIfromLeafAreaModelType
ToyLAIfromLeafAreaModel()

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²
source
PlantSimEngine.Examples.ToyLeafSurfaceModelType
ToyLeafSurfaceModel(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²
source
PlantSimEngine.Examples.ToyLightPartitioningModelType
ToyLightPartitioningModel()

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 denoted A, in gC time-step⁻¹

Details

source
PlantSimEngine.Examples.ToyMaintenanceRespirationModelType
RmQ10FixedN(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 organ
  • nitrogen_content: nitrogen content of the organ (gN gC⁻¹)

Inputs

  • carbon_biomass: the carbon biomass of the organ in gC
source
PlantSimEngine.Examples.ToyPlantLeafSurfaceModelType
ToyPlantLeafSurfaceModel()

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²
source
PlantSimEngine.Examples.ToyPlantRmModelType
ToyPlantRmModel()

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⁻¹
source
PlantSimEngine.Examples.ToyRUEGrowthModelType
ToyRUEGrowthModel(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⁻¹
source
PlantSimEngine.Examples.ToySoilWaterModelType
ToySoilWaterModel(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 of soil_water_content values to sample from. Can be a vector of values [0.5,0.6] or a range 0.1:0.1:1.0. Default is [0.5].
source
PlantSimEngine.Examples.import_mtg_exampleMethod
import_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
source
PlantSimEngine.fitMethod
fit(::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 type
  • df: a DataFrame 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)
source
PlantSimEngine.run!Function
run!(::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: A ModelList 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, see Atmosphere
  • constants = PlantMeteo.Constants(): physical constants. See PlantMeteo.Constants for more details
  • extra = 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]
source