Skip to content

Commit 0827567

Browse files
authored
Merge pull request #127 from VirtualPlantLab/remove-modellist-multi-object-handling
Remove multi-object parallelisation
2 parents b39d5ce + 5936e5e commit 0827567

1 file changed

Lines changed: 75 additions & 104 deletions

File tree

src/run.jl

Lines changed: 75 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -124,32 +124,18 @@ function run!(
124124
executor=ThreadedEx()
125125
) where {T<:Union{AbstractArray,AbstractDict},A}
126126

127-
obj_parallelizable = all([object_parallelizable(dep(obj)) for obj in collect(values(object))])
128-
# Check if the simulation can be parallelized over objects:
129-
if !obj_parallelizable && executor != SequentialEx()
130-
is_obj_parallel = Set{AbstractModel}()
131-
for obj_par in [which_object_parallelizable(dep(obj)) for obj in collect(values(object))]
132-
for mod in obj_par[findall(x -> x.second.second == false, obj_par)]
133-
push!(is_obj_parallel, mod.second.first)
134-
end
135-
end
136-
137-
mods_not_parallel = join(is_obj_parallel, "; ")
138-
139-
check && @warn string(
140-
"A parallel executor was provided (`executor=$(executor)`) but some models cannot be run in parallel over objects: $mods_not_parallel. ",
141-
"The simulation will be run sequentially. Use `executor=SequentialEx()` to remove this warning."
127+
if executor != SequentialEx()
128+
@warn string(
129+
"Parallelisation over objects was removed, (but may be reintroduced in the future). Parallelisation will only occur over timesteps."
142130
) maxlog = 1
143-
executor_obj = SequentialEx()
144-
else
145-
executor_obj = executor
146131
end
147132

148-
@floop executor_obj for obj in collect(values(object))
133+
for obj in collect(values(object))
149134
run!(obj, meteo, constants, extra, check=check, executor=executor)
150135
end
151136
end
152137

138+
153139
# 2- one object, one time-step
154140
function run!(
155141
::SingletonAlike,
@@ -160,10 +146,11 @@ function run!(
160146
extra=nothing;
161147
check=true
162148
)
163-
run!(object, dep(object), 1, status(object, 1), meteo, constants, extra)
149+
run!(object, Weather[meteo], constants, extra; check)
164150
end
165151

166152
# 3- one object, one meteo time-step, several status time-steps (rare case but possible)
153+
# Also occurs when meteo is nothing
167154
function run!(
168155
::TableAlike,
169156
::SingletonAlike,
@@ -199,11 +186,16 @@ function run!(
199186
# the variables the user provided for all time-steps.
200187
for (i, row) in enumerate(sim_rows)
201188
i > 1 && propagate_values!(sim_rows[i-1], row, object.vars_not_propagated)
202-
run!(object, dep_graph, i, row, meteo, constants, extra)
203-
end
189+
roots = collect(dep_graph.roots)
190+
for (process, node) in roots
191+
run_node!(object, node, i, row, meteo, constants, extra)
192+
end end
204193
else
205194
@floop executor for (i, row) in enumerate(sim_rows)
206-
run!(object, dep_graph, i, row, meteo, constants, extra)
195+
local roots = collect(dep_graph.roots)
196+
for (process, node) in roots
197+
run_node!(object, node, i, row, meteo, constants, extra)
198+
end
207199
end
208200
end
209201
end
@@ -248,14 +240,21 @@ function run!(
248240
# Not parallelizable over time-steps, it means some values depend on the previous value.
249241
# In this case we propagate the values of the variables from one time-step to the other, except for
250242
# the variables the user provided for all time-steps.
243+
roots = collect(dep_graph.roots)
244+
251245
for (i, meteo_i) in enumerate(meteo_rows)
252246
i > 1 && propagate_values!(object[i-1], object[i], object.vars_not_propagated)
253-
run!(object, dep_graph, i, object[i], meteo_i, constants, extra)
247+
for (process, node) in roots
248+
run_node!(object, node, i, object[i], meteo_i, constants, extra)
249+
end
254250
end
255251
else
256252
# Computing time-steps in parallel:
257253
@floop executor for (i, meteo_i) in enumerate(meteo_rows)
258-
run!(object, dep_graph, i, object[i], meteo_i, constants, extra)
254+
local roots = collect(dep_graph.roots)
255+
for (process, node) in roots
256+
run_node!(object, node, i, object[i], meteo_i, constants, extra)
257+
end
259258
end
260259
end
261260
end
@@ -276,25 +275,14 @@ function run!(
276275
obj_parallelizable = all([object_parallelizable(graph) for graph in dep_graphs])
277276

278277
# Check if the simulation can be parallelized over objects:
279-
if !obj_parallelizable && executor != SequentialEx()
280-
is_obj_parallel = Set{AbstractModel}()
281-
for graph in dep_graphs
282-
obj_par = which_object_parallelizable(graph)
283-
for mod in obj_par[findall(x -> x.second.second == false, obj_par)]
284-
push!(is_obj_parallel, mod.second.first)
285-
end
286-
end
287-
288-
mods_not_parallel = join(is_obj_parallel, "; ")
289-
290-
check && @warn string(
291-
"A parallel executor was provided (`executor=$(executor)`) but some models cannot be run in parallel over objects: $mods_not_parallel. ",
292-
"The simulation will be run sequentially. Use `executor=SequentialEx()` to remove this warning."
278+
if executor != SequentialEx()
279+
@warn string(
280+
"Parallelisation over objects was removed, (but may be reintroduced in the future). Parallelisation will only occur over timesteps."
293281
) maxlog = 1
294-
executor = SequentialEx()
295282
end
296283
# Each object:
297-
@floop executor for (i, obj) in enumerate(collect(values(object)))
284+
for (i, obj) in enumerate(collect(values(object)))
285+
298286
if check
299287
# Check if the meteo data and the status have the same length (or length 1)
300288
check_dimensions(obj, meteo)
@@ -307,13 +295,49 @@ function run!(
307295
end
308296
end
309297

310-
run!(obj, dep_graphs[i], 1, status(obj)[1], meteo, constants, extra)
298+
roots_i = collect(dep_graphs[i].roots)
299+
for (process_i, node_i) in roots_i
300+
run_node!(obj, node_i, 1, status(obj)[1], meteo, constants, extra)
301+
end
302+
end
303+
end
304+
305+
306+
307+
# for each dependency node in the graph (always one time-step, one object), actual workhorse:
308+
function run_node!(
309+
object::T,
310+
node::SoftDependencyNode,
311+
i, # time-step to index into the dependency node (to know if the model has been called already)
312+
st,
313+
meteo,
314+
constants,
315+
extra
316+
) where {T<:ModelList}
317+
318+
# Check if all the parents have been called before the child:
319+
if !AbstractTrees.isroot(node) && any([p.simulation_id[i] <= node.simulation_id[i] for p in node.parent])
320+
# If not, this node should be called via another parent
321+
return nothing
322+
end
323+
324+
# Actual call to the model:
325+
run!(node.value, object.models, st, meteo, constants, extra)
326+
node.simulation_id[i] += 1 # increment the simulation id, to know if the model has been called already
327+
328+
# Recursively visit the children (soft dependencies only, hard dependencies are handled by the model itself):
329+
for child in node.children
330+
#! check if we can run this safely in a @floop loop. I would say no,
331+
#! because we are running a parallel computation above already, modifying the node.simulation_id,
332+
#! which is not thread-safe.
333+
run_node!(object, child, i, st, meteo, constants, extra)
311334
end
312335
end
313336

314-
# 6- Compatibility with MTG:
315337

316-
# 6.1: if we pass an MTG and a mapping, then we use them to compute a GraphSimulation object
338+
# Compatibility with MTG:
339+
340+
# If we pass an MTG and a mapping, then we use them to compute a GraphSimulation object
317341
# that we use with the first method in this file.
318342
function run!(
319343
object::MultiScaleTreeGraph.Node,
@@ -341,7 +365,6 @@ function run!(
341365
return sim
342366
end
343367

344-
# 6.2: if we pass a TreeAlike object (e.g. a GraphSimulation):
345368
function run!(
346369
::TreeAlike,
347370
::SingletonAlike,
@@ -352,16 +375,9 @@ function run!(
352375
check=true,
353376
executor=ThreadedEx()
354377
)
355-
models = get_models(object)
356-
357-
# Run the simulation of each soft-coupled model in the dependency graph:
358-
# Note: hard-coupled processes handle themselves already
359-
@floop executor for (process_key, dependency_node) in collect(dep(object).roots)
360-
run!(object, dependency_node, 1, models, meteo, constants, object, check, executor)
361-
end
378+
run!(object, Weather[meteo], constants, extra, check, executor)
362379
end
363380

364-
# 6.2 bis, over several time-steps
365381
function run!(
366382
::TreeAlike,
367383
::TableAlike,
@@ -379,65 +395,20 @@ function run!(
379395

380396
!isnothing(extra) && error("Extra parameters are not allowed for the simulation of an MTG (already used for statuses).")
381397

382-
# Note: The object is not thread safe here, because we write all meteo time-steps into the same Status (for each node)
383-
# This is because the number of nodes is usually higher than the number of cores anyway, so we don't gain much by parallelizing over
384-
# meteo time-steps in addition. This way we also reduce the memory footprint that can grow very large if we have many time-steps.
385398
for (i, meteo_i) in enumerate(Tables.rows(meteo))
386-
# In parallel over dependency root, i.e. for independant computations:
387-
@floop executor for (process_key, dependency_node) in collect(dep_graph.roots)
399+
roots = collect(dep_graph.roots)
400+
for (process_key, dependency_node) in roots
388401
# Note: parallelization over objects is handled by the run! method below
389-
run!(object, dependency_node, i, models, meteo_i, constants, object, check, executor)
402+
run_node_multiscale!(object, dependency_node, i, models, meteo_i, constants, object, check, executor)
390403
end
391404
# At the end of the time-step, we save the results of the simulation in the object:
392405
save_results!(object, i)
393406
end
394407
end
395408

396-
#! Actual calls to the model:
397-
# Running the simulation on the dependency graph (always one time-step, one object):
398-
function run!(object::T, dep_graph::DependencyGraph{Dict{Symbol,N}}, i, st, meteo, constants, extra; executor=ThreadedEx()) where {
399-
T<:ModelList,
400-
N<:Union{HardDependencyNode,SoftDependencyNode}
401-
}
402-
# Run the simulation of each soft-coupled model in the dependency graph:
403-
# Note: hard-coupled processes handle themselves already
404-
@floop executor for (process, node) in collect(dep_graph.roots)
405-
run!(object, node, i, st, meteo, constants, extra)
406-
end
407-
end
408-
409-
# for each dependency node in the graph (always one time-step, one object), actual workhorse:
410-
function run!(
411-
object::T,
412-
node::SoftDependencyNode,
413-
i, # time-step to index into the dependency node (to know if the model has been called already)
414-
st,
415-
meteo,
416-
constants,
417-
extra
418-
) where {T<:ModelList}
419-
420-
# Check if all the parents have been called before the child:
421-
if !AbstractTrees.isroot(node) && any([p.simulation_id[i] <= node.simulation_id[i] for p in node.parent])
422-
# If not, this node should be called via another parent
423-
return nothing
424-
end
425-
426-
# Actual call to the model:
427-
run!(node.value, object.models, st, meteo, constants, extra)
428-
node.simulation_id[i] += 1 # increment the simulation id, to know if the model has been called already
429-
430-
# Recursively visit the children (soft dependencies only, hard dependencies are handled by the model itself):
431-
for child in node.children
432-
#! check if we can run this safely in a @floop loop. I would say no,
433-
#! because we are running a parallel computation above already, modifying the node.simulation_id,
434-
#! which is not thread-safe.
435-
run!(object, child, i, st, meteo, constants, extra)
436-
end
437-
end
438409

439410
# For a tree-alike object:
440-
function run!(
411+
function run_node_multiscale!(
441412
object::T,
442413
node::SoftDependencyNode,
443414
i, # time-step to index into the dependency node (to know if the model has been called already)
@@ -469,7 +440,7 @@ function run!(
469440
executor = SequentialEx()
470441
end
471442

472-
@floop executor for st in node_statuses # for each node status at the current scale (potentially in parallel over nodes)
443+
for st in node_statuses # for each node status at the current scale (potentially in parallel over nodes)
473444
# Actual call to the model:
474445
run!(node.value, models_at_scale, st, meteo, constants, extra)
475446
end
@@ -481,6 +452,6 @@ function run!(
481452
#! check if we can run this safely in a @floop loop. I would say no,
482453
#! because we are running a parallel computation above already, modifying the node.simulation_id,
483454
#! which is not thread-safe yet.
484-
run!(object, child, i, models, meteo, constants, extra, check, executor)
455+
run_node_multiscale!(object, child, i, models, meteo, constants, extra, check, executor)
485456
end
486457
end

0 commit comments

Comments
 (0)