Peano
Loading...
Searching...
No Matches
Hardcoded.py
Go to the documentation of this file.
1# This file is part of the Peano load balancing toolbox. For conditions of
2# distribution and use, please see the copyright notice at www.peano-framework.org
3from typing import TYPE_CHECKING
4
5if TYPE_CHECKING:
6 from toolbox.loadbalancing.api.Tree import Tree
7
8
9TopDown = "top-down"
10BottomUp = "bottom-up"
11
12_CppModes = {
13 TopDown: "peano4::SplitInstruction::Mode::AggressiveTopDown",
14 BottomUp: "peano4::SplitInstruction::Mode::BottomUp",
15}
16
17
18def assign_trees_to_ranks_round_Robin(
19 number_of_subtrees: int,
20 max_ranks: int,
21):
22 """!
23
24 Take the trees and assign them to the ranks in a round-robin fashion
25
26 Round robin is the only assignment that the offline load balancing
27 supports, as Peano numbers its trees round robin, too: Tree t always
28 lives on rank t%R for R ranks. See _validate_round_robin() and the
29 documentation of toolbox::loadbalancing::strategies::Hardcoded.
30
31 Note that we implicitly assume that the first tree remains on rank 0,
32 so we really only assign number_of_subtrees-1 entries.
33
34 """
35 result = []
36 for i in range(1, number_of_subtrees):
37 result.append(i % max_ranks)
38 return result
39
40
42 parent_trees,
43 subpartition_sizes,
44 destination_ranks,
45):
46 """!
47 Turn the enumerated splits into the C++ initialiser lists of Hardcoded
48
49 Entry k of each list describes the kth split.
50
51 @param parent_trees: [int]
52 Tree which splits in the kth split.
53
54 @param subpartition_sizes: [int]
55 Number of cells that the kth split hands over.
56
57 @param destination_ranks: [int]
58 Rank which receives the new tree of the kth split.
59 """
60 assert len(parent_trees) == len(subpartition_sizes)
61 assert len(destination_ranks) == len(subpartition_sizes)
62
63 def initialiser_list(values):
64 return "{" + ", ".join(str(value) for value in values) + "}"
65
66 return (
67 initialiser_list(parent_trees)
68 + ", "
69 + initialiser_list(subpartition_sizes)
70 + ", "
71 + initialiser_list(destination_ranks)
72 )
73
74
75def _markers_from_flags(bottom_up_flags):
76 """!
77 Translate the per-split flags of extract_split_instructions() into markers
78
79 @param bottom_up_flags: [bool]
80 True means that the corresponding split is a bottom-up split.
81 """
82 return [BottomUp if bottom_up else TopDown for bottom_up in bottom_up_flags]
83
84
85def _normalise_markers(modes, number_of_splits):
86 """!
87 Turn the user's modes argument into one marker per split
88
89 @param modes: str or [str]
90 Either a single marker which holds for all splits, or a sequence with
91 one marker per split. Each marker is TopDown ("top-down") or BottomUp
92 ("bottom-up").
93
94 @param number_of_splits: int
95 Number of splits that the markers have to describe.
96 """
97 if isinstance(modes, str):
98 modes = [modes] * number_of_splits
99
100 modes = list(modes)
101 if len(modes) != number_of_splits:
102 raise ValueError(
103 "got {} split mode(s), but the decomposition requires {} split(s)".format(
104 len(modes), number_of_splits
105 )
106 )
107 for mode in modes:
108 if mode not in _CppModes:
109 raise ValueError(
110 "unknown split mode '{}'. Use '{}' or '{}'".format(
111 mode, TopDown, BottomUp
112 )
113 )
114 return modes
115
116
117def _format_modes(markers):
118 """!
119 Turn the markers into the C++ mode argument of Hardcoded
120
121 The result is a single mode if all splits share the same mode, and an
122 initialiser list with one mode per split otherwise.
123
124 @param markers: [str]
125 One marker per split.
126 """
127 if not markers:
128 return _CppModes[TopDown]
129 if len(set(markers)) == 1:
130 return _CppModes[markers[0]]
131 return "{" + ", ".join(_CppModes[marker] for marker in markers) + "}"
132
133
134def _normalise_destination_ranks(destination_ranks, subtrees):
135 """!
136 Normalise destination rank input to one rank per split instruction.
137
138 Accepted input formats:
139 1. Per-instruction ranks: len(destination_ranks) == len(subtrees)
140 2. Per-subtree ranks (excluding subtree 0): len(destination_ranks) == max(subtrees)
141 The rank for subtree s>0 is taken from destination_ranks[s-1] and repeated
142 for every occurrence of s in subtrees.
143 """
144 if len(destination_ranks) == len(subtrees):
145 return destination_ranks
146
147 max_subtree_id = max(subtrees, default=0)
148 if len(destination_ranks) == max_subtree_id:
149 return [destination_ranks[subtree_id - 1] for subtree_id in subtrees]
150
151 raise ValueError(
152 "destination_ranks ({}) must either match the number of split instructions ({}) "
153 "or provide one rank per subtree excluding subtree 0 ({})".format(
154 len(destination_ranks), len(subtrees), max_subtree_id
155 )
156 )
157
158
159def _validate_round_robin(subtrees, destination_ranks, number_of_ranks=None):
160 """!
161 Ensure that the subtrees are assigned to the ranks round robin
162
163 Peano's tree numbers encode the rank which hosts a tree:
164 peano4::parallel::Node::getId() gives the nth tree on rank r the number
165 n*R+r for R ranks, and peano4::parallel::Node::getRank() hence maps tree
166 number t onto rank t%R. The offline plan refers to the trees through its
167 subtree numbers, i.e. a split of subtree s is meant to split the tree
168 with number s in Peano. This only holds if subtree s lives on rank s%R,
169 which is exactly what assign_trees_to_ranks_round_Robin() yields.
170
171 @param subtrees: [int]
172 Subtree created by the kth split.
173
174 @param destination_ranks: [int]
175 Rank which receives the subtree of the kth split.
176
177 @param number_of_ranks: int or None
178 Number of ranks R. None derives R from the largest destination rank.
179 """
180 if number_of_ranks is None:
181 number_of_ranks = max(destination_ranks, default=0) + 1
182 for subtree, rank in zip(subtrees, destination_ranks):
183 if rank != subtree % number_of_ranks:
184 raise ValueError(
185 "subtree {} is assigned to rank {}, but Peano can only host a tree with number {} on rank {} "
186 "for {} rank(s). Assign the trees to the ranks round robin, e.g. through "
187 "assign_trees_to_ranks_round_Robin()".format(
188 subtree, rank, subtree, subtree % number_of_ranks, number_of_ranks
189 )
190 )
191
192
193def create_constructor_initialisation_string(
194 peano_tree: "Tree",
195 destination_ranks: list[int],
196 verbose: bool = False,
197 modes=None,
198 number_of_ranks: int | None = None,
199) -> tuple[str, str]:
200 """!
201 Construct the constructor arguments for an instance of Hardcoded
202
203 The routine extracts the enumerated splits from the split tree, i.e.
204 a sequence of splits where the kth split reads "tree t hands n cells
205 over to a new tree on rank r and uses mode m to select these cells".
206 toolbox::loadbalancing::strategies::Hardcoded decides itself when it
207 issues the kth split.
208
209 By default, the mode of each split is inferred (see image below):
210 - `AggressiveTopDown`: all subtrees assigned to rank k start on the same level l.
211 - `BottomUp`: subtrees assigned to rank k start on different levels.
212
213 @image html src/toolbox/loadbalancing/api/output/create_constructor_initialisation_string.png
214
215 You can overwrite the inferred modes through modes.
216
217 @param peano_tree: toolbox.loadbalancing.api.Tree.Tree
218 Tree after the split, i.e. after SplitTrees.split_tree().
219
220 @param destination_ranks: [int]
221 Either one rank per split or one rank per subtree excluding subtree 0.
222
223 @param verbose: bool
224 Print the extracted splits.
225
226 @param modes: None, str or [str]
227 None infers the mode of each split. Otherwise, pass a single marker
228 TopDown ("top-down") or BottomUp ("bottom-up") which holds for all
229 splits, or a sequence with one marker per split.
230
231 @param number_of_ranks: int or None
232 Number of MPI ranks. None derives it from the largest destination
233 rank. See _validate_round_robin() for why we need it.
234
235 @return `(hardcoded_config, hardcoded_mode)` where hardcoded_config is the
236 complete constructor argument string and hardcoded_mode is the mode
237 argument within this string.
238 """
239 from toolbox.loadbalancing.api.strategies.SplitTrees import (
240 extract_split_instructions,
241 )
242
243 (
244 parent_trees,
245 subpartition_sizes,
246 subtrees,
247 bottom_up_flags,
248 ) = extract_split_instructions(peano_tree)
249
250 if modes is None:
251 markers = _markers_from_flags(bottom_up_flags)
252 else:
253 markers = _normalise_markers(modes, len(subtrees))
254 hardcoded_mode = _format_modes(markers)
255
256 destination_ranks = _normalise_destination_ranks(destination_ranks, subtrees)
257 _validate_round_robin(subtrees, destination_ranks, number_of_ranks)
258
259 if verbose:
260 print("Extracted hardcoded parameters:")
261 for k in range(len(subtrees)):
262 print(
263 " split {}: tree {} hands {} cells over to tree {} on rank {}, {}".format(
264 k,
265 parent_trees[k],
266 subpartition_sizes[k],
267 subtrees[k],
268 destination_ranks[k],
269 markers[k],
270 )
271 )
272
273 hardcoded_config = (
274 "{"
276 parent_trees,
277 subpartition_sizes,
278 destination_ranks,
279 )
280 + ", "
281 + hardcoded_mode
282 + "}"
283 )
284 return hardcoded_config, hardcoded_mode
_normalise_markers(modes, number_of_splits)
Turn the user's modes argument into one marker per split.
Definition Hardcoded.py:85
_format_modes(markers)
Turn the markers into the C++ mode argument of Hardcoded.
Definition Hardcoded.py:117
_format_initialisation_lists(parent_trees, subpartition_sizes, destination_ranks)
Turn the enumerated splits into the C++ initialiser lists of Hardcoded.
Definition Hardcoded.py:45
_validate_round_robin(subtrees, destination_ranks, number_of_ranks=None)
Ensure that the subtrees are assigned to the ranks round robin.
Definition Hardcoded.py:159
_markers_from_flags(bottom_up_flags)
Translate the per-split flags of extract_split_instructions() into markers.
Definition Hardcoded.py:75
_normalise_destination_ranks(destination_ranks, subtrees)
Normalise destination rank input to one rank per split instruction.
Definition Hardcoded.py:134