5Create the split-mode PNG of the tutorial 35_offline-load-balancing.ipynb
7The picture contrasts Peano's two split modes. Its left column sketches how
8a top-down and a bottom-up split hand over cells of a small one-dimensional
9spacetree. Its right column shows what the two modes make out of the
10tutorial's offline decomposition: The script computes the decomposition,
11builds the tutorial's Euler setup twice, once with top-down and once with
12bottom-up splits, runs both with four threads, and reads the subdomains from
13the patch files. Call the script from anywhere:
15 python3 tutorials/exahype2/illustrations/35_offline-load-balancing-split-modes.py
17All builds and outputs go into a temporary directory (or --work-directory).
18The script needs matplotlib, but no ParaView.
31import matplotlib.pyplot
as plt
32from matplotlib.colors
import ListedColormap
33from matplotlib.patches
import Patch, Rectangle
35Here = os.path.dirname(os.path.abspath(__file__))
38_specification = importlib.util.spec_from_file_location(
39 "offline_load_balancing_illustrations",
40 os.path.join(Here,
"35_offline-load-balancing.py"),
42offline_load_balancing = importlib.util.module_from_spec(_specification)
43_specification.loader.exec_module(offline_load_balancing)
44domain_decomposition = offline_load_balancing.domain_decomposition
47import toolbox.loadbalancing.api.output.Hardcoded
as Hardcoded
48import toolbox.loadbalancing.api.strategies.Decomposition
as Decomposition
49import toolbox.loadbalancing.api.strategies.SplitTrees
as SplitTrees
50import toolbox.loadbalancing.api.strategies.SFC
as SFC
52SplitModesPicture = os.path.join(Here,
"35_offline-load-balancing-split-modes.png")
54TreeColours = [
"#4c72b0",
"#dd8452",
"#55a868",
"#c44e52"]
61 """! Arguments of the hardcoded load balancing with the given split modes"""
66 peano_tree=tree, number_of_subtrees=domain_decomposition.NumberOfThreads
69 destination_ranks = Hardcoded.assign_trees_to_ranks_round_Robin(
70 number_of_subtrees=tree.number_of_subtrees, max_ranks=1
72 configuration, mode = Hardcoded.create_constructor_initialisation_string(
73 tree, destination_ranks, modes=modes
81 Subdomain of each fine grid cell, read from the first snapshot's patch files
83 The patch files hold one file per tree, but neither the file names nor the
84 order in which the trees write their files tell us the tree number. We
85 only know that tree 0 is the one tree which holds the cells along the
86 periodic boundary. The result therefore holds 0 for tree 0 and 1, 2, ...
87 for the other trees, numbered by the position of their first cell along
88 the space-filling curve. These numbers are not necessarily Peano's tree
89 numbers, as Peano runs through the curve forth and back in alternating
93 curve_position = np.zeros((CellsPerAxis, CellsPerAxis), dtype=int)
94 for position
in range(CellsPerAxis * CellsPerAxis):
96 curve_position[y, x] = position
99 for tree
in range(domain_decomposition.NumberOfThreads):
100 file_name = os.path.join(
101 directory,
"solution-EulerFV-{}.peano-patch-file".format(tree)
103 with open(file_name)
as patch_file:
104 text = patch_file.read()
106 for match
in re.finditer(
r"begin patch\s+offset\s+(\S+)\s+(\S+)", text):
109 (float(match.group(1)) - DomainOffset) / DomainSize * CellsPerAxis
114 (float(match.group(2)) - DomainOffset) / DomainSize * CellsPerAxis
118 cells_per_file.append(cells)
120 def holds_boundary_cell(cells):
121 return any(min(y, x) == 0
or max(y, x) == CellsPerAxis - 1
for (y, x)
in cells)
123 def first_curve_position(cells):
124 return min(curve_position[y, x]
for (y, x)
in cells)
126 tree_zero = [cells
for cells
in cells_per_file
if holds_boundary_cell(cells)]
127 assert len(tree_zero) == 1,
"expected exactly one tree along the periodic boundary"
129 [cells
for cells
in cells_per_file
if not holds_boundary_cell(cells)],
130 key=first_curve_position,
133 owner = -np.ones((CellsPerAxis, CellsPerAxis), dtype=int)
134 for subdomain, cells
in enumerate(tree_zero + others):
136 owner[y, x] = subdomain
141 """! Cells per tree as Peano reports them in the last load balancing statistics of run.log"""
142 with open(os.path.join(directory,
"run.log"))
as log_file:
143 statistics = [line
for line
in log_file
if "dumpStatistics" in line]
144 return [int(count)
for count
in re.findall(
r"#\d+:(\d+)/", statistics[-1])]
150 Draw a one-dimensional spacetree with three levels
152 owners holds the owner per level, i.e. a list with 1, 3 and 9 entries.
155 for level, level_owners
in enumerate(owners):
156 width = 9 / len(level_owners)
157 for i, owner
in enumerate(level_owners):
163 facecolor=TreeColours[owner],
171 xytext=(-0.1, -2 + 0.35),
172 arrowprops=dict(arrowstyle=
"->", color=
"black", lw=0.8),
174 axis.set_xlim(-0.3, 9.3)
175 axis.set_ylim(-2.9, 0.9)
177 axis.set_title(title, fontsize=10)
178 axis.text(4.5, -2.75, caption, ha=
"center", va=
"center", fontsize=8.5)
182 """! Draw the subdomains of a run together with Peano's cell counts per tree"""
187 cmap=ListedColormap(TreeColours),
190 extent=(0, CellsPerAxis, 0, CellsPerAxis),
192 for k
in range(0, CellsPerAxis + 1, 3):
193 width = 1.0
if k % 9 == 0
else 0.3
194 axis.axhline(k, color=
"black", lw=width)
195 axis.axvline(k, color=
"black", lw=width)
198 title +
"\ncells on tree 0, 1, 2, 3: " +
" / ".join(str(c)
for c
in counts),
205def render(top_down_directory, bottom_up_directory, picture=SplitModesPicture):
206 figure, axes = plt.subplots(
207 2, 2, figsize=(9.5, 8.2), gridspec_kw={
"width_ratios": [1.25, 1]}
211 "Top-down: tree 0 hands 4 cells to tree 1",
212 [[0], [1, 1, 0], [1, 1, 1, 1, 1, 1, 0, 0, 0]],
213 "Whole refined cells move while cells are still owed:\n"
214 "the second coarse cell moves although only 1 cell is owed,\n"
215 "so tree 1 gets 6 instead of 4 fine cells",
219 "Bottom-up: tree 0 hands 4 cells to tree 1",
220 [[0], [1, 0, 0], [1, 1, 1, 1, 0, 0, 0, 0, 0]],
221 "Fine cells move one by one along the curve, and a coarse\n"
222 "cell follows once all of its children have moved:\n"
223 "tree 1 gets exactly 4 fine cells",
225 draw_subdomains(axes[0][1], top_down_directory,
"Tutorial run, top-down")
226 draw_subdomains(axes[1][1], bottom_up_directory,
"Tutorial run, bottom-up")
229 Patch(color=TreeColours[0], label=
"tree 0"),
230 Patch(color=TreeColours[1], label=
""),
231 Patch(color=TreeColours[2], label=
""),
233 color=TreeColours[3],
234 label=
"trees split off from tree 0 (tree 1 in the sketches)",
243 plt.tight_layout(rect=(0, 0.04, 1, 1))
244 plt.savefig(picture, dpi=130)
247if __name__ ==
"__main__":
248 parser = argparse.ArgumentParser(
249 description=
"Create the split-mode PNG of the tutorial 35_offline-load-balancing.ipynb"
254 help=
"Directory for builds and outputs (default: temporary directory)",
258 default=domain_decomposition.PeanoRoot,
259 help=
"Peano installation with the compiled libraries (default: repository holding this script)",
261 args = parser.parse_args()
263 work_directory = args.work_directory
or tempfile.mkdtemp(
264 prefix=
"35_offline-load-balancing-split-modes-"
266 print(
"build and run in {}".format(work_directory))
268 for modes
in [Hardcoded.TopDown, Hardcoded.BottomUp]:
269 directories[modes] = os.path.join(work_directory, modes)
270 os.makedirs(directories[modes], exist_ok=
True)
271 domain_decomposition.build_and_run(
273 os.path.abspath(args.peano_root),
274 tutorial=
"35_offline-load-balancing",
275 namespace=
"offlineloadbalancing",
276 load_balancer=
"toolbox::loadbalancing::strategies::Hardcoded",
279 render(directories[Hardcoded.TopDown], directories[Hardcoded.BottomUp])
280 print(
"wrote {}".format(SplitModesPicture))
offline_decomposition(modes)
Arguments of the hardcoded load balancing with the given split modes.
render(top_down_directory, bottom_up_directory, picture=SplitModesPicture)
draw_subdomains(axis, directory, title)
Draw the subdomains of a run together with Peano's cell counts per tree.
draw_tree_sketch(axis, title, owners, caption)
Draw a one-dimensional spacetree with three levels.
read_cells_per_tree(directory)
Cells per tree as Peano reports them in the last load balancing statistics of run....
read_subdomains(directory)
Subdomain of each fine grid cell, read from the first snapshot's patch files.
equidistant_splitting(Tree peano_tree, int number_of_subtrees, verbose=False)
RegularGridGenerator(name=None, max_level=None, dimensions=None)
Generate an artificial regular grid with uniform refinement for testing purposes.
tuple[int, int] Peano_to_Cartesian_2d(int peano_index, int size)
Inverse of Cartesian_to_Peano_2d.
tuple[str, str] split_tree(Tree peano_tree, decomposition, str traversal="bfs_finest_first", bool enable_propagate_bottom_up=False, bool enable_optimize_hierarchy=False, bool require_all_children_for_hierarchy_optimization=True, list[bool]|None periodic_BC=None, bool verbose=False)
Split a tree using a staged load-balancing workflow.