Skip to content

Commit 8468b21

Browse files
authored
Merge branch 'main' into dependabot/github_actions/julia-actions/cache-3
2 parents 364ba59 + ebb15ab commit 8468b21

117 files changed

Lines changed: 11063 additions & 2313 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/Benchmarks.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
branches: [main]
55
workflow_dispatch:
66
permissions:
7-
pull-requests: write
7+
pull-requests: write
88
jobs:
99
bench:
1010
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
@@ -15,7 +15,6 @@ jobs:
1515
matrix:
1616
version:
1717
- "1"
18-
- "1.10"
1918
os:
2019
- ubuntu-latest
2120
arch:
@@ -24,4 +23,4 @@ jobs:
2423
- uses: MilesCranmer/AirspeedVelocity.jl@action-v1
2524
with:
2625
julia-version: ${{ matrix.version }}
27-
bench-on: ${{ github.event.pull_request.head.sha }}
26+
bench-on: ${{ github.event.pull_request.head.sha }}

.github/workflows/CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
fail-fast: false
2424
matrix:
2525
version:
26-
- "1.9"
26+
- "1.10"
2727
- "1"
2828
os:
2929
- ubuntu-latest

.github/workflows/Integration.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ jobs:
2323
fail-fast: false
2424
matrix:
2525
version:
26-
- "1.9"
2726
- "1"
2827
os:
2928
- ubuntu-latest
30-
- macOS-latest
31-
- windows-latest
3229
arch:
3330
- x64
3431
package:
35-
- {user: PalmStudio, repo: XPalm.jl, branch: main, default: main}
36-
- {user: VEZY, repo: PlantBioPhysics.jl, branch: master, default: master}
32+
- { user: PalmStudio, repo: XPalm.jl, branch: main, default: main }
33+
- {
34+
user: VEZY,
35+
repo: PlantBioPhysics.jl,
36+
branch: master,
37+
default: master,
38+
}
3739
steps:
3840
- uses: actions/checkout@v6
3941
- uses: julia-actions/setup-julia@v2
@@ -65,11 +67,11 @@ jobs:
6567
exit(0) # Exit immediately, as a success
6668
end
6769
- name: Only succeed if the checks pass on the dependencies' main/master branches
68-
if: ${{ matrix.package.branch != matrix.package.default }}
70+
if: ${{ matrix.package.branch != matrix.package.default }}
6971
run: |
7072
echo "This action requires the downstream dependencies' branches to be set to their main/master to succeed. Please switch them back and ensure tests pass before merging"
7173
exit 1
7274
- uses: julia-actions/julia-processcoverage@v1
7375
- uses: codecov/codecov-action@v5
7476
with:
75-
files: lcov.info
77+
files: lcov.info

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
docs/Manifest.toml
77
test/Manifest.toml
88
docs/build/
9-
benchmark/Manifest.toml
9+
benchmark/Manifest.toml

AGENTS.md

Lines changed: 340 additions & 0 deletions
Large diffs are not rendered by default.

Project.toml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name = "PlantSimEngine"
22
uuid = "9a576370-710b-4269-adf9-4f603a9c6423"
3-
authors = ["Rémi Vezy <VEZY@users.noreply.github.com> and contributors"]
43
version = "0.13.2"
4+
authors = ["Rémi Vezy <VEZY@users.noreply.github.com> and contributors"]
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
88
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
99
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
1010
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
11+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
1112
FLoops = "cc61a311-1640-44b5-9fba-1b764f453329"
1213
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1314
MultiScaleTreeGraph = "dd4a991b-8a45-4075-bede-262ee62d5583"
@@ -22,16 +23,17 @@ AbstractTrees = "0.4"
2223
CSV = "0.10"
2324
DataAPI = "1.15"
2425
DataFrames = "1"
26+
Dates = "1.10"
2527
FLoops = "0.2"
26-
Markdown = "1.9"
27-
MultiScaleTreeGraph = "0.13, 0.14"
28-
PlantMeteo = "0.7"
28+
Markdown = "1.10"
29+
MultiScaleTreeGraph = "0.15.1"
30+
PlantMeteo = "0.8.2"
2931
SHA = "0.7.0"
30-
Statistics = "1.9"
32+
Statistics = "1.10"
3133
Tables = "1"
32-
Term = "1, 2"
33-
Test = "1.9"
34-
julia = "1.9"
34+
Term = "2"
35+
Test = "1.10"
36+
julia = "1.10"
3537

3638
[extras]
3739
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
- [Unique Features](#unique-features)
1515
- [Automatic Model Coupling](#automatic-model-coupling)
1616
- [Flexibility with Precision Control](#flexibility-with-precision-control)
17+
- [Multi-rate Execution](#multi-rate-execution)
1718
- [Batteries included](#batteries-included)
1819
- [Ask Questions](#ask-questions)
1920
- [Installation](#installation)
2021
- [Example usage](#example-usage)
2122
- [Simple example](#simple-example)
2223
- [Model coupling](#model-coupling)
2324
- [Multiscale modelling](#multiscale-modelling)
25+
- [Multi-rate modelling](#multi-rate-modelling)
2426
- [Projects that use PlantSimEngine](#projects-that-use-plantsimengine)
2527
- [Performance](#performance)
2628
- [Make it yours](#make-it-yours)
@@ -48,11 +50,18 @@
4850

4951
**Effortless Model Switching:** Researchers can switch between different component models using a simple syntax without rewriting the underlying model code. This enables rapid comparison between different hypotheses and model versions, accelerating the scientific discovery process.
5052

53+
### Multi-rate Execution
54+
55+
**Mix model cadences in one simulation:** PlantSimEngine can run models at different timesteps within the same MTG simulation. This makes it possible to combine, for example, hourly leaf processes with daily plant balances and weekly reporting models without writing custom scheduling glue.
56+
57+
**Explicit bindings between rates:** `TimeStepModel`, `InputBindings`, `MeteoBindings`, `ScopeModel`, and `OutputRequest` let you declare how model inputs, meteorology, and exported outputs should behave when rates differ.
58+
5159
## Batteries included
5260

5361
- **Automated Management**: Seamlessly handle inputs, outputs, time-steps, objects, and dependency resolution.
5462
- **Iterative Development**: Fast and interactive prototyping of models with built-in constraints to avoid errors and sensible defaults to streamline the model writing process.
5563
- **Control Your Degrees of Freedom**: Fix variables to constant values or force to observations, use simpler models for specific processes to reduce complexity.
64+
- **Multi-Rate Scheduling**: Combine hourly, daily, and coarser models in the same simulation, with explicit policies for input aggregation and meteorological sampling.
5665
- **High-Speed Computations**: Achieve impressive performance with benchmarks showing operations in the 100th of nanoseconds range for complex models (see this [benchmark script](https://github.com/VirtualPlantLab/PlantSimEngine.jl/blob/main/examples/benchmark.jl)).
5766
- **Parallelize and Distribute Computing**: Out-of-the-box support for sequential, multi-threaded, or distributed computations over objects, time-steps, and independent processes, thanks to [Floops.jl](https://juliafolds.github.io/FLoops.jl/stable/).
5867
- **Scale Effortlessly**: Methods for computing over objects, time-steps, and [Multi-Scale Tree Graphs](https://github.com/VEZY/MultiScaleTreeGraph.jl).
@@ -92,7 +101,7 @@ using PlantSimEngine
92101
using PlantSimEngine.Examples
93102

94103
# Define the model:
95-
model = ModelList(
104+
model = ModelMapping(
96105
ToyLAIModel(),
97106
status=(TT_cu=1.0:2000.0,), # Pass the cumulated degree-days as input to the model
98107
)
@@ -136,27 +145,27 @@ lines(model[:TT_cu], model[:LAI], color=:green, axis=(ylabel="LAI (m² m⁻²)",
136145

137146
### Model coupling
138147

139-
Model coupling is done automatically by the package, and is based on the dependency graph between the models. To couple models, we just have to add them to the `ModelList`. For example, let's couple the `ToyLAIModel` with a model for light interception based on Beer's law:
148+
Model coupling is done automatically by the package, and is based on the dependency graph between the models. To couple models, we just have to add them to the `ModelMapping`. For example, let's couple the `ToyLAIModel` with a model for light interception based on Beer's law:
140149

141150
```julia
142-
# ] add PlantSimEngine, DataFrames, CSV
143-
using PlantSimEngine, PlantMeteo, DataFrames, CSV
151+
# ] add PlantSimEngine, PlantMeteo, Dates
152+
using PlantSimEngine, PlantMeteo, Dates
144153

145154
# Include the model definition from the examples folder:
146155
using PlantSimEngine.Examples
147156

148157
# Import the example meteorological data:
149-
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
158+
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day)
150159

151160
# Define the list of models for coupling:
152-
model = ModelList(
161+
model = ModelMapping(
153162
ToyLAIModel(),
154163
Beer(0.6),
155164
status=(TT_cu=cumsum(meteo_day[:, :TT]),), # Pass the cumulated degree-days as input to `ToyLAIModel`, this could also be done using another model
156165
)
157166
```
158167

159-
The `ModelList` couples the models by automatically computing the dependency graph of the models. The resulting dependency graph is:
168+
The `ModelMapping` couples the models by automatically computing the dependency graph of the models. The resulting dependency graph is:
160169

161170
```
162171
╭──── Dependency graph ──────────────────────────────────────────╮
@@ -223,7 +232,7 @@ fig
223232
The package is designed to be easily scalable, and can be used to simulate models at different scales. For example, you can simulate a model at the leaf scale, and then couple it with models at any other scale, *e.g.* internode, plant, soil, scene scales. Here's an example of a simple model that simulates plant growth using sub-models operating at different scales:
224233

225234
```julia
226-
mapping = Dict(
235+
mapping = ModelMapping(
227236
"Scene" => ToyDegreeDaysCumulModel(),
228237
"Plant" => (
229238
MultiScaleModel(
@@ -295,7 +304,7 @@ meteo = Weather(
295304
And run the simulation:
296305

297306
```julia
298-
out_vars = Dict(
307+
out_vars = ModelMapping(
299308
"Scene" => (:TT_cu,),
300309
"Plant" => (:carbon_allocation, :carbon_assimilation, :soil_water_content, :aPPFD, :TT_cu, :LAI),
301310
"Leaf" => (:carbon_demand, :carbon_allocation),
@@ -337,6 +346,17 @@ An example output of a multiscale simulation is shown in the documentation of Pl
337346

338347
![Plant growth simulation](docs/src/www/image.png)
339348

349+
### Multi-rate modelling
350+
351+
PlantSimEngine also supports multi-rate MTG simulations, where different models run at different cadences inside the same execution. A typical use case is to run leaf-scale processes hourly, aggregate them into daily plant-scale balances, and then export weekly summary series from the same simulation.
352+
353+
The dedicated documentation now has three pages: a short introduction to the
354+
core ideas, a fuller step-by-step tutorial, and an advanced configuration page:
355+
356+
- [Introduction to multi-rate execution](https://VirtualPlantLab.github.io/PlantSimEngine.jl/stable/multirate/introduction/)
357+
- [Step-by-step hourly, daily, weekly simulation](https://VirtualPlantLab.github.io/PlantSimEngine.jl/stable/multirate/multirate_tutorial/)
358+
- [Advanced multi-rate configuration](https://VirtualPlantLab.github.io/PlantSimEngine.jl/stable/multirate/advanced_configuration/)
359+
340360
## Projects that use PlantSimEngine
341361

342362
Take a look at these projects that use PlantSimEngine:

benchmark/Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1010
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1111
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1212
XPalm = "6b523e1e-d512-416c-8e51-a8fbef0064e7"
13+
14+
[sources]
15+
PlantSimEngine = {path = ".."}
16+
XPalm = {rev = "main", url = "https://github.com/PalmStudio/XPalm.jl"}
17+
PlantBiophysics = {rev = "master", url = "https://github.com/VEZY/PlantBiophysics.jl"}

benchmark/benchmarks.jl

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
using Pkg
2-
Pkg.activate(dirname(@__FILE__))
3-
#Pkg.develop(PackageSpec(path=dirname(dirname(@__DIR__))))
4-
Pkg.add(url="https://github.com/VEZY/PlantBiophysics.jl", rev="master")
5-
Pkg.add(url="https://github.com/PalmStudio/XPalm.jl", rev="main")
6-
Pkg.resolve()
7-
Pkg.instantiate()
8-
91
using PlantSimEngine
102
using PlantSimEngine.Examples
113
using DataFrames, CSV
@@ -31,6 +23,12 @@ SUITE[suite_name] = BenchmarkGroup(["PSE", "PBP", "XPalm"])
3123
include("test-PSE-benchmark.jl")
3224
SUITE[suite_name]["PSE"] = @benchmarkable do_benchmark_on_heavier_mtg()
3325

26+
if isdefined(PlantSimEngine, :ModelSpec) # Only in new versions
27+
include("test-multirate-buffer-benchmark.jl")
28+
mtg_mr, mapping_mr, meteo_mr, reqs_mr, tracked_mr, nsteps_mr = setup_multirate_buffer_benchmark()
29+
SUITE[suite_name]["PSE_multirate_status_tracked_run"] = @benchmarkable benchmark_multirate_status_tracked_run($mtg_mr, $mapping_mr, $meteo_mr, $tracked_mr, $nsteps_mr)
30+
SUITE[suite_name]["PSE_multirate_output_request_run"] = @benchmarkable benchmark_multirate_output_request_run($mtg_mr, $mapping_mr, $meteo_mr, $reqs_mr, $tracked_mr, $nsteps_mr)
31+
end
3432
# "PBP benchmark"
3533
include("test-plantbiophysics.jl")
3634
SUITE[suite_name]["PBP"] = @benchmarkable benchmark_plantbiophysics()
@@ -51,4 +49,4 @@ SUITE[suite_name]["XPalm_convert_outputs"] = @benchmarkable xpalm_default_param_
5149

5250
#tune!(SUITE)
5351
#results = run(SUITE, verbose=true)
54-
#BenchmarkTools.save(dirname(@__FILE__) * "/output.json", median(results))
52+
#BenchmarkTools.save(dirname(@__FILE__) * "/output.json", median(results))

benchmark/test-PSE-benchmark.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ function PlantSimEngine.run!(m::ToyInternodeCrazyEmergence, models, status, mete
2727
#end
2828

2929
if length(MultiScaleTreeGraph.children(status.node)) == 1 && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence
30-
30+
3131
status_new_internode = add_organ!(status.node, sim_object, "<", "Internode", 2, index=1)
3232
add_organ!(status_new_internode.node, sim_object, "+", "Leaf", 2, index=1)
3333
status_new_internode.TT_cu_emergence = status.TT_cu
34-
elseif (length(MultiScaleTreeGraph.children(status.node)) >= 2 && length(MultiScaleTreeGraph.children(status.node)) < 7) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence
34+
elseif (length(MultiScaleTreeGraph.children(status.node)) >= 2 && length(MultiScaleTreeGraph.children(status.node)) < 7) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence
3535
status_new_internode = add_organ!(status.node, sim_object, "<", "Internode", 2, index=1)
3636
add_organ!(status.node, sim_object, "+", "Leaf", 2, index=4)
3737
add_organ!(status.node, sim_object, "+", "Leaf", 2, index=5)
3838
status_new_internode.TT_cu_emergence = status.TT_cu
39-
elseif (length(MultiScaleTreeGraph.children(status.node)) >= 7 && length(MultiScaleTreeGraph.children(status.node)) < 30) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence
39+
elseif (length(MultiScaleTreeGraph.children(status.node)) >= 7 && length(MultiScaleTreeGraph.children(status.node)) < 30) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence
4040
add_organ!(status.node, sim_object, "+", "Leaf", 2, index=6)
4141
add_organ!(status.node, sim_object, "+", "Leaf", 2, index=7)
4242
add_organ!(status.node, sim_object, "+", "Leaf", 2, index=8)
@@ -53,13 +53,13 @@ end
5353
# Wrapped this into a function so that it doesn't plague the benchmark with variables on a global scope
5454
#@check_allocs
5555
function do_benchmark_on_heavier_mtg()
56-
mtg = import_mtg_example();
57-
56+
mtg = import_mtg_example()
57+
5858
# Example meteo, 365 timesteps :
5959
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Day)
60-
60+
6161
#similar to the mtg growth test but with a much lower emergence threshold
62-
mapping = Dict(
62+
mapping = ModelMapping(
6363
"Scene" => ToyDegreeDaysCumulModel(),
6464
"Plant" => (
6565
MultiScaleModel(
@@ -110,13 +110,13 @@ function do_benchmark_on_heavier_mtg()
110110
ToySoilWaterModel(),
111111
),
112112
)
113-
113+
114114
out_vars = Dict(
115115
"Leaf" => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation),
116116
"Internode" => (:carbon_allocation, :TT_cu_emergence),
117117
"Plant" => (:carbon_allocation,),
118118
"Soil" => (:soil_water_content,),
119119
)
120-
121-
out = run!(mtg, mapping, meteo_day, tracked_outputs=out_vars, executor=SequentialEx());
120+
121+
out = run!(mtg, mapping, meteo_day, tracked_outputs=out_vars, executor=SequentialEx())
122122
end

0 commit comments

Comments
 (0)