From 1f6f4a51b4247f600b63a8f312c8bc08d26ec719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Wed, 18 Mar 2026 14:52:40 +0100 Subject: [PATCH 1/8] Update Project.toml --- benchmark/Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/Project.toml b/benchmark/Project.toml index 04ade71fc..16cbac02d 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -13,5 +13,5 @@ XPalm = "6b523e1e-d512-416c-8e51-a8fbef0064e7" [sources] PlantSimEngine = {path = ".."} -XPalm = {rev = "PSe-with-multirate", url = "https://github.com/PalmStudio/XPalm.jl"} -PlantBiophysics = {rev = "update-plantsimengine", url = "https://github.com/VEZY/PlantBiophysics.jl"} +XPalm = {rev = "main", url = "https://github.com/PalmStudio/XPalm.jl"} +PlantBiophysics = {rev = "master", url = "https://github.com/VEZY/PlantBiophysics.jl"} From 968838bd2f7c97d08f1adb3869e7c52296d3c1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Wed, 18 Mar 2026 16:27:40 +0100 Subject: [PATCH 2/8] Update Benchmarks.yml --- .github/workflows/Benchmarks.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/Benchmarks.yml b/.github/workflows/Benchmarks.yml index 361b98844..e3bb1f698 100644 --- a/.github/workflows/Benchmarks.yml +++ b/.github/workflows/Benchmarks.yml @@ -24,3 +24,6 @@ jobs: with: julia-version: ${{ matrix.version }} bench-on: ${{ github.event.pull_request.head.sha }} + extra-pkgs: | + https://github.com/PalmStudio/XPalm.jl#main + https://github.com/VEZY/PlantBiophysics.jl#master From 58643b98cbc2baed6e2c7db28afd54d123cca6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Wed, 18 Mar 2026 16:48:02 +0100 Subject: [PATCH 3/8] debugging XPalm benchmark --- benchmark/test-xpalm.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmark/test-xpalm.jl b/benchmark/test-xpalm.jl index 13d891a2b..e3d9f7c98 100644 --- a/benchmark/test-xpalm.jl +++ b/benchmark/test-xpalm.jl @@ -47,6 +47,8 @@ function xpalm_default_param_convert_outputs(sim_outputs) end +println(Pkg.status("XPalm")) + #=@testset "XPalm simple test" begin # default number of seconds is 5 b_XP = @benchmark xpalm_default_param_run() seconds = 120 From 237670dd3c55e96727f3676d73a8810e6dee4922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Fri, 1 May 2026 09:01:12 +0200 Subject: [PATCH 4/8] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 57263c7e7..f81ea322b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ docs/Manifest.toml test/Manifest.toml docs/build/ -benchmark/Manifest.toml \ No newline at end of file +benchmark/Manifest.toml +frontend \ No newline at end of file From 3b384c51e114735cf70eec56345ea625024f2759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Fri, 1 May 2026 09:02:46 +0200 Subject: [PATCH 5/8] Use default arch for github runners --- .github/workflows/CI.yml | 8 +++----- .github/workflows/Integration.yml | 15 ++++----------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4a5d7e130..fdfd8b9b4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,10 +13,11 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} timeout-minutes: 60 - permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + permissions: + # needed to allow julia-actions/cache to proactively delete old caches that it has created actions: write contents: read strategy: @@ -29,14 +30,11 @@ jobs: - ubuntu-latest - macOS-latest - windows-latest - arch: - - x64 steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - uses: julia-actions/cache@v3 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 diff --git a/.github/workflows/Integration.yml b/.github/workflows/Integration.yml index 152135acc..0b5bcf3da 100644 --- a/.github/workflows/Integration.yml +++ b/.github/workflows/Integration.yml @@ -13,10 +13,11 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} timeout-minutes: 60 - permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + permissions: + # needed to allow julia-actions/cache to proactively delete old caches that it has created actions: write contents: read strategy: @@ -26,22 +27,14 @@ jobs: - "1" os: - ubuntu-latest - arch: - - x64 package: - { user: PalmStudio, repo: XPalm.jl, branch: main, default: main } - - { - user: VEZY, - repo: PlantBioPhysics.jl, - branch: master, - default: master, - } + - { user: VEZY, repo: PlantBioPhysics.jl, branch: master, default: master } steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - uses: julia-actions/julia-buildpkg@v1 - name: Clone Downstream uses: actions/checkout@v6 From e62f9772a3ef469be9d0a491247504583f71d589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Fri, 1 May 2026 15:11:19 +0200 Subject: [PATCH 6/8] Update benchmarks.jl Re-create the palm for each run (otherwise it is mutated and that fails) --- benchmark/benchmarks.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 5ca95357c..f3bdc06f7 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -44,8 +44,8 @@ SUITE[suite_name]["XPalm_setup"] = @benchmarkable xpalm_default_param_create() s palm, models, out_vars, meteo = xpalm_default_param_create() sim_outputs = xpalm_default_param_run(palm, models, out_vars, meteo) -SUITE[suite_name]["XPalm_run"] = @benchmarkable xpalm_default_param_run($palm, $models, $out_vars, $meteo) seconds = 120 -SUITE[suite_name]["XPalm_convert_outputs"] = @benchmarkable xpalm_default_param_convert_outputs($sim_outputs) seconds = 120 +SUITE[suite_name]["XPalm_run"] = @benchmarkable xpalm_default_param_run(palm, models, out_vars, meteo) setup = ((palm, models, out_vars, meteo) = xpalm_default_param_create()) +SUITE[suite_name]["XPalm_convert_outputs"] = @benchmarkable xpalm_default_param_convert_outputs($sim_outputs) #tune!(SUITE) #results = run(SUITE, verbose=true) From 696aeb05820aaa1e9bd44f469282808ddc0078da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Fri, 1 May 2026 15:15:53 +0200 Subject: [PATCH 7/8] Fix docs --- docs/src/working_with_data/inputs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/working_with_data/inputs.md b/docs/src/working_with_data/inputs.md index 93642a476..3c94c985b 100644 --- a/docs/src/working_with_data/inputs.md +++ b/docs/src/working_with_data/inputs.md @@ -64,7 +64,7 @@ outputs = run!( ) ``` -In multiscale runs, type promotion is used by [`GraphSimulation`](@ref) during status template creation, `RefVector` creation, output preallocation, and initialization from MTG node attributes. +In multiscale runs, type promotion is used by `GraphSimulation` during status template creation, `RefVector` creation, output preallocation, and initialization from MTG node attributes. ## Special considerations for new input types From aa318284abe4c253413b5f291a683bc9d7f0225c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Vezy?= Date: Sun, 3 May 2026 11:24:28 +0200 Subject: [PATCH 8/8] Replace all sting scales by symbols --- README.md | 36 ++++++------ benchmark/test-PSE-benchmark.jl | 58 +++++++++---------- benchmark/test-multirate-buffer-benchmark.jl | 26 ++++----- docs/src/API/API_public.md | 14 ++--- docs/src/model_execution.md | 12 ++-- .../multiscale/multiscale_considerations.md | 2 +- docs/src/multiscale/single_to_multiscale.md | 2 +- docs/src/prerequisites/key_concepts.md | 10 ++-- examples/ToyInternodeEmergence.jl | 4 +- src/dataframe.jl | 4 +- src/dependencies/dependency_graph.jl | 2 +- src/dependencies/hard_dependencies.jl | 14 ++--- src/dependencies/soft_dependencies.jl | 24 ++++---- src/mtg/GraphSimulation.jl | 8 +-- src/mtg/ModelSpec.jl | 2 +- src/mtg/MultiScaleModel.jl | 2 +- src/mtg/add_organ.jl | 2 +- src/mtg/initialisation.jl | 2 +- src/mtg/mapping/compute_mapping.jl | 16 ++--- src/mtg/mapping/reverse_mapping.jl | 4 +- src/mtg/save_results.jl | 4 +- src/processes/model_initialisation.jl | 8 +-- 22 files changed, 128 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 588838093..39ae36e10 100644 --- a/README.md +++ b/README.md @@ -233,52 +233,52 @@ The package is designed to be easily scalable, and can be used to simulate model ```julia mapping = ModelMapping( - "Scene" => ToyDegreeDaysCumulModel(), - "Plant" => ( + :Scene => ToyDegreeDaysCumulModel(), + :Plant => ( MultiScaleModel( model=ToyLAIModel(), mapped_variables=[ - :TT_cu => "Scene", + :TT_cu => :Scene, ], ), Beer(0.6), MultiScaleModel( model=ToyAssimModel(), - mapped_variables=[:soil_water_content => "Soil"], + mapped_variables=[:soil_water_content => :Soil], ), MultiScaleModel( model=ToyCAllocationModel(), mapped_variables=[ - :carbon_demand => ["Leaf", "Internode"], - :carbon_allocation => ["Leaf", "Internode"] + :carbon_demand => [:Leaf, :Internode], + :carbon_allocation => [:Leaf, :Internode] ], ), MultiScaleModel( model=ToyPlantRmModel(), - mapped_variables=[:Rm_organs => ["Leaf" => :Rm, "Internode" => :Rm],], + mapped_variables=[:Rm_organs => [:Leaf => :Rm, :Internode => :Rm],], ), ), - "Internode" => ( + :Internode => ( MultiScaleModel( model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), - mapped_variables=[:TT => "Scene",], + mapped_variables=[:TT => :Scene,], ), MultiScaleModel( model=ToyInternodeEmergence(TT_emergence=20.0), - mapped_variables=[:TT_cu => "Scene"], + mapped_variables=[:TT_cu => :Scene], ), ToyMaintenanceRespirationModel(1.5, 0.06, 25.0, 0.6, 0.004), Status(carbon_biomass=1.0) ), - "Leaf" => ( + :Leaf => ( MultiScaleModel( model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), - mapped_variables=[:TT => "Scene",], + mapped_variables=[:TT => :Scene,], ), ToyMaintenanceRespirationModel(2.1, 0.06, 25.0, 1.0, 0.025), Status(carbon_biomass=1.0) ), - "Soil" => ( + :Soil => ( ToySoilWaterModel(), ), ); @@ -305,11 +305,11 @@ And run the simulation: ```julia out_vars = ModelMapping( - "Scene" => (:TT_cu,), - "Plant" => (:carbon_allocation, :carbon_assimilation, :soil_water_content, :aPPFD, :TT_cu, :LAI), - "Leaf" => (:carbon_demand, :carbon_allocation), - "Internode" => (:carbon_demand, :carbon_allocation), - "Soil" => (:soil_water_content,), + :Scene => (:TT_cu,), + :Plant => (:carbon_allocation, :carbon_assimilation, :soil_water_content, :aPPFD, :TT_cu, :LAI), + :Leaf => (:carbon_demand, :carbon_allocation), + :Internode => (:carbon_demand, :carbon_allocation), + :Soil => (:soil_water_content,), ) out = run!(mtg, mapping, meteo, outputs=out_vars, executor=SequentialEx()); diff --git a/benchmark/test-PSE-benchmark.jl b/benchmark/test-PSE-benchmark.jl index 0203fde34..080f02b1a 100644 --- a/benchmark/test-PSE-benchmark.jl +++ b/benchmark/test-PSE-benchmark.jl @@ -28,21 +28,21 @@ function PlantSimEngine.run!(m::ToyInternodeCrazyEmergence, models, status, mete if length(MultiScaleTreeGraph.children(status.node)) == 1 && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence - status_new_internode = add_organ!(status.node, sim_object, "<", "Internode", 2, index=1) - add_organ!(status_new_internode.node, sim_object, "+", "Leaf", 2, index=1) + status_new_internode = add_organ!(status.node, sim_object, "<", :Internode, 2, index=1) + add_organ!(status_new_internode.node, sim_object, "+", :Leaf, 2, index=1) status_new_internode.TT_cu_emergence = status.TT_cu elseif (length(MultiScaleTreeGraph.children(status.node)) >= 2 && length(MultiScaleTreeGraph.children(status.node)) < 7) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence - status_new_internode = add_organ!(status.node, sim_object, "<", "Internode", 2, index=1) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=4) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=5) + status_new_internode = add_organ!(status.node, sim_object, "<", :Internode, 2, index=1) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=4) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=5) status_new_internode.TT_cu_emergence = status.TT_cu elseif (length(MultiScaleTreeGraph.children(status.node)) >= 7 && length(MultiScaleTreeGraph.children(status.node)) < 30) && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=6) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=7) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=8) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=9) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=10) - add_organ!(status.node, sim_object, "+", "Leaf", 2, index=11) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=6) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=7) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=8) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=9) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=10) + add_organ!(status.node, sim_object, "+", :Leaf, 2, index=11) end @@ -60,62 +60,62 @@ function do_benchmark_on_heavier_mtg() #similar to the mtg growth test but with a much lower emergence threshold mapping = ModelMapping( - "Scene" => ToyDegreeDaysCumulModel(), - "Plant" => ( + :Scene => ToyDegreeDaysCumulModel(), + :Plant => ( MultiScaleModel( model=ToyLAIModel(), mapped_variables=[ - :TT_cu => "Scene", + :TT_cu => :Scene, ], ), PlantSimEngine.Examples.Beer(0.6), MultiScaleModel( model=ToyCAllocationModel(), mapped_variables=[ - :carbon_assimilation => ["Leaf"], - :carbon_demand => ["Leaf", "Internode"], - :carbon_allocation => ["Leaf", "Internode"] + :carbon_assimilation => [:Leaf], + :carbon_demand => [:Leaf, :Internode], + :carbon_allocation => [:Leaf, :Internode] ], ), MultiScaleModel( model=ToyPlantRmModel(), - mapped_variables=[:Rm_organs => ["Leaf" => :Rm, "Internode" => :Rm],], + mapped_variables=[:Rm_organs => [:Leaf => :Rm, :Internode => :Rm],], ), ), - "Internode" => ( + :Internode => ( MultiScaleModel( model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), - mapped_variables=[:TT => "Scene",], + mapped_variables=[:TT => :Scene,], ), MultiScaleModel( model=ToyInternodeCrazyEmergence(TT_emergence=1.0), - mapped_variables=[:TT_cu => "Scene"], + mapped_variables=[:TT_cu => :Scene], ), ToyMaintenanceRespirationModel(1.5, 0.06, 25.0, 0.6, 0.004), Status(carbon_biomass=1.0) ), - "Leaf" => ( + :Leaf => ( MultiScaleModel( model=ToyAssimModel(), - mapped_variables=[:soil_water_content => "Soil", :aPPFD => "Plant"], + mapped_variables=[:soil_water_content => :Soil, :aPPFD => :Plant], ), MultiScaleModel( model=ToyCDemandModel(optimal_biomass=10.0, development_duration=200.0), - mapped_variables=[:TT => "Scene",], + mapped_variables=[:TT => :Scene,], ), ToyMaintenanceRespirationModel(2.1, 0.06, 25.0, 1.0, 0.025), Status(carbon_biomass=1.0) ), - "Soil" => ( + :Soil => ( ToySoilWaterModel(), ), ) out_vars = Dict( - "Leaf" => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation), - "Internode" => (:carbon_allocation, :TT_cu_emergence), - "Plant" => (:carbon_allocation,), - "Soil" => (:soil_water_content,), + :Leaf => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation), + :Internode => (:carbon_allocation, :TT_cu_emergence), + :Plant => (:carbon_allocation,), + :Soil => (:soil_water_content,), ) out = run!(mtg, mapping, meteo_day, tracked_outputs=out_vars, executor=SequentialEx()) diff --git a/benchmark/test-multirate-buffer-benchmark.jl b/benchmark/test-multirate-buffer-benchmark.jl index ba13c5ec9..2bc67d5a5 100644 --- a/benchmark/test-multirate-buffer-benchmark.jl +++ b/benchmark/test-multirate-buffer-benchmark.jl @@ -31,12 +31,12 @@ function PlantSimEngine.run!(::MRBenchConsumer24Model, models, status, meteo, co end function _build_multirate_benchmark_mtg(nleaves::Int) - mtg = Node(MultiScaleTreeGraph.NodeMTG("/", "Scene", 1, 0)) - plant = Node(mtg, MultiScaleTreeGraph.NodeMTG("+", "Plant", 1, 1)) - internode = Node(plant, MultiScaleTreeGraph.NodeMTG("/", "Internode", 1, 2)) + mtg = Node(MultiScaleTreeGraph.NodeMTG("/", :Scene, 1, 0)) + plant = Node(mtg, MultiScaleTreeGraph.NodeMTG("+", :Plant, 1, 1)) + internode = Node(plant, MultiScaleTreeGraph.NodeMTG("/", :Internode, 1, 2)) for i in 1:nleaves - Node(internode, MultiScaleTreeGraph.NodeMTG("+", "Leaf", i, 2)) + Node(internode, MultiScaleTreeGraph.NodeMTG("+", :Leaf, i, 2)) end return mtg @@ -46,18 +46,18 @@ function setup_multirate_buffer_benchmark(; nleaves=2000, ndays=30) mtg = _build_multirate_benchmark_mtg(nleaves) mapping = ModelMapping( - "Leaf" => ( + :Leaf => ( ModelSpec(MRBenchSourceModel(Ref(0))) |> TimeStepModel(1.0), ), - "Plant" => ( + :Plant => ( ModelSpec(MRBenchConsumer4Model()) |> - MultiScaleModel([:X => ["Leaf"]]) |> + MultiScaleModel([:X => [:Leaf]]) |> TimeStepModel(ClockSpec(4.0, 1.0)) |> - InputBindings(; X=(process=:mrbenchsource, var=:X, scale="Leaf", policy=Integrate())), + InputBindings(; X=(process=:mrbenchsource, var=:X, scale=:Leaf, policy=Integrate())), ModelSpec(MRBenchConsumer24Model()) |> - MultiScaleModel([:X => ["Leaf"]]) |> + MultiScaleModel([:X => [:Leaf]]) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> - InputBindings(; X=(process=:mrbenchsource, var=:X, scale="Leaf", policy=Integrate())), + InputBindings(; X=(process=:mrbenchsource, var=:X, scale=:Leaf, policy=Integrate())), ), ) @@ -65,11 +65,11 @@ function setup_multirate_buffer_benchmark(; nleaves=2000, ndays=30) meteo = Weather(repeat([Atmosphere(T=20.0, Wind=1.0, Rh=0.65)], nsteps)) reqs = [ - OutputRequest("Leaf", :X; name=:x_hourly, process=:mrbenchsource, policy=HoldLast()), - OutputRequest("Leaf", :X; name=:x_daily_sum, process=:mrbenchsource, policy=Integrate(), clock=ClockSpec(24.0, 1.0)), + OutputRequest(:Leaf, :X; name=:x_hourly, process=:mrbenchsource, policy=HoldLast()), + OutputRequest(:Leaf, :X; name=:x_daily_sum, process=:mrbenchsource, policy=Integrate(), clock=ClockSpec(24.0, 1.0)), ] - tracked = Dict("Plant" => (:Y4, :Y24), "Leaf" => (:X,)) + tracked = Dict(:Plant => (:Y4, :Y24), :Leaf => (:X,)) return mtg, mapping, meteo, reqs, tracked, nsteps end diff --git a/docs/src/API/API_public.md b/docs/src/API/API_public.md index 52c4e2800..b36380e4c 100644 --- a/docs/src/API/API_public.md +++ b/docs/src/API/API_public.md @@ -70,8 +70,8 @@ Scope selection detail: ### Exporting variables at requested rates ```julia -req_hold = OutputRequest("Leaf", :A; name=:A_hourly, process=:assim, policy=HoldLast()) -req_day = OutputRequest("Leaf", :A; name=:A_daily_sum, process=:assim, policy=Integrate(), clock=ClockSpec(24.0, 1.0)) +req_hold = OutputRequest(:Leaf, :A; name=:A_hourly, process=:assim, policy=HoldLast()) +req_day = OutputRequest(:Leaf, :A; name=:A_daily_sum, process=:assim, policy=Integrate(), clock=ClockSpec(24.0, 1.0)) run!(sim, meteo; tracked_outputs=[req_hold, req_day], executor=SequentialEx()) out = collect_outputs(sim; sink=DataFrame) @@ -126,23 +126,23 @@ Use `Integrate` for accumulation semantics and `Aggregate` for summary-statistic ```julia ModelSpec(DailyModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> -InputBindings(; a=(process=:hourly_assim, var=:A, scale="Leaf", policy=Integrate(SumReducer()))) +InputBindings(; a=(process=:hourly_assim, var=:A, scale=:Leaf, policy=Integrate(SumReducer()))) ModelSpec(DailyModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> -InputBindings(; a=(process=:hourly_assim, var=:A, scale="Leaf", policy=Aggregate(MaxReducer()))) +InputBindings(; a=(process=:hourly_assim, var=:A, scale=:Leaf, policy=Aggregate(MaxReducer()))) ModelSpec(DailyModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> -InputBindings(; a=(process=:hourly_assim, var=:A, scale="Leaf", policy=Integrate(vals -> maximum(vals) - minimum(vals)))) +InputBindings(; a=(process=:hourly_assim, var=:A, scale=:Leaf, policy=Integrate(vals -> maximum(vals) - minimum(vals)))) ModelSpec(DailyModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> -InputBindings(; a=(process=:hourly_assim, var=:A, scale="Leaf", policy=Integrate((vals, durations) -> sum(vals .* durations)))) +InputBindings(; a=(process=:hourly_assim, var=:A, scale=:Leaf, policy=Integrate((vals, durations) -> sum(vals .* durations)))) ModelSpec(DailyModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> -InputBindings(; a=(process=:hourly_assim, var=:A, scale="Leaf", policy=Integrate(PlantMeteo.DurationSumReducer()))) +InputBindings(; a=(process=:hourly_assim, var=:A, scale=:Leaf, policy=Integrate(PlantMeteo.DurationSumReducer()))) ``` Built-in reducer types are: diff --git a/docs/src/model_execution.md b/docs/src/model_execution.md index 8cfaa52d4..3d1c575f3 100644 --- a/docs/src/model_execution.md +++ b/docs/src/model_execution.md @@ -142,7 +142,7 @@ aggregates over that civil day (including later timesteps from that day when ava ```julia mapping = ModelMapping( - "Leaf" => ( + :Leaf => ( ModelSpec(LeafSourceModel()) |> TimeStepModel(1.0), ModelSpec(LeafConsumerModel()) |> TimeStepModel(ClockSpec(2.0, 1.0)) |> @@ -155,13 +155,13 @@ mapping = ModelMapping( ```julia mapping = ModelMapping( - "Leaf" => ( + :Leaf => ( ModelSpec(HourlyAssimModel()) |> TimeStepModel(1.0), ), - "Plant" => ( + :Plant => ( ModelSpec(DailyCarbonOfferModel()) |> TimeStepModel(ClockSpec(24.0, 1.0)) |> - InputBindings(; A=(process=:hourlyassim, var=:A, scale="Leaf", policy=Integrate())), + InputBindings(; A=(process=:hourlyassim, var=:A, scale=:Leaf, policy=Integrate())), ), ) ``` @@ -170,7 +170,7 @@ mapping = ModelMapping( ```julia mapping = ModelMapping( - "Leaf" => ( + :Leaf => ( ModelSpec(SlowSourceModel()) |> TimeStepModel(ClockSpec(2.0, 1.0)), ModelSpec(FastConsumerModel()) |> TimeStepModel(1.0) |> @@ -195,7 +195,7 @@ on each `ModelSpec`. You can export selected variables at a requested rate from temporal streams: ```julia -req = OutputRequest("Leaf", :carbon_assimilation; +req = OutputRequest(:Leaf, :carbon_assimilation; name=:A_daily, process=:toyassim, policy=Integrate(), diff --git a/docs/src/multiscale/multiscale_considerations.md b/docs/src/multiscale/multiscale_considerations.md index f0125e024..091cf32e6 100644 --- a/docs/src/multiscale/multiscale_considerations.md +++ b/docs/src/multiscale/multiscale_considerations.md @@ -44,7 +44,7 @@ Some models, like the ones we've seen in single-scale simulations, work on a ver More fine-grained models can be tied to a specific plant organ. -For instance, a model computing a leaf's surface area depending on its age would operate at the "leaf" scale, and be called **for every leaf** at every timestep. On the other hand, a model computing the plant's total leaf area only needs to be run once per timestep, and can be run at the :Plant scale. +For instance, a model computing a leaf's surface area depending on its age would operate at the `:Leaf` scale, and be called **for every leaf** at every timestep. On the other hand, a model computing the plant's total leaf area only needs to be run once per timestep, and can be run at the `:Plant` scale. This is a major difference between a single-scale simulation and a multi-scale one. By default, any model in a single-scale simulation will only run **once** per timestep. However, in multi-scale, if a plant has several instances of an organ type -say it has a hundred leaves- then any model operating at the :Leaf scale will by default run one hundred times per timestep, unless it is explicitely controlled by another model (which can happen in hard dependency configurations). diff --git a/docs/src/multiscale/single_to_multiscale.md b/docs/src/multiscale/single_to_multiscale.md index f971eba4a..7e8af7caf 100644 --- a/docs/src/multiscale/single_to_multiscale.md +++ b/docs/src/multiscale/single_to_multiscale.md @@ -51,7 +51,7 @@ 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. +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. We can therefore convert this into the following mapping: diff --git a/docs/src/prerequisites/key_concepts.md b/docs/src/prerequisites/key_concepts.md index a2f077cd8..93bef82e6 100644 --- a/docs/src/prerequisites/key_concepts.md +++ b/docs/src/prerequisites/key_concepts.md @@ -114,12 +114,12 @@ PlantSimEngine documentation tends to use the terms "organ" and "scale" mostly i When working with multi-scale data, the scale will often need to be specified to map variables, or to indicate at what scale level models work out. You will see some code resembling this : ```julia -"Root" => (RootGrowthModel(), OrganAgeModel()), -"Leaf" => (LightInterceptionModel(), OrganAgeModel()), -"Plant" => (TotalBiomassModel(),), +:Root => (RootGrowthModel(), OrganAgeModel()), +:Leaf => (LightInterceptionModel(), OrganAgeModel()), +:Plant => (TotalBiomassModel(),), ``` -This example excerpt links from specific models to a specific scale. Note that one model is reused at two different scales, and note that "Plant" isn't an actual organ, hence the preferred usage of the term "scale". +This example excerpt links from specific models to a specific scale. Note that one model is reused at two different scales, and note that `:Plant` isn't an actual organ, hence the preferred usage of the term "scale". ### Multiscale modeling @@ -154,7 +154,7 @@ You can see a basic display of an MTG by simply typing its name in the REPL: Multi-scale tree graphs have different terminology (see [Organ/Scale](@ref)): -- the MTG node **symbol** represents "something" like a "Plant", "Root", "Scene" or "Leaf". It corresponds to a PlantSimEngine *scale* and has nothing to do with the Julia programming language's definition of symbol (*e.g.* `:var`) +- the MTG node **symbol** represents "something" like a `:Plant`, `:Root`, `:Scene` or `:Leaf`. It corresponds to a PlantSimEngine *scale* and has nothing to do with the Julia programming language's definition of symbol (*e.g.* `:var`) - the MTG node **scale**, is an integer passed to the Node constructor, and describes the level of description of the tree graph object. They don't always have a one-to-one correspondence to the symbol (or PlantSimEngine's scale), but are similar. ![Three scale levels on an MTG, which differ from typical PlantSimEngine concept of scale](../www/Grassy_plant_scales.svg) diff --git a/examples/ToyInternodeEmergence.jl b/examples/ToyInternodeEmergence.jl index 4c67a9718..b43cfd58d 100644 --- a/examples/ToyInternodeEmergence.jl +++ b/examples/ToyInternodeEmergence.jl @@ -26,8 +26,8 @@ function PlantSimEngine.run!(m::ToyInternodeEmergence, models, status, meteo, co if length(MultiScaleTreeGraph.children(status.node)) == 1 && status.TT_cu - status.TT_cu_emergence >= m.TT_emergence # NB: the node can produce one leaf, and one internode only, so we check that it did not produce # any internode yet. - status_new_internode = add_organ!(status.node, sim_object, "<", "Internode", 2, index=1) - add_organ!(status_new_internode.node, sim_object, "+", "Leaf", 2, index=1) + status_new_internode = add_organ!(status.node, sim_object, "<", :Internode, 2, index=1) + add_organ!(status_new_internode.node, sim_object, "+", :Leaf, 2, index=1) status_new_internode.TT_cu_emergence = status.TT_cu end diff --git a/src/dataframe.jl b/src/dataframe.jl index e36c153f7..2a9fba567 100644 --- a/src/dataframe.jl +++ b/src/dataframe.jl @@ -23,12 +23,12 @@ df = DataFrame(models) # Converting to a Dict of ModelMappings models = ModelMapping( - "Leaf" => ModelMapping( + :Leaf => ModelMapping( process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model() ), - "InterNode" => ModelMapping( + :InterNode => ModelMapping( process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model() diff --git a/src/dependencies/dependency_graph.jl b/src/dependencies/dependency_graph.jl index 03973ef3d..7063b77e9 100644 --- a/src/dependencies/dependency_graph.jl +++ b/src/dependencies/dependency_graph.jl @@ -92,7 +92,7 @@ Return a NamedTuple with the variables and their default values. # Arguments - `node::HardDependencyNode`: the node to get the variables from. -- `organ::String`: the organ type, *e.g.* "Leaf". +- `organ::Symbol`: the organ type, *e.g.* :`Leaf`. - `vars_mapping::Dict{String,T}`: the mapping of the models (see details below). - `st::NamedTuple`: an optional named tuple with default values for the variables. diff --git a/src/dependencies/hard_dependencies.jl b/src/dependencies/hard_dependencies.jl index e761e5e8a..ba623980d 100644 --- a/src/dependencies/hard_dependencies.jl +++ b/src/dependencies/hard_dependencies.jl @@ -44,7 +44,7 @@ function hard_dependencies(models; scale=nothing, verbose::Bool=true) # The dependency can be given as multiscale, e.g. `leaf_area=AbstractLeaf_AreaModel => [m.leaf_symbol],` # This means we should search this model in another scale. This is not done here, but after the call to this # function in the other method for `hard_dependencies` below. - if isa(depend, Pair) + if isa(depend, Pair) if !isnothing(scale) # We skip this hard-dependency if it is multiscale, we compute this afterwards in this case target_scales = _normalize_hard_dependency_scales(last(depend), process, p) @@ -87,7 +87,7 @@ function hard_dependencies(models; scale=nothing, verbose::Bool=true) if verbose @info string( "Model ", typeof(i).name.name, " from process ", process, - isnothing(scale) ? "" : " at scale $scale", + isnothing(scale) ? "" : " at scale $scale", " needs a model that is a subtype of ", depend, " in process ", p, ", but the process is not parameterized in the ModelList." ) @@ -155,8 +155,8 @@ function hard_dependencies(mapping::AbstractDict{Symbol,T}; verbose::Bool=true) # Since the hard dependencies are inserted into the soft dependency graph as children and aren't referenced elsewhere # it becomes harder to keep track of them as needed without traversing the graph # so keep tabs on them during initialisation until they're no longer needed - hard_dependency_dict = Dict{Pair{Symbol,Symbol}, HardDependencyNode}() - + hard_dependency_dict = Dict{Pair{Symbol,Symbol},HardDependencyNode}() + hard_deps = Dict(organ => hard_dependencies(mods_scale, scale=organ, verbose=false) for (organ, mods_scale) in mods) # Compute the inputs and outputs of all "root" node of the hard dependencies, so the root @@ -189,7 +189,7 @@ function hard_dependencies(mapping::AbstractDict{Symbol,T}; verbose::Bool=true) # If some models needed as hard-dependency are not found in their own scale, check the other scales: for (organ, model) in mapping - # organ = "Plant"; model = mapping[organ] + # organ = :Plant; model = mapping[organ] # filtering the hard dependency that were defined as multiscale (NamedTuple with information) multiscale_hard_dep = filter(x -> isa(last(x), NamedTuple), hard_deps[organ].not_found) for (p, (parent_process, model_type, scales)) in multiscale_hard_dep @@ -219,7 +219,7 @@ function hard_dependencies(mapping::AbstractDict{Symbol,T}; verbose::Bool=true) end # We make a new node out of the previous one: - new_node = HardDependencyNode( + new_node = HardDependencyNode( dep_node_model.value, dep_node_model.process, dep_node_model.dependency, @@ -230,7 +230,7 @@ function hard_dependencies(mapping::AbstractDict{Symbol,T}; verbose::Bool=true) parent_node, dep_node_model.children ) - + # Add our new node as a child of the parent node (the one that requires it as a hard dependency) push!(parent_node.children, new_node) diff --git a/src/dependencies/soft_dependencies.jl b/src/dependencies/soft_dependencies.jl index 5960b5931..ecf36a8bd 100644 --- a/src/dependencies/soft_dependencies.jl +++ b/src/dependencies/soft_dependencies.jl @@ -140,9 +140,9 @@ end # For multiscale mapping: function soft_dependencies_multiscale(soft_dep_graphs_roots::DependencyGraph{Dict{Symbol,Any}}, reverse_multiscale_mapping, hard_dep_dict::Dict{Pair{Symbol,Symbol},HardDependencyNode}) - + independant_process_root = Dict{Pair{Symbol,Symbol},SoftDependencyNode}() - for (organ, (soft_dep_graph, ins, outs)) in soft_dep_graphs_roots.roots # e.g. organ = "Plant"; soft_dep_graph, ins, outs = soft_dep_graphs_roots.roots[organ] + for (organ, (soft_dep_graph, ins, outs)) in soft_dep_graphs_roots.roots # e.g. organ = :Plant; soft_dep_graph, ins, outs = soft_dep_graphs_roots.roots[organ] for (proc, i) in soft_dep_graph # proc = :leaf_surface; i = soft_dep_graph[proc] # Search if the process has soft dependencies: @@ -157,8 +157,8 @@ function soft_dependencies_multiscale(soft_dep_graphs_roots::DependencyGraph{Dic # Check if the process has soft dependencies at other scales: soft_deps_multiscale = search_inputs_in_multiscale_output(proc, organ, ins, soft_dep_graphs_roots.roots, reverse_multiscale_mapping, hard_dependencies_from_other_scale) - # Example output: "Soil" => Dict(:soil_water=>[:soil_water_content]), which means that the variable :soil_water_content - # is computed by the process :soil_water at the scale "Soil". + # Example output: :Soil => Dict(:soil_water=>[:soil_water_content]), which means that the variable :soil_water_content + # is computed by the process :soil_water at the scale :Soil. if length(soft_deps_not_hard) == 0 && i.process in keys(soft_dep_graph) && length(soft_deps_multiscale) == 0 # If the process has no soft (multiscale) dependencies, then it is independant (so it is a root) @@ -438,9 +438,9 @@ end # Arguments - `process::Symbol`: the process for which we want to find the soft dependencies at other scales. -- `organ::String`: the organ for which we want to find the soft dependencies. +- `organ::Symbol`: the organ for which we want to find the soft dependencies. - `inputs::Dict{Symbol, Vector{Pair{Symbol}, Tuple{Symbol, Vararg{Symbol}}}}`: a dict of process => [:subprocess => (:var1, :var2)]. -- `soft_dep_graphs::Dict{String, ...}`: a dict of organ => (soft_dep_graph, inputs, outputs). +- `soft_dep_graphs::Dict{Symbol, ...}`: a dict of organ => (soft_dep_graph, inputs, outputs). - `rev_mapping::Dict{Symbol, Symbol}`: a dict of mapped variable => source variable (this is the reverse mapping). - 'hard_dependencies_from_other_scale' : a vector of HardDependencyNode to provide access to the hard dependencies without traversing the whole graph @@ -454,13 +454,13 @@ come from itself (its own inputs), or from another process that is a hard-depend A dictionary with the soft dependencies variables found in outputs of other scales for each process, e.g.: ```julia -Dict{String, Dict{Symbol, Vector{Symbol}}} with 2 entries: - "Internode" => Dict(:carbon_demand=>[:carbon_demand]) - "Leaf" => Dict(:carbon_assimilation=>[:carbon_assimilation], :carbon_demand=>[:carbon_demand]) +Dict{Symbol, Dict{Symbol, Vector{Symbol}}} with 2 entries: + :Internode => Dict(:carbon_demand=>[:carbon_demand]) + :Leaf => Dict(:carbon_assimilation=>[:carbon_assimilation], :carbon_demand=>[:carbon_demand]) ``` -This means that the variable `:carbon_demand` is computed by the process `:carbon_demand` at the scale "Internode", and the variable `:carbon_assimilation` -is computed by the process `:carbon_assimilation` at the scale "Leaf". Those variables are used as inputs for the process that we just passed. +This means that the variable `:carbon_demand` is computed by the process `:carbon_demand` at the scale `:Internode`, and the variable `:carbon_assimilation` +is computed by the process `:carbon_assimilation` at the scale `:Leaf`. Those variables are used as inputs for the process that we just passed. """ function search_inputs_in_multiscale_output(process, organ, inputs, soft_dep_graphs, rev_mapping, hard_dependencies_from_other_scale) # proc, organ, ins, soft_dep_graphs=soft_dep_graphs_roots.roots @@ -478,7 +478,7 @@ function search_inputs_in_multiscale_output(process, organ, inputs, soft_dep_gra end @assert all(var_o != organ for var_o in var_organ) "$var in process $process is set to be multiscale, but points to its own scale ($organ). This is not allowed." - for org in var_organ # e.g. org = "Leaf" + for org in var_organ # e.g. org = :Leaf # The variable is a multiscale variable: haskey(soft_dep_graphs, org) || error("Scale $org not found in the mapping, but mapped to the $organ scale.") mapped_var = mapped_variable(val) diff --git a/src/mtg/GraphSimulation.jl b/src/mtg/GraphSimulation.jl index 07909de2e..8b826680d 100644 --- a/src/mtg/GraphSimulation.jl +++ b/src/mtg/GraphSimulation.jl @@ -89,10 +89,10 @@ mtg = import_mtg_example(); ```@example out = run!(mtg, mapping, meteo, tracked_outputs = Dict( - "Leaf" => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation), - "Internode" => (:carbon_allocation,), - "Plant" => (:carbon_allocation,), - "Soil" => (:soil_water_content,), + :Leaf => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation), + :Internode => (:carbon_allocation,), + :Plant => (:carbon_allocation,), + :Soil => (:soil_water_content,), )); ``` diff --git a/src/mtg/ModelSpec.jl b/src/mtg/ModelSpec.jl index 6b0fe73b1..f461515ec 100644 --- a/src/mtg/ModelSpec.jl +++ b/src/mtg/ModelSpec.jl @@ -285,7 +285,7 @@ explicit `InputBindings(...)`. ```julia ModelSpec(ConsumerModel()) |> TimeStepModel(ClockSpec(24.0, 0.0)) |> -InputBindings(; A=(process=:assim, var=:carbon_assimilation, scale="Leaf", policy=Integrate())) +InputBindings(; A=(process=:assim, var=:carbon_assimilation, scale=:Leaf, policy=Integrate())) ``` """ InputBindings(bindings) = x -> with_input_bindings(x, bindings) diff --git a/src/mtg/MultiScaleModel.jl b/src/mtg/MultiScaleModel.jl index 83b8e6e24..a5535af6f 100644 --- a/src/mtg/MultiScaleModel.jl +++ b/src/mtg/MultiScaleModel.jl @@ -167,7 +167,7 @@ function _normalize_mapped_scale(scale::AbstractString) return Symbol(scale) end -# Case 1: [:variable_name => "Plant"] (deprecated, coerced to symbol scale) +# Case 1: [:variable_name => :Plant] (deprecated, coerced to symbol scale) function _get_var(i::Pair{Symbol,S}, proc::Symbol=:unknown) where {S<:AbstractString} scale = _normalize_mapped_scale(last(i)) return first(i) => scale => first(i) diff --git a/src/mtg/add_organ.jl b/src/mtg/add_organ.jl index 1c2925efe..784127f89 100644 --- a/src/mtg/add_organ.jl +++ b/src/mtg/add_organ.jl @@ -13,7 +13,7 @@ This function should be called from a model that implements organ emergence, for * `"<"`: the new node is following the parent organ * `"+"`: the new node is branching the parent organ * `"/"`: the new node is decomposing the parent organ, *i.e.* we change scale -* `symbol`: the symbol of the organ, *e.g.* `"Leaf"` +* `symbol`: the symbol of the organ, *e.g.* `:Leaf` * `scale`: the scale of the organ, *e.g.* `2`. * `index`: the index of the organ, *e.g.* `1`. The index may be used to easily identify branching order, or growth unit index on the axis. It is different from the node `id` that is unique. * `id`: the unique id of the new node. If not provided, a new id is generated. diff --git a/src/mtg/initialisation.jl b/src/mtg/initialisation.jl index 80411136a..98aa612c6 100644 --- a/src/mtg/initialisation.jl +++ b/src/mtg/initialisation.jl @@ -155,7 +155,7 @@ function init_node_status!(node, statuses, mapped_vars, reverse_multiscale_mappi # 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, node_scale) - for (organ, vars) in reverse_multiscale_mapping[node_scale] # e.g.: organ = "Leaf"; vars = reverse_multiscale_mapping[symbol(node)][organ] + 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/mapping/compute_mapping.jl b/src/mtg/mapping/compute_mapping.jl index efe3f4fac..a6ccabccf 100644 --- a/src/mtg/mapping/compute_mapping.jl +++ b/src/mtg/mapping/compute_mapping.jl @@ -71,7 +71,7 @@ This function is used with mapped_variables function variables_outputs_from_other_scale(mapped_vars) vars_outputs_from_scales = Dict{Symbol,Vector{Pair{Symbol,Any}}}() # Scale at which we have to add a variable => [(source_process, source_scale, variable), ...] - for (organ, outs) in mapped_vars[:outputs] # organ = "Leaf" ; outs = mapped_vars[:outputs][organ] + for (organ, outs) in mapped_vars[:outputs] # organ = :Leaf ; outs = mapped_vars[:outputs][organ] for (var, val) in pairs(outs) # var = :carbon_biomass ; val = outs[1] if isa(val, MappedVar) orgs = mapped_organ(val) @@ -161,7 +161,7 @@ This helps us declare it as a reference when we create the template status objec These node are found in the mapping as `[:variable_name => :Plant]` (notice that `:Plant` is a scalar value). """ function transform_single_node_mapped_variables_as_self_node_output!(mapped_vars) - for (organ, vars) in mapped_vars[:inputs] # e.g. organ = "Leaf"; vars = mapped_vars[:inputs][organ] + for (organ, vars) in mapped_vars[:inputs] # e.g. organ = :Leaf; vars = mapped_vars[:inputs][organ] for (var, mapped_var) in pairs(vars) # e.g. var = :carbon_biomass; mapped_var = vars[var] if isa(mapped_var, MappedVar{SingleNodeMapping}) source_organ = mapped_organ(mapped_var) @@ -175,7 +175,7 @@ function transform_single_node_mapped_variables_as_self_node_output!(mapped_vars # If the source variable was already defined as a `MappedVar{SelfNodeMapping}` by another scale, we skip it: isa(mapped_vars[:outputs][source_organ][source_variable(mapped_var)], MappedVar{SelfNodeMapping}) && continue # Note: this happens when a variable is mapped to several scales, e.g. soil_water_content computed at soil scale can be - # mapped at "Leaf" and "Internode" scale. + # mapped at :Leaf and :Internode scale. # Transforming the variable into a MappedVar pointing to itself: self_mapped_var = (; @@ -220,7 +220,7 @@ function get_multiscale_default_value(mapped_vars, val, mapping_stacktrace=[], l m_organ = [m_organ] end default_vals = [] - for o in m_organ # e.g. o = "Leaf" + for o in m_organ # e.g. o = :Leaf haskey(mapped_vars[o], source_variable(val, o)) || error("Variable `$(source_variable(val, o))` is mapped from scale `$o` to another scale, but is not computed by any model at `$o` scale.") upper_value = mapped_vars[o][source_variable(val, o)] push!(mapping_stacktrace, (mapped_organ=o, mapped_variable=source_variable(val, o), mapped_value=mapped_default(upper_value), level=level)) @@ -256,7 +256,7 @@ Get the default values for the mapped variables by recursively searching from th """ function default_variables_from_mapping(mapped_vars, verbose=true) mapped_vars_mutable = Dict{Symbol,Dict{Symbol,Any}}(k => Dict(pairs(v)) for (k, v) in mapped_vars) - for (organ, vars) in mapped_vars # organ = "Leaf"; vars = mapped_vars[organ] + for (organ, vars) in mapped_vars # organ = :Leaf; vars = mapped_vars[organ] for (var, val) in pairs(vars) # var = :carbon_biomass; val = getproperty(vars,var) if isa(val, MappedVar) && !isa(val, MappedVar{SelfNodeMapping}) && mapped_organ(val) != Symbol("") mapping_stacktrace = Any[(mapped_organ=organ, mapped_variable=var, mapped_value=mapped_default(mapped_vars[organ][var]), level=1)] @@ -295,7 +295,7 @@ function convert_reference_values!(mapped_vars::Dict{Symbol,Dict{Symbol,Any}}) dict_mapped_vars = Dict{Pair,Any}() # First pass: converting the MappedVar{SelfNodeMapping} and MappedVar{SingleNodeMapping} to RefValues: - for (organ, vars) in mapped_vars # e.g.: organ = "Plant"; vars = mapped_vars[organ] + for (organ, vars) in mapped_vars # e.g.: organ = :Plant; vars = mapped_vars[organ] for (k, v) in vars # e.g.: k = :aPPFD_larger_scale; v = vars[k] if isa(v, MappedVar{SelfNodeMapping}) || isa(v, MappedVar{SingleNodeMapping}) mapped_org = isa(v, MappedVar{SelfNodeMapping}) ? organ : mapped_organ(v) @@ -314,7 +314,7 @@ function convert_reference_values!(mapped_vars::Dict{Symbol,Dict{Symbol,Any}}) end # Second pass: converting the MappedVar{MultiNodeMapping} to RefVectors: - for (organ, vars) in mapped_vars # e.g.: organ = "Plant"; vars = mapped_vars[organ] + for (organ, vars) in mapped_vars # e.g.: organ = :Plant; vars = mapped_vars[organ] for (k, v) in vars # e.g.: k = :carbon_allocation; v = vars[k] if isa(v, MappedVar{MultiNodeMapping}) # We have to create a RefVector for the target organ: @@ -337,7 +337,7 @@ function convert_reference_values!(mapped_vars::Dict{Symbol,Dict{Symbol,Any}}) end # Third pass: getting the same reference for the variables that are mapped at the same scale to another variable (changing its name): - for (organ, vars) in mapped_vars # e.g.: organ = "Plant"; vars = mapped_vars[organ] + for (organ, vars) in mapped_vars # e.g.: organ = :Plant; vars = mapped_vars[organ] for (k, v) in vars # e.g.: k = :carbon_allocation; v = vars[k] if isa(v, MappedVar) && mapped_organ(v) == Symbol("") mapped_var = mapped_variable(v) diff --git a/src/mtg/mapping/reverse_mapping.jl b/src/mtg/mapping/reverse_mapping.jl index 61917de9a..aafca47a5 100644 --- a/src/mtg/mapping/reverse_mapping.jl +++ b/src/mtg/mapping/reverse_mapping.jl @@ -78,7 +78,7 @@ end function reverse_mapping(mapped_vars::Dict{Symbol,Dict{Symbol,Any}}; all=true) reverse_multiscale_mapping = Dict{Symbol,Dict{Symbol,Dict{Symbol,Any}}}(org => Dict{Symbol,Dict{Symbol,Any}}() for org in keys(mapped_vars)) - for (organ, vars) in mapped_vars # e.g.: organ = "Plant"; vars = mapped_vars[organ] + for (organ, vars) in mapped_vars # e.g.: organ = :Plant; vars = mapped_vars[organ] for (var, val) in vars # e.g. var = :Rm_organs; val = vars[var] if isa(val, MappedVar) && !isa(val, MappedVar{SelfNodeMapping}) && (all || !isa(val, MappedVar{SingleNodeMapping})) # Note: We skip the MappedVar{SelfNodeMapping} because it is a special case where the variable is mapped to itself @@ -93,7 +93,7 @@ function reverse_mapping(mapped_vars::Dict{Symbol,Dict{Symbol,Any}}; all=true) mapped_orgs = [_normalize_scale(mapped_orgs; warn=true, context=:ModelMapping)] end - for mapped_o in mapped_orgs # e.g.: mapped_o = "Leaf" + for mapped_o in mapped_orgs # e.g.: mapped_o = :Leaf mapped_o == Symbol("") && continue # if !haskey(reverse_multiscale_mapping, mapped_o) # reverse_multiscale_mapping[mapped_o] = Dict{Symbol,Vector{MappedVar}}() diff --git a/src/mtg/save_results.jl b/src/mtg/save_results.jl index 21f3bb670..8230f534a 100644 --- a/src/mtg/save_results.jl +++ b/src/mtg/save_results.jl @@ -124,7 +124,7 @@ function pre_allocate_outputs(statuses, statuses_template, reverse_multiscale_ma outs_[i] = [] end else - for i in keys(outs) # i = "Plant" + for i in keys(outs) # i = :Plant i isa Symbol || error("Output scale keys must be `Symbol`, got `$(typeof(i))` for key `$(repr(i))`.") @assert isa(outs[i], Tuple{Vararg{Symbol}}) """Outputs for scale $i should be a tuple of symbols, *e.g.* `$i => (:a, :b)`, found `$i => $(outs[i])` instead.""" outs_[i] = [outs[i]...] @@ -165,7 +165,7 @@ function pre_allocate_outputs(statuses, statuses_template, reverse_multiscale_ma end # Checking that variables in outputs exist in the statuses, and adding the :node variable: - for (organ, vars) in outs_ # organ = "Leaf"; vars = outs_[organ] + for (organ, vars) in outs_ # organ = :Leaf; vars = outs_[organ] if length(statuses[organ]) == 0 # The organ is not found in the mtg, we return an info and get along (it might be created during the simulation): check && @info "You required outputs for organ $organ, but this organ is not found in the provided MTG at this point." diff --git a/src/processes/model_initialisation.jl b/src/processes/model_initialisation.jl index b5a3a6358..7c999d903 100755 --- a/src/processes/model_initialisation.jl +++ b/src/processes/model_initialisation.jl @@ -53,12 +53,12 @@ using PlantSimEngine using PlantSimEngine.Examples mapping = ModelMapping( - "Leaf" => ModelMapping( + :Leaf => ModelMapping( process1=Process1Model(1.0), process2=Process2Model(), process3=Process3Model() ), - "Internode" => ModelMapping( + :Internode => ModelMapping( process1=Process1Model(1.0), ) ) @@ -117,7 +117,7 @@ end function to_initialize(m::T) where {T<:Dict{Symbol,ModelMapping}} toinit = Dict{Symbol,NamedTuple}() for (key, value) in m - # key = "Leaf"; value = m[key] + # key = :Leaf; value = m[key] toinit_ = to_initialize(value) if length(toinit_) > 0 @@ -183,7 +183,7 @@ models = Dict( process2=Process2Model(), process3=Process3Model() ), - :InterNode => ModelMapping( + :Internode => ModelMapping( process1=Process1Model(1.0), ) )