Peano
Loading...
Searching...
No Matches
ReportCompiledStepSequence.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3from typing import List
4
5import peano4.api
7
8
9def report_compiled_step_sequence(
10 steps: List["peano4.api.solversteps.Step"], title: str = "Compiled task graph"
11) -> None:
12 """!
13
14 Report the result of compiling a task graph into a linear sequence of
15 Observers, through peano4.api.Logger() - the compiled-sequence
16 counterpart to peano4.api.taskgraph.Graph.log_as_text()/visualise(),
17 which report on the *pre*-compilation Graph/Task structure instead.
18
19 Meant to be called right after a compiler (@see
20 peano4.api.taskgraph.compiler.Sequential.translate) has derived its
21 Observer sequence. Note that
22 peano4.api.taskgraph.compiler.PropagateUseStatements.propagate_use_statements()
23 runs as postprocessing after the compiler, i.e. any use_cell()/
24 use_face()/use_vertex() declarations it adds later on are not part of
25 this report.
26
27 Each step's action sets are listed as an enumerated list in the order
28 in which they are invoked ("<number>. do / <name>"). Action sets that
29 may run in parallel share the same number and are told apart by
30 letters ("1.a do / <name>", "1.b do / <name>", ...). The numbering
31 follows the task groups of the generated observer - @ref
32 peano4.api.taskgraph.compiler.ReportCompiledStepSequence._action_set_lines().
33
34 Dispatches on peano4.api.Logger().verbosity exactly like
35 Graph.log_as_text()/visualise() do: LoggerVerbosity.TEXT logs a plain
36 listing of the sequence and, for each step, its action sets plus the
37 cell/face/vertex submodels it uses;
38 LoggerVerbosity.VISUALISATION draws a flow chart in the same UML state
39 diagram style as Graph.visualise() - a top-to-bottom chain of states
40 (one per step, in sequence order, since a compiled sequence has no
41 dependency structure left to lay out - it already is the order),
42 listing each step's action sets as enumerated "<number>. do / <name>"
43 lines and the
44 cell/face/vertex submodels it uses as "cell / <name> [<qualifier>]",
45 "face / <name> [<qualifier>]", "vertex / <name> [<qualifier>]" lines -
46 <qualifier> being the originating Task's Task.DataAccess
47 (READ_AND_DISCARD, WRITE, READ_AND_WRITE or TEMPORARY), taken from the
48 step's cell_data_access/face_data_access/vertex_data_access (@see
49 peano4.api.taskgraph.compiler.Sequential.translate(), which populates
50 them; a submodel missing from there - i.e. a Step built some other
51 way than through that compiler - is simply listed without a
52 qualifier) - self-loops for steps with has_to_repeat set, and
53 initial/final pseudostates. Neither happens (this call is then a
54 no-op) under any other verbosity.
55
56 @param steps: [peano4.api.solversteps.Step]
57 The compiled sequence to report on, exactly as the compiler is about
58 to return it.
59
60 @param title: str
61 Used as-is as the text report's heading / the flow chart's title.
62
63 """
64 if peano4.api.Logger().is_visualisation:
65 _visualise_compiled_step_sequence(steps, title)
66 if peano4.api.Logger().is_text:
68
69
70def _submodel_label(submodel, access) -> str:
71 """!
72 "<name>", or "<name> [<qualifier>]" if access (one of a Step's
73 cell_data_access/face_data_access/vertex_data_access dicts) has a
74 Task.DataAccess qualifier on file for this submodel.
75 """
76 qualifier = access.get(submodel)
77 return f"{submodel.name} [{qualifier.name}]" if qualifier is not None else submodel.name
78
79
80def _action_set_lines(step) -> List[str]:
81 """!
82 Enumerated list of the step's action sets in invocation order, i.e.
83 "<number>. do / <name>". The numbers enumerate the task groups which
84 the generated observer invokes one after the other
85 (@ref peano4.api.solversteps.SynchroniseActionSets.get_task_groups()).
86 The action sets of a group with more than one action set run in
87 parallel. They share the number and get a letter each, i.e.
88 "1.a do / <name>", "1.b do / <name>", ...
89 """
90 lines = []
91 for number, group in enumerate(peano4.api.solversteps.get_task_groups(step.action_sets), start=1):
92 for letter_index, action_set in enumerate(group):
93 label = f"{number}.{chr(ord('a') + letter_index)}" if len(group) > 1 else f"{number}."
94 lines.append(f"{label} do / {action_set.get_action_set_name()}")
95 return lines
96
97
98def _log_compiled_step_sequence_as_text(steps, title: str) -> None:
99 if not steps:
100 peano4.api.Logger().log(f"Compiled task graph '{title}' is empty.")
101 return
102
103 lines = [f"Compiled task graph '{title}':"]
104 for step in steps:
105 lines.append(f"- {step.name}")
106 for line in _action_set_lines(step):
107 lines.append(f" {line}")
108 for kind, submodels, access in (
109 ("cell", step.cell_data, step.cell_data_access),
110 ("face", step.face_data, step.face_data_access),
111 ("vertex", step.vertex_data, step.vertex_data_access),
112 ):
113 if submodels:
114 lines.append(
115 f" {kind} / " + ", ".join(_submodel_label(submodel, access) for submodel in submodels)
116 )
117 if step.has_to_repeat:
118 lines.append(f" repeats while: {step.has_to_repeat}")
119
120 peano4.api.Logger().log("\n".join(lines))
121
122
123def _visualise_compiled_step_sequence(steps, title: str) -> None:
124 """!
125 Simplified sibling of peano4.api.taskgraph.Graph.visualise(): the same
126 UML-state-diagram look (rounded-rectangle states, an internal-activity
127 compartment listing "do / <name>" per action set plus "cell / <name>",
128 "face / <name>", "vertex / <name>" per kind of mesh data the step uses,
129 self-loops for repeating steps, initial/final pseudostates), but laid
130 out as a single top-to-bottom chain following steps' list order, since a
131 compiled sequence - unlike a Graph - has no dependency structure left to
132 derive layers from; it already is the order.
133 """
134 import matplotlib.pyplot as plt
135 from matplotlib.patches import Circle, FancyArrowPatch, FancyBboxPatch
136 from matplotlib.path import Path
137
138 if not steps:
139 peano4.api.Logger().log(f"Compiled task graph '{title}' is empty - nothing to visualise.")
140 return
141
142 HEADER_HEIGHT = 0.52
143 ACTIVITY_LINE_HEIGHT = 0.22
144 BODY_PADDING = 0.14
145 MIN_WIDTH = 1.7
146 CHAR_WIDTH = 0.10
147 VERTICAL_GAP = 0.6
148 SELF_LOOP_WIDTH = 0.4
149 SELF_LOOP_LABEL_CHAR_WIDTH = 0.065
150 PSEUDOSTATE_OFFSET = 0.6
151 INCH_PER_UNIT = 0.62
152 PADDING = 0.5
153
154 def activity_lines(step):
155 lines = _action_set_lines(step)
156 for kind, submodels, access in (
157 ("cell", step.cell_data, step.cell_data_access),
158 ("face", step.face_data, step.face_data_access),
159 ("vertex", step.vertex_data, step.vertex_data_access),
160 ):
161 if submodels:
162 lines.append(f"{kind} / " + ", ".join(_submodel_label(submodel, access) for submodel in submodels))
163 return lines
164
165 width_of_step, height_of_step = {}, {}
166 for step in steps:
167 lines = activity_lines(step)
168 longest_line = max([len(step.name)] + [len(line) for line in lines])
169 width_of_step[step] = max(MIN_WIDTH, CHAR_WIDTH * longest_line + 0.6)
170 height_of_step[step] = HEADER_HEIGHT + (
171 BODY_PADDING + len(lines) * ACTIVITY_LINE_HEIGHT + 0.5 * BODY_PADDING if lines else 0
172 )
173
174 # One step per row, stacked top to bottom and centred around x=0
175 position_of_step = {}
176 row_y = 0.0
177 for step in steps:
178 position_of_step[step] = (0.0, row_y - height_of_step[step] / 2)
179 row_y -= height_of_step[step] + VERTICAL_GAP
180 bottom_y = row_y + VERTICAL_GAP
181
182 def self_loop_extent(step) -> float:
183 if not step.has_to_repeat:
184 return 0.0
185 return SELF_LOOP_WIDTH + 0.06 + SELF_LOOP_LABEL_CHAR_WIDTH * len(step.has_to_repeat)
186
187 min_x = -max(width_of_step.values()) / 2 - PADDING
188 max_x = max(width_of_step[step] / 2 + self_loop_extent(step) for step in steps) + PADDING
189 min_y = bottom_y - PSEUDOSTATE_OFFSET - 0.11 - PADDING
190 max_y = PSEUDOSTATE_OFFSET + 0.09 + PADDING
191
192 LEFT_CHROME, RIGHT_CHROME, TOP_CHROME, BOTTOM_CHROME = 0.1, 0.1, 0.4, 0.15
193 data_width_inches = (max_x - min_x) * INCH_PER_UNIT
194 data_height_inches = (max_y - min_y) * INCH_PER_UNIT
195 figure_width = data_width_inches + LEFT_CHROME + RIGHT_CHROME
196 figure_height = data_height_inches + TOP_CHROME + BOTTOM_CHROME
197
198 figure = plt.figure(figsize=(figure_width, figure_height))
199 axes = figure.add_axes(
200 (
201 LEFT_CHROME / figure_width,
202 BOTTOM_CHROME / figure_height,
203 data_width_inches / figure_width,
204 data_height_inches / figure_height,
205 )
206 )
207 axes.set_xlim(min_x, max_x)
208 axes.set_ylim(min_y, max_y)
209
210 state_patch = {}
211 for step in steps:
212 x, y = position_of_step[step]
213 w, h = width_of_step[step], height_of_step[step]
214 lines = activity_lines(step)
215
216 box = FancyBboxPatch(
217 (x - w / 2, y - h / 2),
218 w,
219 h,
220 boxstyle="round,pad=0,rounding_size=0.09",
221 linewidth=1.3,
222 edgecolor="black",
223 facecolor="white",
224 zorder=2,
225 )
226 axes.add_patch(box)
227 state_patch[step] = box
228
229 header_center_y = y + h / 2 - HEADER_HEIGHT / 2 if lines else y
230 axes.annotate(
231 step.name,
232 (x, header_center_y),
233 ha="center", va="center", zorder=3, fontsize=7.5, fontweight="bold",
234 )
235
236 if lines:
237 separator_y = y + h / 2 - HEADER_HEIGHT
238 axes.plot(
239 [x - w / 2, x + w / 2], [separator_y, separator_y],
240 color="black", linewidth=0.8, zorder=3,
241 )
242 for index, line in enumerate(lines):
243 line_y = separator_y - BODY_PADDING - index * ACTIVITY_LINE_HEIGHT - 0.08
244 axes.annotate(
245 line,
246 (x - w / 2 + 0.12, line_y),
247 ha="left", va="center", zorder=3, fontsize=6.5, color="dimgray",
248 )
249
250 for i in range(len(steps) - 1):
251 axes.add_patch(
252 FancyArrowPatch(
253 position_of_step[steps[i]],
254 position_of_step[steps[i + 1]],
255 patchA=state_patch[steps[i]],
256 patchB=state_patch[steps[i + 1]],
257 arrowstyle="-|>",
258 mutation_scale=13,
259 color="black",
260 linewidth=1.1,
261 zorder=1,
262 )
263 )
264
265 for step in steps:
266 if step.has_to_repeat:
267 x, y = position_of_step[step]
268 w, h = width_of_step[step], height_of_step[step]
269 right_x = x + w / 2
270 upper_y, lower_y = y + 0.18 * h, y - 0.18 * h
271 path = Path(
272 [
273 (right_x, upper_y),
274 (right_x + SELF_LOOP_WIDTH, upper_y),
275 (right_x + SELF_LOOP_WIDTH, lower_y),
276 (right_x, lower_y),
277 ],
278 [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4],
279 )
280 axes.add_patch(
281 FancyArrowPatch(
282 path=path,
283 arrowstyle="-|>",
284 mutation_scale=12,
285 color="crimson",
286 linewidth=1.1,
287 zorder=2.5,
288 )
289 )
290 axes.annotate(
291 step.has_to_repeat,
292 (right_x + SELF_LOOP_WIDTH + 0.06, y),
293 ha="left", va="center", zorder=3, fontsize=6, style="italic", color="crimson",
294 )
295
296 first_pos = position_of_step[steps[0]]
297 initial_pos = (first_pos[0], PSEUDOSTATE_OFFSET)
298 initial_patch = Circle(initial_pos, 0.09, facecolor="black", edgecolor="black", zorder=3)
299 axes.add_patch(initial_patch)
300 axes.add_patch(
301 FancyArrowPatch(
302 initial_pos, first_pos,
303 patchA=initial_patch, patchB=state_patch[steps[0]],
304 arrowstyle="-|>", mutation_scale=13, color="black", linewidth=1.1, zorder=1,
305 )
306 )
307
308 last_pos = position_of_step[steps[-1]]
309 final_pos = (last_pos[0], bottom_y - PSEUDOSTATE_OFFSET)
310 final_outer = Circle(final_pos, 0.11, facecolor="white", edgecolor="black", linewidth=1.3, zorder=3)
311 axes.add_patch(final_outer)
312 axes.add_patch(Circle(final_pos, 0.05, facecolor="black", edgecolor="none", zorder=4))
313 axes.add_patch(
314 FancyArrowPatch(
315 last_pos, final_pos,
316 patchA=state_patch[steps[-1]], patchB=final_outer,
317 arrowstyle="-|>", mutation_scale=13, color="black", linewidth=1.1, zorder=1,
318 )
319 )
320
321 figure.suptitle(title, y=1.0 - 0.5 * TOP_CHROME / figure_height, fontsize=10)
322 axes.set_xticks([])
323 axes.set_yticks([])
324 axes.set_aspect("equal")
325 # Non-blocking - @see peano4.api.taskgraph.Graph.wait_for_visualisations(),
326 # which the caller invokes once after a whole group of compiled
327 # sequences (e.g. SetupSimulation/TimeStep/Plot) has been reported, to
328 # let them all appear side by side rather than one at a time.
329 plt.show(block=False)
Singleton logger for the Peano 4 Python API.
Definition Logger.py:37
The Python-side model of "what happens during one mesh sweep".
Definition __init__.py:1
List[str] _action_set_lines(step)
Enumerated list of the step's action sets in invocation order, i.e.
str _submodel_label(submodel, access)
"<name>", or "<name> [<qualifier>]" if access (one of a Step's cell_data_access/face_data_access/vert...