diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f5ad4416e..ac31d7e63 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: version: - - "1.9" + - "1.10" - "1" os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 6e65c5449..5f543dd73 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/README.md b/README.md index 933958683..8fb24438a 100644 --- a/README.md +++ b/README.md @@ -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( diff --git a/docs/make.jl b/docs/make.jl index c4dce4715..351b772b8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -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" => [ diff --git a/docs/src/FAQ/translate_a_model.md b/docs/src/FAQ/translate_a_model.md index 4cfedbd97..89b2b6a4c 100644 --- a/docs/src/FAQ/translate_a_model.md +++ b/docs/src/FAQ/translate_a_model.md @@ -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))) @@ -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 @@ -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")] diff --git a/docs/src/index.md b/docs/src/index.md index 5a41a9d4a..21dead04d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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( @@ -125,6 +125,8 @@ model = ModelMapping( ) out = run!(model) # run the model and extract its outputs + +out[1:3,:] ``` > **Note** @@ -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( @@ -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: diff --git a/docs/src/model_coupling/model_coupling_user.md b/docs/src/model_coupling/model_coupling_user.md index a88471d9d..b24be6931 100644 --- a/docs/src/model_coupling/model_coupling_user.md +++ b/docs/src/model_coupling/model_coupling_user.md @@ -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 diff --git a/docs/src/multirate/multirate_tutorial.md b/docs/src/multirate/multirate_tutorial.md index dff027d69..3794b32fe 100644 --- a/docs/src/multirate/multirate_tutorial.md +++ b/docs/src/multirate/multirate_tutorial.md @@ -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 @@ -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 diff --git a/docs/src/multiscale/multiscale_considerations.md b/docs/src/multiscale/multiscale_considerations.md index fd85f4145..db7e10f5e 100644 --- a/docs/src/multiscale/multiscale_considerations.md +++ b/docs/src/multiscale/multiscale_considerations.md @@ -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 diff --git a/docs/src/multiscale/multiscale_coupling.md b/docs/src/multiscale/multiscale_coupling.md index ce62af441..7d1d766b0 100644 --- a/docs/src/multiscale/multiscale_coupling.md +++ b/docs/src/multiscale/multiscale_coupling.md @@ -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 ``` @@ -168,4 +168,4 @@ function PlantSimEngine.run!(m::ReproductiveOrganEmission, models, status, meteo ... end end -``` \ No newline at end of file +``` diff --git a/docs/src/multiscale/multiscale_example_1.md b/docs/src/multiscale/multiscale_example_1.md index 9f6c769b4..2d7bcb3d6 100644 --- a/docs/src/multiscale/multiscale_example_1.md +++ b/docs/src/multiscale/multiscale_example_1.md @@ -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 @@ -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 ``` @@ -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 ``` diff --git a/docs/src/multiscale/multiscale_example_2.md b/docs/src/multiscale/multiscale_example_2.md index 7815e07a4..f9262e2e6 100644 --- a/docs/src/multiscale/multiscale_example_2.md +++ b/docs/src/multiscale/multiscale_example_2.md @@ -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 @@ -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 ``` @@ -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 @@ -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 diff --git a/docs/src/multiscale/multiscale_example_3.md b/docs/src/multiscale/multiscale_example_3.md index c0b4a4886..2a3588ae3 100644 --- a/docs/src/multiscale/multiscale_example_3.md +++ b/docs/src/multiscale/multiscale_example_3.md @@ -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 @@ -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) ``` diff --git a/docs/src/multiscale/multiscale_example_4.md b/docs/src/multiscale/multiscale_example_4.md index cdf8c5889..eafc0b556 100644 --- a/docs/src/multiscale/multiscale_example_4.md +++ b/docs/src/multiscale/multiscale_example_4.md @@ -116,7 +116,7 @@ function add_geometry!(mtg, refmesh_internode) internode_length = 1.0 traverse!(mtg) do node - if symbol(node) == "Internode" + if symbol(node) == :Internode # Set to scale, then translate by the total height mesh_transformation = Meshes.Scale(internode_width, internode_width, internode_length) → Meshes.Translate(0.0, 0.0, internode_height) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_internode, transformation=mesh_transformation) @@ -177,7 +177,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) i = 0 traverse!(mtg) do node - if symbol(node) == "Internode" + if symbol(node) == :Internode # Set to scale, then translate by the total height mesh_transformation = Meshes.Scale(internode_width, internode_width, internode_length) → Meshes.Translate(0.0, 0.0, internode_height) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_internode, transformation=mesh_transformation) @@ -186,7 +186,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) # Leaves are placed relatively to the parent internode, halfway along it for chnode in children(node) - if symbol(chnode) == "Leaf" + if symbol(chnode) == :Leaf mesh_transformation = Meshes.Scale(leaf_scale_width, leaf_scale_width, leaf_scale_height) → Meshes.Rotate(RotX(-MathConstants.pi / 6.0)) → Meshes.Translate(0.0, -internode_width, internode_height - internode_length / 2.0) → Meshes.Rotate(RotZ(leaf_rotation)) chnode.geometry = PlantGeom.Geometry(ref_mesh=refmesh_leaf, transformation=mesh_transformation) # Set the second leaf in a pair opposite to the first one => add a 180° rotation @@ -202,7 +202,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) leaf_rotation = MathConstants.pi end - elseif symbol(node) == "Root" + elseif symbol(node) == :Root mesh_transformation = Meshes.Scale(root_width, root_width, root_length) → Meshes.Translate(0.0, 0.0, root_depth) → Meshes.Rotate(RotZ(MathConstants.pi)) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_root, transformation=mesh_transformation) root_depth -= root_length diff --git a/docs/src/multiscale/single_to_multiscale.md b/docs/src/multiscale/single_to_multiscale.md index 7643d0556..bd98975ed 100644 --- a/docs/src/multiscale/single_to_multiscale.md +++ b/docs/src/multiscale/single_to_multiscale.md @@ -1,15 +1,15 @@ # Converting a single-scale simulation to multi-scale + ```@meta CurrentModule = PlantSimEngine ``` + ```@setup usepkg -using PlantMeteo +using PlantMeteo, Dates using PlantSimEngine using PlantSimEngine.Examples -using CSV -using DataFrames using MultiScaleTreeGraph -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) models_singlescale = ModelMapping( ToyLAIModel(), Beer(0.5), @@ -34,12 +34,11 @@ Depth = 3 For example, let's return to the [`ModelMapping`](@ref) coupling a light interception model, a Leaf Area Index model, and a carbon biomass increment model that was discussed in the [Model switching](@ref) subsection: ```@example usepkg -using PlantMeteo +using PlantMeteo, Dates using PlantSimEngine using PlantSimEngine.Examples -using CSV -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) models_singlescale = ModelMapping( ToyLAIModel(), @@ -49,6 +48,7 @@ models_singlescale = ModelMapping( ) outputs_singlescale = run!(models_singlescale, meteo_day) +outputs_singlescale[1:3,:] # show the first 3 rows of the output ``` Those models all operate on a simplified model of a single plant, without any organ-local information. We can therefore consider them to be working at the 'whole plant' scale. Their variables also operate at that "plant" scale, so there is no need to map any variable to other scales. diff --git a/docs/src/prerequisites/installing_plantsimengine.md b/docs/src/prerequisites/installing_plantsimengine.md index 33ef87535..d992f7a20 100644 --- a/docs/src/prerequisites/installing_plantsimengine.md +++ b/docs/src/prerequisites/installing_plantsimengine.md @@ -65,7 +65,7 @@ using PlantSimEngine.Examples Assuming you've setup you're environement, correctly added `PlantMeteo` and `PlantSimEngine` to that environment, and downloaded everything with `instantiate`, you'll be able to run a test example in your REPL by typing line-by-line: ```@example mypkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates using PlantSimEngine.Examples meteo = Atmosphere(T = 20.0, Wind = 1.0, Rh = 0.65, Ri_PAR_f = 500.0) leaf = ModelMapping(Beer(0.5), status = (LAI = 2.0,)) diff --git a/docs/src/step_by_step/advanced_coupling.md b/docs/src/step_by_step/advanced_coupling.md index 622e4d0e2..07b81c8bb 100644 --- a/docs/src/step_by_step/advanced_coupling.md +++ b/docs/src/step_by_step/advanced_coupling.md @@ -1,7 +1,7 @@ # Coupling more complex models ```@setup usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates # Import the example models defined in the `Examples` sub-module: using PlantSimEngine.Examples diff --git a/docs/src/step_by_step/detailed_first_example.md b/docs/src/step_by_step/detailed_first_example.md index 398ac61f7..068341b5e 100644 --- a/docs/src/step_by_step/detailed_first_example.md +++ b/docs/src/step_by_step/detailed_first_example.md @@ -7,7 +7,7 @@ A working trimmed-down script can be found further down in the [Example simulati If you simply wish to copy-paste examples and tinker with them, you can find a few examples on the [Quick examples](@ref) page. ```@setup usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates using PlantSimEngine.Examples meteo = Atmosphere(T = 20.0, Wind = 1.0, Rh = 0.65, Ri_PAR_f = 500.0) leaf = ModelMapping(Beer(0.5), status = (LAI = 2.0,)) @@ -189,7 +189,7 @@ The [`ModelMapping`](@ref) should already be initialized for the given process b For example we can simulate the `light_interception` of a leaf like so: ```@example usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates # Import the examples defined in the `Examples` sub-module using PlantSimEngine.Examples diff --git a/docs/src/step_by_step/model_switching.md b/docs/src/step_by_step/model_switching.md index b091351f7..c63a31c8f 100644 --- a/docs/src/step_by_step/model_switching.md +++ b/docs/src/step_by_step/model_switching.md @@ -1,11 +1,11 @@ # Model switching ```@setup usepkg -using PlantSimEngine, PlantMeteo, CSV, DataFrames +using PlantSimEngine, PlantMeteo, Dates # Import the examples defined in the `Examples` sub-module using PlantSimEngine.Examples -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) models = ModelMapping( ToyLAIModel(), @@ -55,7 +55,7 @@ nothing # hide We can the simulation by calling the [`run!`](@ref) function with meteorology data. Here we use an example data set: ```@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 ``` @@ -63,6 +63,7 @@ We can now run the simulation: ```@example usepkg output_initial = run!(models, meteo_day) +output_initial[1:3,:] # show the first 3 rows of the output ``` ## Switching one model in the simulation @@ -88,6 +89,7 @@ We can run a new simulation and see that the simulation's results are different ```@example usepkg output_updated = run!(models2, meteo_day) +output_updated[1:3,:] # show the first 3 rows of the output ``` And that's it! We can switch between models without changing the code, and without having to recompute the dependency graph manually. This is a very powerful feature of PlantSimEngine!💪 diff --git a/docs/src/step_by_step/quick_and_dirty_examples.md b/docs/src/step_by_step/quick_and_dirty_examples.md index e8a022e98..e871e8518 100644 --- a/docs/src/step_by_step/quick_and_dirty_examples.md +++ b/docs/src/step_by_step/quick_and_dirty_examples.md @@ -22,7 +22,7 @@ These examples assume you have a working Julia environment with PlantSimengine a ## Example with a single light interception model and a single weather timestep ```@example usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates using PlantSimEngine.Examples meteo = Atmosphere(T = 20.0, Wind = 1.0, Rh = 0.65, Ri_PAR_f = 500.0) leaf = ModelMapping(Beer(0.5), status = (LAI = 2.0,)) @@ -35,11 +35,10 @@ The weather data in this example contains data over 365 days, meaning the simula ```@example usepkg using PlantSimEngine -using PlantMeteo, CSV, DataFrames - +using PlantMeteo, Dates using PlantSimEngine.Examples -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) models = ModelMapping( ToyLAIModel(), @@ -48,6 +47,7 @@ models = ModelMapping( ) outputs_coupled = run!(models, meteo_day) +outputs_coupled[1:3,:] # show the first 3 rows of the output ``` ## Coupling the light interception and Leaf Area Index models with a biomass increment model @@ -55,11 +55,10 @@ outputs_coupled = run!(models, meteo_day) ```@example usepkg using PlantSimEngine -using PlantMeteo, CSV, DataFrames - +using PlantMeteo, Dates using PlantSimEngine.Examples -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) models = ModelMapping( ToyLAIModel(), @@ -69,6 +68,7 @@ models = ModelMapping( ) outputs_coupled = run!(models, meteo_day) +outputs_coupled[1:3,:] # show the first 3 rows of the output ``` ## Example using PlantBioPhysics diff --git a/docs/src/step_by_step/simple_model_coupling.md b/docs/src/step_by_step/simple_model_coupling.md index 70f1069c8..179b36d6f 100644 --- a/docs/src/step_by_step/simple_model_coupling.md +++ b/docs/src/step_by_step/simple_model_coupling.md @@ -3,9 +3,9 @@ ```@setup usepkg using PlantSimEngine using PlantSimEngine.Examples -using CSV -using DataFrames -meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18) +using PlantMeteo, Dates + +meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day) models = ModelMapping( ToyLAIModel(), Beer(0.5), @@ -89,13 +89,13 @@ The `Beer` model requires a specific meteorological parameter. Let's fix that by using PlantSimEngine # PlantMeteo and CSV packages are now used -using PlantMeteo, CSV +using PlantMeteo, Dates # Import the examples defined in the `Examples` sub-module: using PlantSimEngine.Examples # Import example weather 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) # A ModelMapping with two coupled models models = ModelMapping( @@ -106,7 +106,7 @@ models = ModelMapping( # Add the weather data to the run! call outputs_coupled = run!(models, meteo_day) - +outputs_coupled[1:3,:] ``` And there you have it. The light interception model made its computations using the Leaf Area Index computed by ToyLAIModel. diff --git a/docs/src/troubleshooting_and_testing/plantsimengine_and_julia_troubleshooting.md b/docs/src/troubleshooting_and_testing/plantsimengine_and_julia_troubleshooting.md index 131edefae..e1e06c479 100644 --- a/docs/src/troubleshooting_and_testing/plantsimengine_and_julia_troubleshooting.md +++ b/docs/src/troubleshooting_and_testing/plantsimengine_and_julia_troubleshooting.md @@ -20,7 +20,7 @@ Depth = 3 Some errors are very specific as to their cause, and the PlantSimEngine errors tend to be explicit about which parameter / variable / organ is causing the error, helping narrow down its origin. -Some generic-looking errors usually do contain some extra information to help focus the debugging hunt. For instance, a dispatch failure on run! caused by some issue with args/kwargs may highlight explicitely indicate which arguments are currently causing conflict. In VSCode, such arguments are highlighted in red (the first and last arguments in the example below) : +Some generic-looking errors usually do contain some extra information to help focus the debugging hunt. For instance, a dispatch failure on run! caused by some issue with args/kwargs may highlight explicitely indicate which arguments are currently causing conflict. In VSCode, such arguments are highlighted in red (the first and last arguments in the example below): ```julia a = 1 @@ -256,27 +256,27 @@ MultiScaleModel( ### Kwarg and arg parameter issues when calling run! -There are, unfortunately, multiple ways of passing in arguments to the run! functions that will confuse dynamic dispatch. Some of it is due to imperfections in type declarations on PlantSimEngine's end and may be improved upon in the future. +There are, unfortunately, multiple ways of passing in arguments to the run! functions that will confuse dynamic dispatch. Some of it is due to imperfections in type declarations on PlantSimEngine's end and may be improved upon in the future. -Here are a few examples when modifying the usual multiscale run! call in this working example : +Here are a few examples when modifying the usual multiscale run! call in this working example: ```julia - meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18) - mtg = Node(MultiScaleTreeGraph.NodeMTG("/", "Plant", 1, 1)) - var1 = 15.0 +meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day) +mtg = Node(MultiScaleTreeGraph.NodeMTG("/", "Plant", 1, 1)) +var1 = 15.0 - mapping = ModelMapping( - "Leaf" => ( - Process1Model(1.0), - Process2Model(), - Process3Model(), - Status(var1=var1,) - ) +mapping = ModelMapping( + "Leaf" => ( + Process1Model(1.0), + Process2Model(), + Process3Model(), + Status(var1=var1,) ) +) - outs = Dict( - "Leaf" => (:var1,), # :non_existing_variable is not computed by any model - ) +outs = Dict( + "Leaf" => (:var1,), # :non_existing_variable is not computed by any model +) run!(mtg, mapping, meteo_day, PlantMeteo.Constants(), tracked_outputs=outs) ``` diff --git a/docs/src/troubleshooting_and_testing/tips_and_workarounds.md b/docs/src/troubleshooting_and_testing/tips_and_workarounds.md index 8f5f5bf3e..a10a21270 100644 --- a/docs/src/troubleshooting_and_testing/tips_and_workarounds.md +++ b/docs/src/troubleshooting_and_testing/tips_and_workarounds.md @@ -67,7 +67,7 @@ It will parse your mapping, generate custom models to store and feed the vector !!! warning Only subtypes of AbstractVector present in statuses will be affected. In some cases, meteo values might need a small conversion. For instance : ``` - 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) status(TT_cu=cumsum(meteo_day.TT),)``` cumsum(meteo_day.TT) actually returns a CSV.SentinelArray.ChainedVectors{T, Vector{T}}, which is not a subtype of AbstractVector. @@ -78,8 +78,8 @@ Here's an example usage, fixing the first attempt at [Converting a single-scale ```julia using PlantSimEngine using PlantSimEngine.Examples -using PlantMeteo, CSV, DataFrames -meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18) +using PlantMeteo, Dates +meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day) # Direct translation of the single-scale simulation mapping_pseudo_multiscale = ModelMapping( diff --git a/docs/src/working_with_data/fitting.md b/docs/src/working_with_data/fitting.md index 3554d9845..0232acb21 100644 --- a/docs/src/working_with_data/fitting.md +++ b/docs/src/working_with_data/fitting.md @@ -1,7 +1,7 @@ # Parameter fitting ```@setup usepkg -using PlantSimEngine, PlantMeteo, DataFrames, Statistics +using PlantSimEngine, PlantMeteo, Dates, Statistics, DataFrames using PlantSimEngine.Examples meteo = Atmosphere(T=20.0, Wind=1.0, P=101.3, Rh=0.65, Ri_PAR_f=300.0) @@ -42,7 +42,7 @@ Here's an example of how to use the `fit` method: Importing the script first: ```julia -using PlantSimEngine, PlantMeteo, DataFrames, Statistics +using PlantSimEngine, PlantMeteo, Dates, DataFrames, Statistics # Import the examples defined in the `Examples` sub-module: using PlantSimEngine.Examples ``` diff --git a/docs/src/working_with_data/floating_point_accumulation_error.md b/docs/src/working_with_data/floating_point_accumulation_error.md index 6616a1d28..65420cd5a 100644 --- a/docs/src/working_with_data/floating_point_accumulation_error.md +++ b/docs/src/working_with_data/floating_point_accumulation_error.md @@ -3,8 +3,8 @@ ```@setup usepkg using PlantSimEngine using PlantSimEngine.Examples -using PlantMeteo, MultiScaleTreeGraph, CSV -meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), DataFrame, header=18) +using PlantMeteo, Dates, MultiScaleTreeGraph +meteo_day = read_weather(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), duration=Dates.Day) models = ModelMapping( ToyLAIModel(), @@ -22,7 +22,7 @@ In the [Converting a single-scale simulation to multi-scale](@ref) page, a singl ### Single-scale simulation ```@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) models_singlescale = ModelMapping( ToyLAIModel(), @@ -32,6 +32,7 @@ models_singlescale = ModelMapping( ) outputs_singlescale = run!(models_singlescale, meteo_day) +outputs_singlescale[1:3,:] # show the first 3 rows of the output ``` ### Multi-scale equivalent diff --git a/docs/src/working_with_data/inputs.md b/docs/src/working_with_data/inputs.md index dbc68f85c..aa4c2b6a8 100644 --- a/docs/src/working_with_data/inputs.md +++ b/docs/src/working_with_data/inputs.md @@ -37,7 +37,7 @@ To do so, you need to implement the following methods for your structure that de Here's a quick example showcasing how to export the example weather data to your own file : ```julia -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) PlantMeteo.write_weather("examples/meteo_day.csv", meteo_day, duration = Dates.Day) ``` diff --git a/docs/src/working_with_data/reducing_dof.md b/docs/src/working_with_data/reducing_dof.md index e172fff9a..1a4b925de 100644 --- a/docs/src/working_with_data/reducing_dof.md +++ b/docs/src/working_with_data/reducing_dof.md @@ -1,7 +1,7 @@ # Reducing the DoF ```@setup usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates # Import the examples defined in the `Examples` sub-module: using PlantSimEngine.Examples @@ -44,7 +44,7 @@ PlantSimEngine provides a simple way to reduce the degrees of freedom in a model Let's define a model list as usual with the seven processes from `examples/dummy.jl`: ```@example usepkg -using PlantSimEngine, PlantMeteo +using PlantSimEngine, PlantMeteo, Dates # Import the examples defined in the `Examples` sub-module: using PlantSimEngine.Examples diff --git a/docs/src/working_with_data/visualising_outputs.md b/docs/src/working_with_data/visualising_outputs.md index fb3279693..e7d833e2a 100644 --- a/docs/src/working_with_data/visualising_outputs.md +++ b/docs/src/working_with_data/visualising_outputs.md @@ -1,12 +1,12 @@ ```@setup usepkg -# ] add PlantSimEngine, DataFrames, CSV -using PlantSimEngine, PlantMeteo, DataFrames, CSV +# ] add PlantSimEngine, PlantMeteo +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( @@ -29,14 +29,14 @@ PlantSimEngine's run! functions return for each timestep the state of the variab Here's an example indicating how to plot output data using CairoMakie, a package used for plotting. ```@example usepkg -# ] add PlantSimEngine, DataFrames, CSV -using PlantSimEngine, PlantMeteo, DataFrames, CSV +# ] add PlantSimEngine, PlantMeteo +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: models = ModelMapping( @@ -47,6 +47,7 @@ models = ModelMapping( # Run the simulation: sim_outputs = run!(models, meteo_day) +sim_outputs[1:3,:] # show the first 3 rows of the output ``` The output data is displayed as a by default as a `TimeStepTable`. It is also possible to filter which variables are kept via the optional `tracked_outputs` keyword argument. diff --git a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation1.jl b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation1.jl index 298b83dac..63c86c570 100644 --- a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation1.jl +++ b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation1.jl @@ -6,7 +6,7 @@ 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 @@ -137,4 +137,4 @@ meteo_day = CSV.read(joinpath(pkgdir(PlantSimEngine), "examples/meteo_day.csv"), outs = run!(mtg, mapping, meteo_day) mtg -length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol="Leaf")) +length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol=:Leaf)) diff --git a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation2.jl b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation2.jl index 9f156bca1..31c0a92a5 100644 --- a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation2.jl +++ b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation2.jl @@ -8,17 +8,17 @@ 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 @@ -232,4 +232,4 @@ outs = run!(mtg, mapping, meteo_day) mtg -length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol="Leaf")) \ No newline at end of file +length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol=:Leaf)) \ No newline at end of file diff --git a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation3.jl b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation3.jl index cda458e84..bdb7846e0 100644 --- a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation3.jl +++ b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation3.jl @@ -8,17 +8,17 @@ 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 @@ -248,4 +248,4 @@ outs = run!(mtg, mapping, meteo_day) mtg -length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol="Leaf")) \ No newline at end of file +length(MultiScaleTreeGraph.traverse(mtg, x -> x, symbol=:Leaf)) \ No newline at end of file diff --git a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation4.jl b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation4.jl index 7cdcb1b50..1684ab44e 100644 --- a/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation4.jl +++ b/examples/ToyMultiScalePlantTutorial/ToyPlantSimulation4.jl @@ -67,7 +67,7 @@ function add_geometry!(mtg, refmesh_internode) internode_length = 1.0 traverse!(mtg) do node - if symbol(node) == "Internode" + if symbol(node) == :Internode # Set to scale, then translate by the total height mesh_transformation = Meshes.Scale(internode_width, internode_width, internode_length) → Meshes.Translate(0.0, 0.0, internode_height) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_internode, transformation=mesh_transformation) @@ -107,7 +107,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) i = 0 traverse!(mtg) do node - if symbol(node) == "Internode" + if symbol(node) == :Internode # Set to scale, then translate by the total height mesh_transformation = Meshes.Scale(internode_width, internode_width, internode_length) → Meshes.Translate(0.0, 0.0, internode_height) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_internode, transformation=mesh_transformation) @@ -116,7 +116,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) # Leaves are placed relatively to the parent internode for chnode in children(node) - if symbol(chnode) == "Leaf" + if symbol(chnode) == :Leaf # Leaves are placed halfway along the the parent internode mesh_transformation = Meshes.Scale(leaf_scale_width, leaf_scale_width, leaf_scale_height) → Meshes.Rotate(RotX(-MathConstants.pi / 6.0)) → Meshes.Translate(0.0, -internode_width, internode_height - internode_length / 2.0) → Meshes.Rotate(RotZ(leaf_rotation)) chnode.geometry = PlantGeom.Geometry(ref_mesh=refmesh_leaf, transformation=mesh_transformation) @@ -133,7 +133,7 @@ function add_geometry!(mtg, refmesh_internode, refmesh_root, refmesh_leaf) leaf_rotation = MathConstants.pi end - elseif symbol(node) == "Root" + elseif symbol(node) == :Root mesh_transformation = Meshes.Scale(root_width, root_width, root_length) → Meshes.Translate(0.0, 0.0, root_depth) → Meshes.Rotate(RotZ(MathConstants.pi)) node.geometry = PlantGeom.Geometry(ref_mesh=refmesh_root, transformation=mesh_transformation) root_depth -= root_length diff --git a/src/checks/dimensions.jl b/src/checks/dimensions.jl index 46a29959e..6cc929113 100644 --- a/src/checks/dimensions.jl +++ b/src/checks/dimensions.jl @@ -115,3 +115,5 @@ end function get_nsteps(::TableAlike, t) DataAPI.nrow(t) end + +get_nsteps(::TableAlike, t::PlantMeteo.TimeStepRows) = length(t) diff --git a/src/component_models/ModelList.jl b/src/component_models/ModelList.jl index ae88b37e0..44fb7d2d9 100644 --- a/src/component_models/ModelList.jl +++ b/src/component_models/ModelList.jl @@ -87,14 +87,7 @@ julia> meteo = Atmosphere(T = 22.0, Wind = 0.8333, P = 101.325, Rh = 0.4490995); ``` ```jldoctest 1 -julia> outputs_sim = run!(models,meteo) -TimeStepTable{Status{(:var5, :var4, :var6, ...}(1 x 6): -╭─────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────╮ -│ Row │ var5 │ var4 │ var6 │ var1 │ var3 │ var2 │ -│ │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ -├─────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ -│ 1 │ 36.0139 │ 22.0 │ 58.0139 │ 15.0 │ 5.5 │ 0.3 │ -╰─────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────╯ +julia> outputs_sim = run!(models,meteo); ``` ```jldoctest 1 @@ -475,4 +468,4 @@ end # Short form printing (e.g. inside another object) function Base.show(io::IO, t::ModelList) print(io, "ModelList", (; zip(keys(t.models), typeof.(values(t.models)))...)) -end \ No newline at end of file +end diff --git a/src/mtg/initialisation.jl b/src/mtg/initialisation.jl index b659619bd..36fed0660 100644 --- a/src/mtg/initialisation.jl +++ b/src/mtg/initialisation.jl @@ -92,19 +92,21 @@ in the node attributes (using the variable name). If `true`, the function return """ function init_node_status!(node, statuses, mapped_vars, reverse_multiscale_mapping, vars_need_init=Dict{String,Any}(), type_promotion=nothing; check=true, attribute_name=:plantsimengine_status) + node_scale = string(symbol(node)) + # Check if the node has a model defined for its symbol, if not, no need to compute - symbol(node) ∉ collect(keys(mapped_vars)) && return + haskey(mapped_vars, node_scale) || return # We make a copy of the template status for this node: - st_template = copy(mapped_vars[symbol(node)]) + st_template = copy(mapped_vars[node_scale]) # We add a reference to the node into the status, so that we can access it from the models if needed. push!(st_template, :node => Ref(node)) # If some variables still need to be instantiated in the status, look into the MTG node if we can find them, # and if so, use their value in the status: - if haskey(vars_need_init, symbol(node)) && length(vars_need_init[symbol(node)]) > 0 - for var in vars_need_init[symbol(node)] # e.g. var = :carbon_biomass + if haskey(vars_need_init, node_scale) && length(vars_need_init[node_scale]) > 0 + for var in vars_need_init[node_scale] # e.g. var = :carbon_biomass if !haskey(node, var) if !check # If we don't check, we use the default value from the model (and if it's an UninitializedVar we take its default value): @@ -148,12 +150,12 @@ function init_node_status!(node, statuses, mapped_vars, reverse_multiscale_mappi # Make the node status from the template: st = status_from_template(st_template) - push!(statuses[symbol(node)], st) + push!(statuses[node_scale], st) # Instantiate the RefVectors on the fly for other scales that map into this scale, *i.e.* # add a reference to the value of any variable that is used by another scale into its RefVector: - if haskey(reverse_multiscale_mapping, symbol(node)) - for (organ, vars) in reverse_multiscale_mapping[symbol(node)] # e.g.: organ = "Leaf"; vars = reverse_multiscale_mapping[symbol(node)][organ] + if haskey(reverse_multiscale_mapping, node_scale) + for (organ, vars) in reverse_multiscale_mapping[node_scale] # e.g.: organ = "Leaf"; vars = reverse_multiscale_mapping[symbol(node)][organ] for (var_source, var_target_) in vars # e.g.: var_source = :soil_water_content; var_target = vars[var_source] var_target = var_target_ isa PreviousTimeStep ? var_target_.variable : var_target_ push!(mapped_vars[organ][var_target], refvalue(st, var_source)) diff --git a/src/mtg/save_results.jl b/src/mtg/save_results.jl index df585215e..00bb6b87e 100644 --- a/src/mtg/save_results.jl +++ b/src/mtg/save_results.jl @@ -365,6 +365,6 @@ function save_results!(status_flattened::Status, outputs, i) outs = outputs[i] for var in keys(outs) - outs[var] = status_flattened[var] + setproperty!(outs, var, status_flattened[var]) end -end \ No newline at end of file +end diff --git a/src/run.jl b/src/run.jl index 7271fbaab..9cdb88454 100644 --- a/src/run.jl +++ b/src/run.jl @@ -394,7 +394,7 @@ function _run_modellist_singleton( run_node!(object, node, i, status_flattened, meteo_i, constants, extra) end for var in vars - outputs_preallocated_mt[i][var] = status_flattened[var] + setproperty!(outputs_preallocated_mt[i], var, status_flattened[var]) end end return outputs_preallocated_mt diff --git a/src/time/runtime/scopes.jl b/src/time/runtime/scopes.jl index 0e3ca7b7d..014bddb4f 100644 --- a/src/time/runtime/scopes.jl +++ b/src/time/runtime/scopes.jl @@ -18,7 +18,7 @@ function _model_spec_for_process(sim::GraphSimulation, scale::String, process::S return as_model_spec(models_at_scale[process]) end -function _find_ancestor_by_symbol(node, target::String) +function _find_ancestor_by_symbol(node, target::Symbol) current = node while !isnothing(current) symbol(current) == target && return current @@ -26,6 +26,7 @@ function _find_ancestor_by_symbol(node, target::String) end return nothing end +_find_ancestor_by_symbol(node, target::AbstractString) = _find_ancestor_by_symbol(node, Symbol(target)) function _scope_from_builtin(selector::Symbol, node, scale::String, process::Symbol) if selector == :global @@ -33,14 +34,14 @@ function _scope_from_builtin(selector::Symbol, node, scale::String, process::Sym elseif selector == :self return ScopeId(:self, node_id(node)) elseif selector == :plant - plant = _find_ancestor_by_symbol(node, "Plant") + plant = _find_ancestor_by_symbol(node, :Plant) isnothing(plant) && error( "Scope selector `:plant` for process `$(process)` at scale `$(scale)` ", "could not find a `Plant` ancestor for node `$(node_id(node))`." ) return ScopeId(:plant, node_id(plant)) elseif selector == :scene - scene = _find_ancestor_by_symbol(node, "Scene") + scene = _find_ancestor_by_symbol(node, :Scene) isnothing(scene) && error( "Scope selector `:scene` for process `$(process)` at scale `$(scale)` ", "could not find a `Scene` ancestor for node `$(node_id(node))`." @@ -106,4 +107,3 @@ function _scope_for_status(sim::GraphSimulation, model_spec, scale::String, proc selector = isnothing(model_spec) ? :global : model_scope(model_spec) return _scope_from_selector(selector, node, scale, process) end - diff --git a/src/traits/table_traits.jl b/src/traits/table_traits.jl index 480e31ce0..e4eab2f83 100644 --- a/src/traits/table_traits.jl +++ b/src/traits/table_traits.jl @@ -49,6 +49,7 @@ PlantSimEngine.SingletonAlike() """ DataFormat(::Type{<:DataFrames.AbstractDataFrame}) = TableAlike() DataFormat(::Type{<:PlantMeteo.TimeStepTable}) = TableAlike() +DataFormat(::Type{<:PlantMeteo.TimeStepRows}) = TableAlike() # Giving a ModelList as a vector or a dict of objects: DataFormat(::Type{<:AbstractVector}) = TableAlike() diff --git a/test/test-TimeStepTable.jl b/test/test-TimeStepTable.jl index 46cf95af9..02c601bff 100644 --- a/test/test-TimeStepTable.jl +++ b/test/test-TimeStepTable.jl @@ -54,8 +54,11 @@ end @test ts.Ra_SW_f == [4.0, 4.0] - # Setting all values in a column at once: - ts.Ra_SW_f = [5.0, 5.0] + # Setting all values in a column at once through row access: + # PlantMeteo.TimeStepTable no longer supports direct vector assignment for Status-backed rows. + for (row, value) in zip(Tables.rows(ts), [5.0, 5.0]) + row.Ra_SW_f = value + end @test ts.Ra_SW_f == [5.0, 5.0] # Testing transforming into a DataFrame: @@ -65,4 +68,4 @@ @test df.Ra_SW_f == [5.0, 5.0] @test df.sky_fraction == [0.8, 0.8] @test names(df) == [string.(keys(vars))...] -end \ No newline at end of file +end diff --git a/test/test-mtg-multiscale-cyclic-dep.jl b/test/test-mtg-multiscale-cyclic-dep.jl index 662673b9b..6c87e6755 100644 --- a/test/test-mtg-multiscale-cyclic-dep.jl +++ b/test/test-mtg-multiscale-cyclic-dep.jl @@ -241,8 +241,8 @@ end for organ in keys(out) reduced_ref_df = ref_df[(ref_df.organ .== organ), Not(:organ)] reduced_ref_df_no_missing = reduced_ref_df[:, any.(!ismissing, eachcol(reduced_ref_df))] - sorted_reduced_ref_df_no_missing = select(reduced_ref_df_no_missing, sort(propertynames(reduced_ref_df_no_missing))) - sorted_out_df_dict_organ = select(out_df_dict[organ], sort(propertynames(out_df_dict[organ]))) + sorted_reduced_ref_df_no_missing = DataFrames.select(reduced_ref_df_no_missing, sort(propertynames(reduced_ref_df_no_missing))) + sorted_out_df_dict_organ = DataFrames.select(out_df_dict[organ], sort(propertynames(out_df_dict[organ]))) @test sorted_reduced_ref_df_no_missing == sorted_out_df_dict_organ end -end \ No newline at end of file +end diff --git a/test/test-mtg-multiscale.jl b/test/test-mtg-multiscale.jl index 8bc5e2375..238ce0971 100644 --- a/test/test-mtg-multiscale.jl +++ b/test/test-mtg-multiscale.jl @@ -82,7 +82,7 @@ end # Another MTG with initialisation values for biomass: mtg_init = deepcopy(mtg) -transform!(mtg_init, (x -> 1.0) => :carbon_biomass, symbol=["Internode", "Leaf"]) +MultiScaleTreeGraph.transform!(mtg_init, (x -> 1.0) => :carbon_biomass, symbol=[:Internode, :Leaf]) @testset "Multiscale dependency graph" begin d = dep(mapping_1) diff --git a/test/test-multirate-runtime.jl b/test/test-multirate-runtime.jl index 585e3e034..ce339322e 100644 --- a/test/test-multirate-runtime.jl +++ b/test/test-multirate-runtime.jl @@ -541,7 +541,7 @@ PlantSimEngine.meteo_hint(::Type{<:MRMeteoHintConsumerModel}) = ( function plant_ancestor_id(node) current = node - while !isnothing(current) && symbol(current) != "Plant" + while !isnothing(current) && symbol(current) != :Plant current = parent(current) end isnothing(current) && error("Expected a Plant ancestor in scoped test tree.")