Skip to content

Commit 5ddefe1

Browse files
authored
Merge pull request #169 from VirtualPlantLab/graphsim-initialisation-changes
Streamline graphsim initialisation
2 parents 0bc9303 + efdace7 commit 5ddefe1

6 files changed

Lines changed: 52 additions & 18 deletions

File tree

src/dependencies/dependencies.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,15 @@ function dep(mapping::Dict{String,T}; verbose::Bool=true) where {T}
105105
# only the nodes that are not hard-dependency of other nodes. These nodes are taken as roots for the soft-dependency graph because they
106106
# are independant.
107107
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+
108112
# Second step, compute the soft-dependency graph between SoftDependencyNodes computed in the first step. To do so, we search the
109113
# inputs of each process into the outputs of the other processes, at the same scale, but also between scales. Then we keep only the
110114
# 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
111115
# of the nodes that they depend on.
112-
dep_graph = soft_dependencies_multiscale(soft_dep_graphs_roots, mapping, hard_dep_dict)
116+
dep_graph = soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_multiscale_mapping, hard_dep_dict)
113117
# During the building of the soft-dependency graph, we identified the inputs and outputs of each dependency node,
114118
# and also defined **inputs** as MappedVar if they are multiscale, i.e. if they take their values from another scale.
115119
# What we are missing is that we need to also define **outputs** as multiscale if they are needed by another scale.

src/dependencies/soft_dependencies.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,8 @@ function soft_dependencies(d::DependencyGraph{Dict{Symbol,HardDependencyNode}},
139139
end
140140

141141
# For multiscale mapping:
142-
function soft_dependencies_multiscale(soft_dep_graphs_roots::DependencyGraph{Dict{String,Any}}, mapping::Dict{String,A}, hard_dep_dict::Dict{Pair{Symbol,String},HardDependencyNode}) where {A<:Any}
143-
mapped_vars = mapped_variables(mapping, soft_dep_graphs_roots, verbose=false)
144-
rev_mapping = reverse_mapping(mapped_vars, all=false)
145-
142+
function soft_dependencies_multiscale(soft_dep_graphs_roots::DependencyGraph{Dict{String,Any}}, reverse_multiscale_mapping, hard_dep_dict::Dict{Pair{Symbol,String},HardDependencyNode})
143+
146144
independant_process_root = Dict{Pair{String,Symbol},SoftDependencyNode}()
147145
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]
148146
for (proc, i) in soft_dep_graph
@@ -158,7 +156,7 @@ function soft_dependencies_multiscale(soft_dep_graphs_roots::DependencyGraph{Dic
158156
# NB: if a node is already a hard dependency of the node, it cannot be a soft dependency
159157

160158
# Check if the process has soft dependencies at other scales:
161-
soft_deps_multiscale = search_inputs_in_multiscale_output(proc, organ, ins, soft_dep_graphs_roots.roots, rev_mapping, hard_dependencies_from_other_scale)
159+
soft_deps_multiscale = search_inputs_in_multiscale_output(proc, organ, ins, soft_dep_graphs_roots.roots, reverse_multiscale_mapping, hard_dependencies_from_other_scale)
162160
# Example output: "Soil" => Dict(:soil_water=>[:soil_water_content]), which means that the variable :soil_water_content
163161
# is computed by the process :soil_water at the scale "Soil".
164162

src/mtg/initialisation.jl

Lines changed: 26 additions & 4 deletions
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)); type_promotion=nothing, verbose=false, check=true)
24+
function init_statuses(mtg, mapping, dependency_graph; type_promotion=nothing, verbose=false, check=true)
2525
# We compute the variables mapping for each scale:
2626
mapped_vars = mapped_variables(mapping, dependency_graph, verbose=verbose)
2727

@@ -314,10 +314,32 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
314314
@assert false "Error : Mapping status at $organ_with_vector level contains a vector. If this was intentional, call the function generate_models_from_status_vectors on your mapping before calling run!. And bear in mind this is not meant for production. If this wasn't intentional, then it's likely an issue on the mapping definition, or an unusual model."
315315
end
316316

317+
soft_dep_graphs_roots, hard_dep_dict = hard_dependencies(mapping; verbose=false)
318+
317319
# Get the status of each node by node type, pre-initialised considering multi-scale variables:
318320
statuses, status_templates, reverse_multiscale_mapping, vars_need_init =
319-
init_statuses(mtg, mapping, first(hard_dependencies(mapping; verbose=false)); type_promotion=type_promotion, verbose=verbose, check=check)
320-
321+
init_statuses(mtg, mapping, soft_dep_graphs_roots; type_promotion=type_promotion, verbose=verbose, check=check)
322+
323+
# First step, get the hard-dependency graph and create SoftDependencyNodes for each hard-dependency root. In other word, we want
324+
# only the nodes that are not hard-dependency of other nodes. These nodes are taken as roots for the soft-dependency graph because they
325+
# are independant.
326+
# Second step, compute the soft-dependency graph between SoftDependencyNodes computed in the first step. To do so, we search the
327+
# inputs of each process into the outputs of the other processes, at the same scale, but also between scales. Then we keep only the
328+
# 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
329+
# of the nodes that they depend on.
330+
dep_graph = soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_multiscale_mapping, hard_dep_dict)
331+
# During the building of the soft-dependency graph, we identified the inputs and outputs of each dependency node,
332+
# and also defined **inputs** as MappedVar if they are multiscale, i.e. if they take their values from another scale.
333+
# What we are missing is that we need to also define **outputs** as multiscale if they are needed by another scale.
334+
335+
# Checking that the graph is acyclic:
336+
iscyclic, cycle_vec = is_graph_cyclic(dep_graph; warn=false)
337+
# Note: we could do that in `soft_dependencies_multiscale` but we prefer to keep the function as simple as possible, and
338+
# usable on its own.
339+
340+
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.")
341+
# Third step, we identify which
342+
321343
# Print an info if models are declared for nodes that don't exist in the MTG:
322344
if check && any(x -> length(last(x)) == 0, statuses)
323345
model_no_node = join(findall(x -> length(x) == 0, statuses), ", ")
@@ -329,5 +351,5 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
329351
outputs = pre_allocate_outputs(statuses, status_templates, reverse_multiscale_mapping, vars_need_init, outputs, nsteps, type_promotion=type_promotion, check=check)
330352

331353
outputs_index = Dict{String, Int}(s => 1 for s in keys(outputs))
332-
return (; mtg, statuses, status_templates, reverse_multiscale_mapping, vars_need_init, dependency_graph=dep(mapping, verbose=verbose), models, outputs, outputs_index)
354+
return (; mtg, statuses, status_templates, reverse_multiscale_mapping, vars_need_init, dependency_graph=dep_graph, models, outputs, outputs_index)
333355
end

src/run.jl

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

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

test/test-mtg-multiscale-cyclic-dep.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ out_vars = Dict(
4848
@test_throws "Cyclic dependency detected in the graph. Cycle:" dep(mapping_cyclic)
4949

5050
soft_dep_graphs_roots, hard_dep_dict = PlantSimEngine.hard_dependencies(mapping_cyclic)
51-
dep_graph = PlantSimEngine.soft_dependencies_multiscale(soft_dep_graphs_roots, mapping_cyclic, hard_dep_dict)
51+
52+
mapped_vars = PlantSimEngine.mapped_variables(mapping_cyclic, soft_dep_graphs_roots, verbose=false)
53+
reverse_mapping_cyclic = PlantSimEngine.reverse_mapping(mapped_vars, all=false)
54+
55+
dep_graph = PlantSimEngine.soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_mapping_cyclic, hard_dep_dict)
5256
iscyclic, cycle_vec = PlantSimEngine.is_graph_cyclic(dep_graph; warn=false)
5357

5458
@test iscyclic
@@ -97,7 +101,10 @@ end
97101

98102
soft_dep_graphs_roots, hard_dep_dict = PlantSimEngine.hard_dependencies(mapping_nocyclic)
99103
# soft_dep_graphs_roots.roots["Leaf"].inputs
100-
dep_graph = PlantSimEngine.soft_dependencies_multiscale(soft_dep_graphs_roots, mapping_nocyclic, hard_dep_dict)
104+
mapped_vars = PlantSimEngine.mapped_variables(mapping_nocyclic, soft_dep_graphs_roots, verbose=false)
105+
reverse_mapping_nocyclic = PlantSimEngine.reverse_mapping(mapped_vars, all=false)
106+
107+
dep_graph = PlantSimEngine.soft_dependencies_multiscale(soft_dep_graphs_roots, reverse_mapping_nocyclic, hard_dep_dict)
101108
iscyclic, cycle_vec = PlantSimEngine.is_graph_cyclic(dep_graph; warn=false)
102109

103110
@test !iscyclic

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)