You can supply measured leaf area index (LAI) while calculating light interception. This is useful for testing the light model independently of a model that predicts LAI. Here LAI is m² leaf per m² ground, and incident PAR is an energy flux in W m⁻² ground.
Supply a constant LAI in status, where the canopy stores its values, and use only the light model:
using PlantSimEngine, Dates, DataFrames
using PlantSimEngine.Examples
fixed_model = CompositeModel(
Beer(0.6);
status=(LAI=2.0,), id=:canopy, scale=:Canopy,
environment=(Ri_PAR_f=100.0, duration=Day(1)),
)
fixed_simulation = run!(fixed_model; outputs=:all)
final_state(fixed_simulation).aPPFDIf you add a model that calculates LAI, it will replace this supplied value when it runs. To keep the observed LAI fixed, use it without an LAI model.
For observations that change over time, use a model that reads each observation and supplies it as LAI. The small ObservedLAI model below only copies the value; it performs no fitting, interpolation, or unit conversion. Include it from the downloadable source to run the example:
include("observed_lai.jl")
observations = DataFrame(
duration=fill(Day(1), 3),
measured_LAI=[1.0, 2.0, 3.0],
Ri_PAR_f=fill(100.0, 3),
)
model = CompositeModel(
ObservedLAI(), Beer(0.6);
id=:canopy, scale=:Canopy, environment=observations,
)
simulation = run!(model; steps=nrow(observations), outputs=:all)
rows = collect_outputs(simulation; sink=DataFrame)
filter(row -> row.variable == :LAI || row.variable == :aPPFD, rows)| Row | timestep | time | application_id | object_id | variable | value |
|---|---|---|---|---|---|---|
| Int64 | Float64 | Symbol | Symbol | Symbol | Float64 | |
| 1 | 1 | 1.0 | LAI_Dynamic | canopy | LAI | 1.0 |
| 2 | 1 | 1.0 | light_interception | canopy | aPPFD | 206.193 |
| 3 | 2 | 2.0 | LAI_Dynamic | canopy | LAI | 2.0 |
| 4 | 2 | 2.0 | light_interception | canopy | aPPFD | 319.354 |
| 5 | 3 | 3.0 | LAI_Dynamic | canopy | LAI | 3.0 |
| 6 | 3 | 3.0 | light_interception | canopy | aPPFD | 381.458 |
PlantSimEngine passes LAI from ObservedLAI to Beer automatically: one model supplies it and the other needs it on the same canopy. To predict LAI instead, replace ObservedLAI() with ToyLAIModel() and supply the thermal time that model requires. A change of model can change the inputs you must provide; compare alternatives before making a replacement.
The complete source file is short:
struct ObservedLAI <: PlantSimEngine.Examples.AbstractLai_DynamicModel end
PlantSimEngine.inputs_(::ObservedLAI) = NamedTuple()
PlantSimEngine.outputs_(::ObservedLAI) = (LAI=0.0,)
PlantSimEngine.environment_inputs_(::ObservedLAI) = (measured_LAI=0.0,)
function PlantSimEngine.run!(::ObservedLAI, status, environment, constants, context)
status.LAI = environment.measured_LAI
return nothing
endBefore using real measurements, check their units, whether they refer to leaf or ground area, their time stamps, and any missing values. This example has one observation per day. If your measurements are less frequent, decide how to fill the gaps: for example, interpolate between measurements or keep the last measured value until the next one. Document that choice before using the data. This approach supplies a measured input directly. It does not perform data assimilation, which would use observations to estimate or correct the model state and could account for measurement uncertainty.
See Collect and plot results for displaying predictions alongside observations and Parameter fitting when the objective is to estimate model parameters.