Skip to content

Commit adb9e2d

Browse files
committed
Merge remote-tracking branch 'origin/main' into Mapping-rework-experimentation
2 parents dc36bf0 + 9a4d9d4 commit adb9e2d

5 files changed

Lines changed: 59 additions & 7 deletions

File tree

.github/workflows/Benchmarks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
matrix:
1616
version:
1717
- "1"
18+
- "1.10"
1819
os:
1920
- ubuntu-latest
2021
arch:

src/dependencies/dependencies.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,31 @@ end
9999
function dep(m::NamedTuple, nsteps=1; verbose::Bool=true)
100100
dep(nsteps; verbose=verbose, m...)
101101
end
102+
103+
function dep(mapping::Dict{String,T}; verbose::Bool=true) where {T}
104+
# First step, get the hard-dependency graph and create SoftDependencyNodes for each hard-dependency root. In other word, we want
105+
# only the nodes that are not hard-dependency of other nodes. These nodes are taken as roots for the soft-dependency graph because they
106+
# are independant.
107+
soft_dep_graphs_roots, hard_dep_dict = hard_dependencies(mapping; verbose=verbose)
108+
109+
mapped_vars = mapped_variables(mapping, soft_dep_graphs_roots, verbose=false)
110+
reverse_multiscale_mapping = reverse_mapping(mapped_vars, all=false)
111+
112+
# Second step, compute the soft-dependency graph between SoftDependencyNodes computed in the first step. To do so, we search the
113+
# inputs of each process into the outputs of the other processes, at the same scale, but also between scales. Then we keep only the
114+
# nodes that have no soft-dependencies, and we set them as root nodes of the soft-dependency graph. The other nodes are set as children
115+
# of the nodes that they depend on.
116+
dep_graph = soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_multiscale_mapping, hard_dep_dict)
117+
# During the building of the soft-dependency graph, we identified the inputs and outputs of each dependency node,
118+
# and also defined **inputs** as MappedVar if they are multiscale, i.e. if they take their values from another scale.
119+
# What we are missing is that we need to also define **outputs** as multiscale if they are needed by another scale.
120+
121+
# Checking that the graph is acyclic:
122+
iscyclic, cycle_vec = is_graph_cyclic(dep_graph; warn=false)
123+
# Note: we could do that in `soft_dependencies_multiscale` but we prefer to keep the function as simple as possible, and
124+
# usable on its own.
125+
126+
iscyclic && error("Cyclic dependency detected in the graph. Cycle: \n $(print_cycle(cycle_vec)) \n You can break the cycle using the `PreviousTimeStep` variable in the mapping.")
127+
# Third step, we identify which
128+
return dep_graph
129+
end

src/mtg/initialisation.jl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ a dictionary of variables that need to be initialised or computed by other model
2121
2222
`(;statuses, status_templates, reverse_multiscale_mapping, vars_need_init, nodes_with_models)`
2323
"""
24-
function init_statuses(mtg, mapping, dependency_graph#=first(hard_dependencies(mapping; verbose=false, orchestrator=Orchestrator()))=#; type_promotion=nothing, verbose=false, check=true, orchestrator=Orchestrator())
24+
function init_statuses(mtg, mapping, dependency_graph=first(hard_dependencies(mapping; verbose=false, orchestrator=Orchestrator())); type_promotion=nothing, verbose=false, check=true, orchestrator=Orchestrator())
2525
# We compute the variables mapping for each scale:
2626
mapped_vars = mapped_variables(mapping, dependency_graph, verbose=verbose,orchestrator=orchestrator)
2727

@@ -340,6 +340,26 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
340340
init_statuses(mtg, mapping, soft_dep_graph_roots; type_promotion=type_promotion, verbose=verbose, check=check, orchestrator=orchestrator)
341341

342342

343+
# First step, get the hard-dependency graph and create SoftDependencyNodes for each hard-dependency root. In other word, we want
344+
# only the nodes that are not hard-dependency of other nodes. These nodes are taken as roots for the soft-dependency graph because they
345+
# are independant.
346+
# Second step, compute the soft-dependency graph between SoftDependencyNodes computed in the first step. To do so, we search the
347+
# inputs of each process into the outputs of the other processes, at the same scale, but also between scales. Then we keep only the
348+
# nodes that have no soft-dependencies, and we set them as root nodes of the soft-dependency graph. The other nodes are set as children
349+
# of the nodes that they depend on.
350+
dep_graph = soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_multiscale_mapping, hard_dep_dict)
351+
# During the building of the soft-dependency graph, we identified the inputs and outputs of each dependency node,
352+
# and also defined **inputs** as MappedVar if they are multiscale, i.e. if they take their values from another scale.
353+
# What we are missing is that we need to also define **outputs** as multiscale if they are needed by another scale.
354+
355+
# Checking that the graph is acyclic:
356+
iscyclic, cycle_vec = is_graph_cyclic(dep_graph; warn=false)
357+
# Note: we could do that in `soft_dependencies_multiscale` but we prefer to keep the function as simple as possible, and
358+
# usable on its own.
359+
360+
iscyclic && error("Cyclic dependency detected in the graph. Cycle: \n $(print_cycle(cycle_vec)) \n You can break the cycle using the `PreviousTimeStep` variable in the mapping.")
361+
# Third step, we identify which
362+
343363
# Print an info if models are declared for nodes that don't exist in the MTG:
344364
if check && any(x -> length(last(x)) == 0, statuses)
345365
model_no_node = join(findall(x -> length(x) == 0, statuses), ", ")

src/run.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ function run!(
400400
executor=ThreadedEx()
401401
)
402402

403-
dep_graph = dep(object)
403+
dep_graph = object.dependency_graph
404404
models = get_models(object)
405405
# st = status(object)
406406

test/test-mtg-multiscale.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ end
127127
end
128128

129129
@testset "Status initialisation" begin
130-
@test_throws "Variable `carbon_biomass` is not computed by any model, not initialised by the user in the status, and not found in the MTG at scale Internode (checked for MTG node 4)." PlantSimEngine.init_statuses(mtg, mapping_1)
131-
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1)
130+
# Samuel : internal function, suppressing REPL display errors
131+
hard_dep_graph = first(PlantSimEngine.hard_dependencies(mapping_1; verbose=false));
132+
@test_throws "Variable `carbon_biomass` is not computed by any model, not initialised by the user in the status, and not found in the MTG at scale Internode (checked for MTG node 4)." PlantSimEngine.init_statuses(mtg, mapping_1, hard_dep_graph)
133+
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1, hard_dep_graph)
132134

133135
@test collect(keys(organs_statuses)) == ["Soil", "Internode", "Plant", "Leaf"]
134136
# Check that the soil_water_content is linked between the soil and the leaves:
@@ -148,7 +150,7 @@ end
148150
@test organs_statuses["Leaf"][1][:carbon_demand] == -Inf
149151

150152
# Testing with a different type:
151-
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1, type_promotion=Dict(Float64 => Float32, Vector{Float64} => Vector{Float32}))
153+
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1, hard_dep_graph, type_promotion=Dict(Float64 => Float32, Vector{Float64} => Vector{Float32}))
152154

153155
@test isa(organs_statuses["Plant"][1][:carbon_assimilation], PlantSimEngine.RefVector{Float32})
154156
@test isa(organs_statuses["Plant"][1][:carbon_allocation], PlantSimEngine.RefVector{Float32})
@@ -168,7 +170,8 @@ end
168170
type_promotion = nothing
169171
nsteps = 2
170172
dependency_graph = dep(mapping_1)
171-
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1, first(PlantSimEngine.hard_dependencies(mapping_1; verbose=false)); type_promotion=type_promotion)
173+
hard_dep_graph = first(PlantSimEngine.hard_dependencies(mapping_1; verbose=false))
174+
organs_statuses, others = PlantSimEngine.init_statuses(mtg_init, mapping_1, hard_dep_graph; type_promotion=type_promotion)
172175

173176
@test collect(keys(organs_statuses)) == ["Soil", "Internode", "Plant", "Leaf"]
174177
@test collect(keys(organs_statuses["Soil"][1])) == [:node, :soil_water_content]
@@ -194,7 +197,7 @@ end
194197
@test PlantSimEngine.to_initialize(mapping_1, mtg) == Dict("Internode" => [:carbon_biomass], "Leaf" => [:carbon_biomass])
195198
@test PlantSimEngine.to_initialize(mapping_1, mtg_init) == Dict{String,Symbol}()
196199

197-
statuses, status_templates, reverse_multiscale_mapping, vars_need_init = PlantSimEngine.init_statuses(mtg_init, mapping_1)
200+
statuses, status_templates, reverse_multiscale_mapping, vars_need_init = PlantSimEngine.init_statuses(mtg_init, mapping_1, hard_dep_graph)
198201
@test collect(keys(statuses)) == ["Soil", "Internode", "Plant", "Leaf"]
199202

200203
@test length(statuses["Internode"]) == length(statuses["Leaf"]) == 2

0 commit comments

Comments
 (0)