Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
fail-fast: false
matrix:
version:
- "1.9"
- "1.10"
- "1"
os:
- ubuntu-latest
Expand Down
16 changes: 8 additions & 8 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ AbstractTrees = "0.4"
CSV = "0.10"
DataAPI = "1.15"
DataFrames = "1"
Dates = "1.9"
Dates = "1.10"
FLoops = "0.2"
Markdown = "1.9"
MultiScaleTreeGraph = "0.13, 0.14"
PlantMeteo = "0.7"
Markdown = "1.10"
MultiScaleTreeGraph = "0.15"
PlantMeteo = "0.8"
SHA = "0.7.0"
Statistics = "1.9"
Statistics = "1.10"
Tables = "1"
Term = "1, 2"
Test = "1.9"
julia = "1.9"
Term = "2"
Test = "1.10"
julia = "1.10"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ lines(model[:TT_cu], model[:LAI], color=:green, axis=(ylabel="LAI (m² m⁻²)",
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:

```julia
# ] add PlantSimEngine, DataFrames, CSV
using PlantSimEngine, PlantMeteo, DataFrames, CSV
# ] add PlantSimEngine, PlantMeteo, Dates
using PlantSimEngine, PlantMeteo, Dates

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

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

# Define the list of models for coupling:
model = ModelMapping(
Expand Down
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ makedocs(;
canonical="https://VirtualPlantLab.github.io/PlantSimEngine.jl",
edit_link="main",
assets=String[],
size_threshold=500000
size_threshold=700000
), pages=[
"Home" => "index.md",
"Introduction" => [
Expand Down
8 changes: 4 additions & 4 deletions docs/src/FAQ/translate_a_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
```@setup mymodel
using PlantSimEngine
using CairoMakie
using CSV, DataFrames
# Import the example models defined in the `Examples` sub-module:
using PlantSimEngine.Examples
using PlantMeteo, Dates

function lai_toymodel(TT_cu; max_lai=8.0, dd_incslope=500, inc_slope=70, dd_decslope=1000, dec_slope=20)
LAI = max_lai * (1 / (1 + exp((dd_incslope - TT_cu) / inc_slope)) - 1 / (1 + exp((dd_decslope - TT_cu) / dec_slope)))
Expand All @@ -15,12 +15,12 @@ function lai_toymodel(TT_cu; max_lai=8.0, dd_incslope=500, inc_slope=70, dd_decs
return LAI
end

meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day)
```

If you already have a model, you can easily use `PlantSimEngine` to couple it with other models with minor adjustments.

## Toy LAI Model
## Toy LAI Model

### Model description

Expand Down Expand Up @@ -128,7 +128,7 @@ Now that we have everything set up, we can run a simulation. The first step here

```julia
# Import the packages we need:
using PlantMeteo, Dates, DataFrames
using PlantMeteo, Dates

# Define the period of the simulation:
period = [Dates.Date("2021-01-01"), Dates.Date("2021-12-31")]
Expand Down
13 changes: 8 additions & 5 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ CurrentModule = PlantSimEngine
```

```@setup readme
using PlantSimEngine, PlantMeteo, DataFrames, CSV
using PlantSimEngine, PlantMeteo, Dates

# Import the examples defined in the `Examples` sub-module:
using PlantSimEngine.Examples

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

# Define the model mapping:
model = ModelMapping(
Expand Down Expand Up @@ -125,6 +125,8 @@ model = ModelMapping(
)

out = run!(model) # run the model and extract its outputs

out[1:3,:]
```

> **Note**
Expand All @@ -144,14 +146,14 @@ lines(out[:TT_cu], out[:LAI], color=:green, axis=(ylabel="LAI (m² m⁻²)", xla
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:

```@example readme
# ] add PlantSimEngine, DataFrames, CSV
using PlantSimEngine, PlantMeteo, DataFrames, CSV
# ] add PlantSimEngine, PlantMeteo
using PlantSimEngine, PlantMeteo, Dates

# Import the examples defined in the `Examples` sub-module
using PlantSimEngine.Examples

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

# Define the mapping for coupled models:
model2 = ModelMapping(
Expand All @@ -162,6 +164,7 @@ model2 = ModelMapping(

# Run the simulation:
out2 = run!(model2, meteo_day)
out2[1:3,:]
```

The `ModelMapping` couples the models by automatically computing the dependency graph of the models. The resulting dependency graph is:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/model_coupling/model_coupling_user.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Model coupling for users

```@setup usepkg
using PlantSimEngine, PlantMeteo
using PlantSimEngine, PlantMeteo, Dates
# Import the example models defined in the `Examples` sub-module:
using PlantSimEngine.Examples

Expand Down
3 changes: 2 additions & 1 deletion docs/src/multirate/multirate_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ for row in eachrow(week_df)
end

meteo_hourly = Weather(hourly_rows)
meteo_hourly[1:3] # show the first 3 rows of the hourly weather table
```

## 2. Define simple tutorial models
Expand Down Expand Up @@ -300,7 +301,7 @@ Of course the outputs of the models are still available in the `status_outputs`

```@example multirate_tutorial
outs = convert_outputs(out_status, DataFrame)
outs["Plant"]
outs["Plant"][1:3,:]
```

## 5. Deeper notes
Expand Down
8 changes: 3 additions & 5 deletions docs/src/multiscale/multiscale_considerations.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,17 @@ The [`run!`](@ref) function differs slightly from its single-scale version. The
run!(mtg, mapping::ModelMapping, meteo, constants, extra; nsteps, tracked_outputs)
```

Instead of a just the [`ModelMapping`](@ref), it also takes an MTG as the first argument. The optional `meteo` and `constants` argument are identical to the single-scale version. The `extra` argument is now reserved and should not be used. A new `nsteps` keyword argument is available to restrict the simulation to a specified number of steps.
Instead of a just the [`ModelMapping`](@ref), it also takes an MTG as the first argument. The optional `meteo` and `constants` argument are identical to the single-scale version. The `extra` argument is now reserved and should not be used. A new `nsteps` keyword argument is available to restrict the simulation to a specified number of steps.

## Multi-scale output data structure

The output structure, like the mapping, is a Julia `Dict` structure indexed by the scale name. Values are a per-scale `Vector{NamedTuple}` which lists the requested variables for every node at that scale, for every timestep in the simulation. Timestep and Multiscale Tree Graph nodes are also added to the output data, as a `:timestep`and a `:node` entry.

The output structure, like the mapping, is a Julia `Dict` structure indexed by the scale name. Values are a per-scale `Vector{NamedTuple}` which lists the requested variables for every node at that scale, for every timestep in the simulation. Timestep and Multiscale Tree Graph nodes are also added to the output data, as a `:timestep`and a `:node` entry.

This dictionary structure makes the outputs as-is a little more verbose to inspect than in single-scale, but the general usage is similar, and it is both compact, and fast to convert to a `Dict{String, DataFrame}` which can make queries easier.
This dictionary structure makes the outputs as-is a little more verbose to inspect than in single-scale, but the general usage is similar, and it is both compact, and fast to convert to a `Dict{String, DataFrame}` which can make queries easier.

!!! note
Some of the mapped variables -those that map from scalar to vector- will not be added to the outputs to save some memory and space since they are redundant.


To illustrate, here's an example output from part 3 of the Toy plant tutorial, zeroing in on a variable at the "Root" scale: [Fixing bugs in the plant simulation](@ref):

```julia
Expand Down
4 changes: 2 additions & 2 deletions docs/src/multiscale/multiscale_coupling.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mapping = ModelMapping(
The model's constructor provides convenient default names for the scale corresponding to the reproductive organs. A user may override that if their naming schemes or MTG attributes differ.

```julia
function ReproductiveOrganEmission(mtg::MultiScaleTreeGraph.Node; phytomer_symbol="Phytomer", male_symbol="Male", female_symbol="Female")
function ReproductiveOrganEmission(mtg::MultiScaleTreeGraph.Node; phytomer_symbol=:Phytomer, male_symbol=:Male, female_symbol=:Female)
...
end
```
Expand Down Expand Up @@ -168,4 +168,4 @@ function PlantSimEngine.run!(m::ReproductiveOrganEmission, models, status, meteo
...
end
end
```
```
29 changes: 14 additions & 15 deletions docs/src/multiscale/multiscale_example_1.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ We'll need to make use of a few packages, as usual, after adding them to our Jul
```@example usepkg
using PlantSimEngine
using PlantSimEngine.Examples # to import the ToyDegreeDaysCumulModel model
using PlantMeteo
using PlantMeteo, Dates
using MultiScaleTreeGraph # multi-scale
using CSV, DataFrames # used to import the example weather data
```

## A basic growing plant
Expand Down Expand Up @@ -119,7 +118,7 @@ Let's first define a helper function that iterates across a Multiscale Tree Grap
```@example usepkg
function get_n_leaves(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol="Leaf"))
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol=:Leaf))
return nleaves
end
```
Expand Down Expand Up @@ -246,27 +245,27 @@ mapping = ModelMapping(
!!! note
This excerpt (and the complete script file) showcase the final properly initialized mapping, but when developing, you are encouraged to make liberal use of the helper function [`to_initialize`](@ref) and check the PlantSimEngine user errors.

### Running a simulation
### Running a simulation

We only need an MTG, and some weather data, and then we'll be set. Let's create a simple MTG :

```@example usepkg
mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0))
plant = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG("+", "Plant", 1, 1))
internode1 = MultiScaleTreeGraph.Node(plant, MultiScaleTreeGraph.NodeMTG("/", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0))
plant = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG("+", "Plant", 1, 1))

internode1 = MultiScaleTreeGraph.Node(plant, MultiScaleTreeGraph.NodeMTG("/", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))

internode2 = MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("<", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
internode2 = MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("<", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
```

Import some weather data :
Import some weather data:

```@example usepkg
meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day)
nothing # hide
```

Expand Down
11 changes: 5 additions & 6 deletions docs/src/multiscale/multiscale_example_2.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ Once again, with a properly set-up Julia environment:
```@example usepkg
using PlantSimEngine
using PlantSimEngine.Examples
using PlantMeteo
using PlantMeteo, Dates, Dates
using MultiScaleTreeGraph
using CSV, DataFrames

PlantSimEngine.@process "leaf_carbon_capture" verbose = false

Expand All @@ -38,7 +37,7 @@ end

function get_n_leaves(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol="Leaf"))
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol=:Leaf))
return nleaves
end
```
Expand Down Expand Up @@ -78,12 +77,12 @@ It also makes use of a couple of helper functions to find the end root and compu
```@example usepkg
function get_root_end_node(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
return MultiScaleTreeGraph.traverse(root, x->x, symbol="Root", filter_fun = MultiScaleTreeGraph.isleaf)
return MultiScaleTreeGraph.traverse(root, x->x, symbol=:Root, filter_fun = MultiScaleTreeGraph.isleaf)
end

function get_roots_count(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
return length(MultiScaleTreeGraph.traverse(root, x->x, symbol="Root"))
return length(MultiScaleTreeGraph.traverse(root, x->x, symbol=:Root))
end

PlantSimEngine.@process "root_growth" verbose = false
Expand Down Expand Up @@ -254,7 +253,7 @@ mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0))
MultiScaleTreeGraph.NodeMTG("+", "Root", 1, 3),
)

meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day)

outs = run!(mtg, mapping, meteo_day)
mtg
Expand Down
36 changes: 18 additions & 18 deletions docs/src/multiscale/multiscale_example_3.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
```@setup usepkg
using PlantSimEngine
using PlantSimEngine.Examples
using PlantMeteo, CSV, DataFrames
using PlantMeteo, Dates
using MultiScaleTreeGraph
function get_root_end_node(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
return MultiScaleTreeGraph.traverse(root, x->x, symbol="Root", filter_fun = MultiScaleTreeGraph.isleaf)
return MultiScaleTreeGraph.traverse(root, x->x, symbol=:Root, filter_fun = MultiScaleTreeGraph.isleaf)
end

function get_roots_count(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
return length(MultiScaleTreeGraph.traverse(root, x->x, symbol="Root"))
return length(MultiScaleTreeGraph.traverse(root, x->x, symbol=:Root))
end

function get_n_leaves(node::MultiScaleTreeGraph.Node)
root = MultiScaleTreeGraph.get_root(node)
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol="Leaf"))
nleaves = length(MultiScaleTreeGraph.traverse(root, x->1, symbol=:Leaf))
return nleaves
end

Expand Down Expand Up @@ -210,24 +210,24 @@ mapping = ModelMapping(
"Leaf" => ( ToyLeafCarbonCaptureModel(),),
)

mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0))
mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0))

plant = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG("+", "Plant", 1, 1))
internode1 = MultiScaleTreeGraph.Node(plant, MultiScaleTreeGraph.NodeMTG("/", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
plant = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG("+", "Plant", 1, 1))

internode1 = MultiScaleTreeGraph.Node(plant, MultiScaleTreeGraph.NodeMTG("/", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))

internode2 = MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("<", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
internode2 = MultiScaleTreeGraph.Node(internode1, MultiScaleTreeGraph.NodeMTG("<", "Internode", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))
MultiScaleTreeGraph.Node(internode2, MultiScaleTreeGraph.NodeMTG("+", "Leaf", 1, 2))

plant_root_start = MultiScaleTreeGraph.Node(
plant,
MultiScaleTreeGraph.NodeMTG("+", "Root", 1, 3),
)
plant_root_start = MultiScaleTreeGraph.Node(
plant,
MultiScaleTreeGraph.NodeMTG("+", "Root", 1, 3),
)

meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18)
meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day)

```

Expand Down
Loading
Loading