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.
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.mdUse 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.
Each level answers a different question:
| Check | Question it answers |
|---|---|
| Direct equation test | Does the implementation reproduce a known calculation? |
| Declaration check | Are required inputs, outputs, and physical meanings explicit? |
| Small simulation | Can the model obtain its inputs and run on the intended object? |
| Coupled scenario | Does it interact correctly with the other models used in this study? |
Start with the biomass example from Implement a basic model:
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.0f0These 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.
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.
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.