PlantSimEngine.jlPlantSimEngine.jl

Implement Environment And Cadence Traits​#

A model may need air temperature or may be intended to run once a day. Describe these requirements alongside the model's equation. The simulation setup then chooses where the temperature comes from and how often the model runs. This update frequency is also called its cadence.

Start with Understand Environments and Give Models Different Cadences for the scenario-user perspective.

Declare an environmental input​#

This teaching model simply copies the environmental temperature to an output. It shows how to read environmental data before adding a biological equation. These are its actual definitions in examples/ToySpatialEnvironment.jl:

julia
struct ToyEnvironmentReaderModel <: AbstractToy_Environment_ReaderModel end

PlantSimEngine.inputs_(::ToyEnvironmentReaderModel) = NamedTuple()
PlantSimEngine.outputs_(::ToyEnvironmentReaderModel) = (temperature_seen=0.0,)
PlantSimEngine.environment_inputs_(::ToyEnvironmentReaderModel) = (T=0.0,)

function PlantSimEngine.run!(
    ::ToyEnvironmentReaderModel,
    status,
    environment,
    constants,
    context,
)
    status.temperature_seen = environment.T
    return nothing
end

environment_inputs_ declares T, and run! reads it from environment.T. The model does not need to know whether the temperature comes from a weather file or varies with position in the canopy.

Test that read directly:

julia
using Dates, Test, PlantSimEngine
using PlantSimEngine.Examples

reader = ToyEnvironmentReaderModel()
sample = Status(temperature_seen=0.0)
PlantSimEngine.run!(reader, sample, (T=25.0,), nothing, nothing)
@test sample.temperature_seen == 25.0
sample.temperature_seen
25.0

Then supply an environment through the simulation:

julia
model = CompositeModel(
    reader;
    environment=(T=25.0, duration=Hour(1)),
)
result = final_state(run!(model)).temperature_seen
@test result == 25.0
result
25.0

A scientific model should also declare the temperature's units and meaning with variable_contracts_, as in Implement a basic model. Use Port an existing model for an equation that combines environmental temperature with object state.

Give a model a default cadence​#

The next teaching model adds a fixed increment whenever it runs. By default, it runs every 24 simulation steps. Other models can keep reading its latest result until it runs again:

julia
PlantSimEngine.inputs_(::ToyDailyDevelopmentModel) = NamedTuple()
PlantSimEngine.outputs_(model::ToyDailyDevelopmentModel) = (
    daily_growth=zero(model.increment),
)
PlantSimEngine.timespec(::Type{<:ToyDailyDevelopmentModel}) =
    ClockSpec(24.0, 1.0)
PlantSimEngine.output_policy(::Type{<:ToyDailyDevelopmentModel}) = (
    daily_growth=HoldLast(),
)

function PlantSimEngine.run!(
    model::ToyDailyDevelopmentModel,
    status,
    environment,
    constants,
    context,
)
    status.daily_growth += model.increment
    return nothing
end

timespec sets the default update frequency. output_policy describes how other models read the result between updates. Here HoldLast tells them to use the latest available value; it does not run this equation again.

ClockSpec(24.0, 1.0) means every 24 base steps, starting at step 1. It corresponds to a day only when the base step is an hour. In a scenario, every=Day(1) expresses the intended duration directly:

julia
daily = ToyDailyDevelopmentModel(2.0)
daily_model = CompositeModel(
    Object(:plant; scale=:Plant);
    applications=(
        ModelSpec(
            daily; name=:daily_development, on=One(scale=:Plant),
            every=Day(1),
        ),
    ),
    environment=[(duration=Hour(1),) for _ in 1:25],
)

daily_simulation = run!(daily_model; steps=25, outputs=:all)
growth = final_state(daily_simulation).daily_growth
@test growth == 4.0
growth
4.0

The model runs at steps 1 and 25, adding 2 each time. When connecting another model, you can choose to average or add results over an interval instead of keeping the last value. The choice depends on what the variable represents; see Give Models Different Cadences.

Give a model a default update frequency only when its equations require one. Choose weather data, timing for a particular study, and results to save in the simulation setup.