PlantSimEngine.jlPlantSimEngine.jl

Model repository layout and tests​#

Organize a model package so a colleague can find the equation, its alternative hypotheses, and the evidence used to test it. A small package can start with one model file and one test file, then grow as needed.

Group alternatives by scientific process​#

text
src/
├── MyModels.jl
└── processes/
    ├── photosynthesis/
    │   ├── process.jl
    │   ├── Farquhar.jl
    │   └── EmpiricalAssimilation.jl
    └── growth/
        ├── process.jl
        └── CarbonLimitedGrowth.jl

test/
├── runtests.jl
├── models/
├── coupling/
└── scenarios/

docs/src/models/
├── photosynthesis.md
└── growth.md

Use process.jl only when this package declares a new process. If another package already declares it, import that package's abstract process type. Keep one readable file per hypothesis. List the files to include and the names users can import in MyModels.jl.

A process page should compare the alternatives: equations, assumptions, required data, units, parameters, references, and the conditions under which each model has been tested. Label teaching models and unfinished experiments clearly.

Test from the equation outward​#

Each level answers a different question:

CheckQuestion it answers
Direct equation testDoes the implementation reproduce a known calculation?
Declaration checkAre required inputs, outputs, and physical meanings explicit?
Small simulationCan the model obtain its inputs and run on the intended object?
Coupled scenarioDoes it interact correctly with the other models used in this study?

Start with the biomass example from Implement a basic model:

julia
using Dates, Test, PlantSimEngine
include(joinpath(
    pkgdir(PlantSimEngine), "skills", "plantsimengine",
    "assets", "minimal-model.jl",
))
using .MinimalModelExample

model = RadiationUseEfficiency(1.5f0)
status = Status(intercepted_par=10.0f0, biomass_increment=0.0f0)
PlantSimEngine.run!(model, status, NamedTuple(), nothing, nothing)
@test status.biomass_increment == 15.0f0
@test status.biomass_increment isa Float32
@test Authoring.validate_model(model; strict=true).valid

scenario = CompositeModel(
    model; status=(intercepted_par=10.0f0,), timestep=Day(1),
)
@test final_state(run!(scenario)).biomass_increment == 15.0f0
Test Passed

These small checks test the equation first, then test how the simulation supplies its inputs and runs it. For a scientific model, add edge cases and a trusted reference calculation or dataset. Agreement with the reference needs a numerical tolerance appropriate to the calculation.

Add checks for the features you use​#

If a model reads values from other objects, check that it uses the intended objects. If coupled models run at different frequencies, check what happens when each one updates. For a growing plant, check results before and after organs are added or removed. For a model that tries several solutions, test both rejected trials and the results it finally accepts.

Use Diagnostics to inspect the relevant connections and schedules, then assert the scientific relationship you intend to preserve. For example, check that a plant total uses its own leaves and excludes a neighbouring plant's leaves.

Exercise the numerical types your model supports. Measure performance where large organ counts or repeated calls matter; keep such benchmarks separate from equation checks. See Numerical Reliability and the benchmarking guidance.

Review a new hypothesis​#

Before sharing a model, check that:

  • it belongs to the intended process;

  • its inputs, outputs, units, and timing are documented, including whether each value is per organ, per plant, or per unit area;

  • its equation and fixed parameters are easy to find;

  • each object's changing state stays separate;

  • direct tests and the relevant coupled tests pass;

  • scientific validation and remaining uncertainty are stated.

When a simulation setup becomes long, group the ModelSpec definitions into functions for leaf, plant, and soil processes, for example. Have those functions return the ModelSpec values so readers can still see which models are used and how they are connected.