Peano
Loading...
Searching...
No Matches
13_euler-with-amr.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
3"""!
4
5Create the PNG illustrations of the tutorial 13_euler-with-amr.ipynb
6
7The script builds the tutorial's point explosion on a regular grid, runs
8it and writes all pictures into the directory of this script. It then
9builds the setup with the point-wise refinement criterion, runs it on the
10resulting adaptive mesh, and draws the mesh, the solution, a comparison
11with the regular mesh, plus the setup's task graph. Finally, it runs the
12dynamically adaptive setup, which also erases cells while it runs time
13steps, and draws its mesh adaptation task graph, the number of patches
14over time and the mesh before and after it changes. Finally, it runs the
15same dynamically adaptive setup with the gradient-based criterion and
16compares the density profiles, the meshes and the numbers of patches of
17the two criteria. The PDE terms are injected from Python, i.e. we do not
18need any hand-written C++ file. Call it from anywhere:
19
20 python3 tutorials/exahype2/illustrations/13_euler-with-amr.py
21
22Pass --gradient-only to create only the pictures of the gradient section.
23
24All builds and outputs go into a temporary directory (or --work-directory).
25
26"""
27import argparse
28import os
29import re
30import types
31import subprocess
32import sys
33import tempfile
34
35Here = os.path.dirname(os.path.abspath(__file__))
36PeanoRoot = os.path.abspath(os.path.join(Here, "..", "..", ".."))
37sys.path.insert(0, os.path.join(PeanoRoot, "src"))
38
39import matplotlib
40
41matplotlib.use("Agg")
42import matplotlib.patches
43import matplotlib.pyplot as plt
44import numpy as np
45
46import peano4.api
47import exahype2
49
50
51Flux = """
52 const double irho = 1.0 / Q[0];
53 constexpr double gamma = 1.4;
54 const double p = (gamma - 1.0) * (Q[3] - 0.5 * irho * (Q[1] * Q[1] + Q[2] * Q[2]));
55 switch (normal) {
56 case 0:
57 F[0] = Q[1];
58 F[1] = irho * Q[1] * Q[1] + p;
59 F[2] = irho * Q[1] * Q[2];
60 F[3] = irho * Q[1] * (Q[3] + p);
61 break;
62 case 1:
63 F[0] = Q[2];
64 F[1] = irho * Q[2] * Q[1];
65 F[2] = irho * Q[2] * Q[2] + p;
66 F[3] = irho * Q[2] * (Q[3] + p);
67 break;
68 }
69"""
70
71MaxEigenvalue = """
72 const double irho = 1.0 / Q[0];
73 constexpr double gamma = 1.4;
74 const double p = (gamma - 1.0) * (Q[3] - 0.5 * irho * (Q[1] * Q[1] + Q[2] * Q[2]));
75 const double c = std::sqrt(gamma * p * irho);
76 const double u = Q[1 + normal] * irho;
77 lambdaMax[0] = std::max(std::abs(u + c), std::abs(u - c));
78"""
79
80InitPointWisely = """
81 constexpr double gamma = 1.4;
82 constexpr double radius = 0.1;
83 const bool inside = x[0] * x[0] + x[1] * x[1] < radius * radius;
84 const double rho = inside ? 1.0 : 0.125;
85 const double p = inside ? 1.0 : 0.1;
86 Q[0] = rho;
87 Q[1] = 0.0;
88 Q[2] = 0.0;
89 Q[3] = p / (gamma - 1.0);
90"""
91
92VolumeSize = 0.005
93AdaptiveVolumeSize = 0.02
94AdaptiveMinVolumeSize = 0.002
95RefineErasePointwisely = """
96 if (Q[0] > 0.8) *refinementCommand = ::exahype2::RefinementCommand::Refine;
97"""
98DynamicRefineErasePointwisely = """
99 if (Q[0] > 0.8) *refinementCommand = ::exahype2::RefinementCommand::Refine;
100 else if (Q[0] < 0.1) *refinementCommand = ::exahype2::RefinementCommand::Erase;
101"""
102GradientRefinement = """
103 const double densityGradient = std::abs(gradient[0]);
104 if (densityGradient > 5.0) *refinementCommand = ::exahype2::RefinementCommand::Refine;
105 else if (densityGradient < 1.0) *refinementCommand = ::exahype2::RefinementCommand::Erase;
106"""
107EndTime = 0.2
108DynamicEndTime = 0.3
109TimeInBetweenPlots = 0.01
110PatchFile = "solution-EulerFV.peano-patch-file"
111
112FD4InitPointWisely = """
113 constexpr double gamma = 1.4;
114 constexpr double width = 0.05;
115 const double r2 = x[0] * x[0] + x[1] * x[1];
116 const double rho = 1.0 + 0.5 * std::exp(-r2 / (width * width));
117 const double p = std::pow(rho, gamma);
118 Q[0] = rho;
119 Q[1] = 0.0;
120 Q[2] = 0.0;
121 Q[3] = p / (gamma - 1.0);
122"""
123FD4PatchFile = "solution-EulerFD4.peano-patch-file"
124
125
126def create_solver(adaptive, dynamic=False, gradient=False):
127 """!
128
129 The tutorial's solver over a regular, an adaptive or a dynamically
130 adaptive mesh
131
132 With gradient, the solver refines and erases with the gradient-based
133 criterion instead of the point-wise one. The flag implies a dynamically
134 adaptive mesh.
135
136 """
137 solver = exahype2.api.Solver("EulerFV", 4, 0)
138 solver.add_user_solver_includes("#include <cmath>\n#include <algorithm>\n")
139 solver.add_pde_term(exahype2.api.Flux(implementation=Flux, stateless=False))
140 solver.add_pde_term(
141 exahype2.api.MaxEigenvalue(implementation=MaxEigenvalue, stateless=False)
142 )
143 solver.add_pde_term(
144 exahype2.api.InitPointWisely(implementation=InitPointWisely, stateless=False)
145 )
146 if adaptive and gradient:
147 solver.add_pde_term(
148 exahype2.api.GradientRefinement(implementation=GradientRefinement)
149 )
150 elif adaptive:
151 solver.add_pde_term(
153 implementation=DynamicRefineErasePointwisely
154 if dynamic
155 else RefineErasePointwisely
156 )
157 )
158 if adaptive:
159 solver.numerical_scheme = exahype2.api.fv.RusanovWithExplicitEuler(
160 solver,
161 9,
162 min_volume_size=AdaptiveMinVolumeSize,
163 max_volume_size=AdaptiveVolumeSize,
164 )
165 else:
166 solver.numerical_scheme = exahype2.api.fv.RusanovWithExplicitEuler(solver, 9)
167 if adaptive:
168 solver.grid_construction = (
170 solver,
171 volume_size=AdaptiveVolumeSize,
172 min_volume_size=AdaptiveMinVolumeSize,
173 )
174 )
175 else:
176 solver.grid_construction = exahype2.api.fv.SetupSimulationOverRegularGrid(
177 solver, volume_size=VolumeSize
178 )
179 solver.plotter = exahype2.api.fv.PlotAsVTKFile(
180 solver, first_plot_time_stamp=0.0, time_in_between_plots=TimeInBetweenPlots
181 )
182 return solver
183
184
185def build_and_run(directory, adaptive=False, dynamic=False, gradient=False):
186 """! Generate, compile and run the tutorial setup within directory"""
187 end_time = DynamicEndTime if dynamic or gradient else EndTime
188 project = exahype2.api.Project(
189 namespace=["tutorials", "exahype2", "euler"],
190 project_name="Euler",
191 directory=directory,
192 executable="euler",
193 )
194 solver = create_solver(adaptive, dynamic, gradient)
195 project.add_solver(solver)
196 project.set_global_simulation_parameters(
197 dimensions=2,
198 offset=[-0.5, -0.5],
199 size=[1.0, 1.0],
200 min_end_time=end_time,
201 max_end_time=end_time,
202 periodic_BC=[True, True, True],
203 )
204 project.set_Peano4_installation(PeanoRoot, peano4.api.output.CompileMode.RELEASE)
205 peano4_project = project.generate_Peano4_project()
206 peano4_project.generate()
207 subprocess.run(["make", "-j"], cwd=directory, check=True, stdout=subprocess.DEVNULL)
208 subprocess.run(["./euler"], cwd=directory, check=True, stdout=subprocess.DEVNULL)
209
210
211def build_and_run_fd4(directory, init_point_wisely):
212 """!
213
214 Generate, compile and run the fourth-order Finite Differences over the
215 regular mesh within directory
216
217 The run writes its output into directory/run.log, so we can parse the
218 time step sizes afterwards. A run of the point explosion breaks down
219 early, so we do not check its exit code.
220
221 """
222 project = exahype2.api.Project(
223 namespace=["tutorials", "exahype2", "euler"],
224 project_name="Euler",
225 directory=directory,
226 executable="euler",
227 )
228 solver = exahype2.api.Solver("EulerFD4", 4, 0)
229 solver.add_user_solver_includes("#include <cmath>\n#include <algorithm>\n")
230 solver.add_pde_term(exahype2.api.Flux(implementation=Flux, stateless=False))
231 solver.add_pde_term(
232 exahype2.api.MaxEigenvalue(implementation=MaxEigenvalue, stateless=False)
233 )
234 solver.add_pde_term(
235 exahype2.api.InitPointWisely(implementation=init_point_wisely, stateless=False)
236 )
238 solver, patch_size=9, rk_order=4
239 )
240 solver.grid_construction = exahype2.api.rkfd4.SetupSimulationOverRegularGrid(
241 solver, volume_size=VolumeSize
242 )
243 solver.plotter = exahype2.api.rkfd4.PlotAsVTKFile(
244 solver, first_plot_time_stamp=0.0, time_in_between_plots=TimeInBetweenPlots
245 )
246 project.add_solver(solver)
247 project.set_global_simulation_parameters(
248 dimensions=2,
249 offset=[-0.5, -0.5],
250 size=[1.0, 1.0],
251 min_end_time=EndTime,
252 max_end_time=EndTime,
253 periodic_BC=[True, True, True],
254 )
255 project.set_Peano4_installation(PeanoRoot, peano4.api.output.CompileMode.RELEASE)
256 peano4_project = project.generate_Peano4_project()
257 peano4_project.generate()
258 subprocess.run(["make", "-j"], cwd=directory, check=True, stdout=subprocess.DEVNULL)
259 with open(os.path.join(directory, "run.log"), "w") as log:
260 subprocess.run(["./euler"], cwd=directory, stdout=log, stderr=subprocess.STDOUT)
261
262
263def read_snapshot(directory, snapshot, patch_file=PatchFile):
264 """! Return the time stamp and all patches (offset, size, values) of a snapshot"""
266 os.path.join(directory, patch_file)
267 )
268 time_stamp, patch_files = snapshots[snapshot]
269 patches = [
270 p for f in patch_files for p in tarch.plotter.api.notebook.read_patches(f)
271 ]
272 return time_stamp, patches
273
274
275def number_of_snapshots(directory, patch_file=PatchFile):
276 return len(
277 tarch.plotter.api.notebook.read_snapshots(os.path.join(directory, patch_file))
278 )
279
280
281def density_along_x_axis(directory, snapshot, patch_file=PatchFile):
282 """! Return time stamp plus volume centres and densities along y=0, x>=0"""
283 time_stamp, patches = read_snapshot(directory, snapshot, patch_file)
284 x, rho = [], []
285 for offset, size, values in patches:
286 if offset[1] <= 0.0 < offset[1] + size[1]:
287 dof = values.shape[0]
288 h = size[0] / dof
289 row = int((0.0 - offset[1]) / (size[1] / dof))
290 for i in range(dof):
291 centre = offset[0] + (i + 0.5) * h
292 if centre >= 0.0:
293 x.append(centre)
294 rho.append(values[row, i, 0])
295 order = np.argsort(x)
296 return time_stamp, np.array(x)[order], np.array(rho)[order]
297
298
299def plot_mesh(directory):
300 """! Initial density plus the patch boundaries"""
301 file_name = os.path.join(directory, PatchFile)
302 ax = tarch.plotter.api.notebook.plot_snapshot(file_name, 0, 0, label="density")
303 _, patches = read_snapshot(directory, 0)
304 for offset, size, values in patches:
305 ax.add_patch(
306 matplotlib.patches.Rectangle(
307 offset, size[0], size[1], fill=False, edgecolor="white", linewidth=0.4
308 )
309 )
310 ax.set_title(
311 "Initial density\n{0}x{0} patches, {1}x{1} volumes per patch".format(
312 round(1.0 / patches[0][1][0]), patches[0][2].shape[0]
313 )
314 )
315 ax.figure.savefig(
316 os.path.join(Here, "13_euler-with-amr-mesh.png"), dpi=100, bbox_inches="tight"
317 )
318 plt.close(ax.figure)
319
320
321def plot_snapshots(directory):
322 """! Density at three time stamps side by side"""
323 file_name = os.path.join(directory, PatchFile)
324 snapshots = number_of_snapshots(directory)
325 fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))
326 for ax, snapshot in zip(
327 axes, [(snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1]
328 ):
330 file_name, snapshot, 0, label="density", ax=ax
331 )
332 fig.tight_layout()
333 fig.savefig(
334 os.path.join(Here, "13_euler-with-amr-snapshots.png"),
335 dpi=100,
336 bbox_inches="tight",
337 )
338 plt.close(fig)
339
340
341def plot_profile(directory):
342 """! Density along the positive x-axis at a few time stamps"""
343 fig, ax = plt.subplots(figsize=(7, 4))
344 snapshots = number_of_snapshots(directory)
345 for snapshot, colour in zip(
346 [0, (snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1],
347 ["C0", "C1", "C2", "C3"],
348 ):
349 t, x, rho = density_along_x_axis(directory, snapshot)
350 ax.plot(
351 x,
352 rho,
353 "o-",
354 color=colour,
355 markersize=2,
356 linewidth=0.8,
357 label="t={:.2f}".format(t),
358 )
359 ax.set_xlabel("distance r from the centre (along y=0)")
360 ax.set_ylabel("density")
361 ax.set_title("Density profile of the point explosion")
362 ax.legend()
363 fig.savefig(
364 os.path.join(Here, "13_euler-with-amr-profile.png"),
365 dpi=100,
366 bbox_inches="tight",
367 )
368 plt.close(fig)
369
370
371def plot_active_patches(directory):
372 """!
373
374 Highlight the patches which hold steep density gradients at the end
375
376 A patch counts as active if the largest density jump between two
377 neighbouring volumes within the patch exceeds 20% of the largest such
378 jump in the whole domain. This is the kind of quantity a refinement
379 criterion would look at.
380
381 """
382 file_name = os.path.join(directory, PatchFile)
383 snapshot = number_of_snapshots(directory) - 1
385 file_name, snapshot, 0, label="density"
386 )
387 t, patches = read_snapshot(directory, snapshot)
388
389 def max_jump(values):
390 rho = values[..., 0]
391 return max(
392 np.abs(np.diff(rho, axis=0)).max(), np.abs(np.diff(rho, axis=1)).max()
393 )
394
395 jumps = [max_jump(values) for _, _, values in patches]
396 threshold = 0.2 * max(jumps)
397 active = 0
398 for (offset, size, values), jump in zip(patches, jumps):
399 if jump > threshold:
400 active += 1
401 ax.add_patch(
402 matplotlib.patches.Rectangle(
403 offset,
404 size[0],
405 size[1],
406 facecolor=(1.0, 0.0, 0.0, 0.35),
407 edgecolor="red",
408 linewidth=0.5,
409 )
410 )
411 ax.set_title(
412 "t={:.2f}: {} of {} patches ({:.0f}%) hold steep gradients".format(
413 t, active, len(patches), 100.0 * active / len(patches)
414 )
415 )
416 ax.figure.savefig(
417 os.path.join(Here, "13_euler-with-amr-active-patches.png"),
418 dpi=100,
419 bbox_inches="tight",
420 )
421 plt.close(ax.figure)
422 print("{} of {} patches are active".format(active, len(patches)))
423
424
425def plot_adaptive_mesh(directory):
426 """! Initial density on the adaptive mesh plus the patch boundaries"""
427 file_name = os.path.join(directory, PatchFile)
428 ax = tarch.plotter.api.notebook.plot_snapshot(file_name, 0, 0, label="density")
429 _, patches = read_snapshot(directory, 0)
430 for offset, size, values in patches:
431 ax.add_patch(
432 matplotlib.patches.Rectangle(
433 offset, size[0], size[1], fill=False, edgecolor="white", linewidth=0.3
434 )
435 )
436 sizes = sorted({round(size[0], 6) for _, size, _ in patches}, reverse=True)
437 counts = [sum(1 for _, size, _ in patches if round(size[0], 6) == h) for h in sizes]
438 ax.set_title(
439 "Initial density on the adaptive mesh\n"
440 + ", ".join(
441 "{} patches of size 1/{}".format(count, round(1.0 / h))
442 for h, count in zip(sizes, counts)
443 ),
444 fontsize=9,
445 )
446 ax.figure.savefig(
447 os.path.join(Here, "13_euler-with-amr-adaptive-mesh.png"),
448 dpi=100,
449 bbox_inches="tight",
450 )
451 plt.close(ax.figure)
452 print("adaptive mesh: {}".format(dict(zip(sizes, counts))))
453
454
456 """! Density at three time stamps on the adaptive mesh plus patch boundaries"""
457 file_name = os.path.join(directory, PatchFile)
458 snapshots = number_of_snapshots(directory)
459 fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))
460 for ax, snapshot in zip(
461 axes, [(snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1]
462 ):
464 file_name, snapshot, 0, label="density", ax=ax
465 )
466 _, patches = read_snapshot(directory, snapshot)
467 for offset, size, values in patches:
468 ax.add_patch(
469 matplotlib.patches.Rectangle(
470 offset,
471 size[0],
472 size[1],
473 fill=False,
474 edgecolor="white",
475 linewidth=0.2,
476 )
477 )
478 fig.tight_layout()
479 fig.savefig(
480 os.path.join(Here, "13_euler-with-amr-adaptive-snapshots.png"),
481 dpi=100,
482 bbox_inches="tight",
483 )
484 plt.close(fig)
485
486
487def plot_profile_comparison(regular_directory, adaptive_directory):
488 """! Density along the positive x-axis: regular against adaptive mesh"""
489 fig, ax = plt.subplots(figsize=(7, 4))
490 snapshots = number_of_snapshots(regular_directory)
491 assert snapshots == number_of_snapshots(adaptive_directory)
492 for snapshot, colour in zip(
493 [(snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1],
494 ["C1", "C2", "C3"],
495 ):
496 t, x, rho = density_along_x_axis(regular_directory, snapshot)
497 ax.plot(
498 x,
499 rho,
500 "-",
501 color=colour,
502 linewidth=1.0,
503 label="regular, t={:.2f}".format(t),
504 )
505 t, x, rho = density_along_x_axis(adaptive_directory, snapshot)
506 ax.plot(
507 x,
508 rho,
509 "o",
510 color=colour,
511 markersize=1.5,
512 label="adaptive, t={:.2f}".format(t),
513 )
514 ax.set_xlabel("distance r from the centre (along y=0)")
515 ax.set_ylabel("density")
516 ax.set_title("Regular mesh (lines) against adaptive mesh (dots)")
517 ax.legend(fontsize=8)
518 fig.savefig(
519 os.path.join(Here, "13_euler-with-amr-profile-comparison.png"),
520 dpi=100,
521 bbox_inches="tight",
522 )
523 plt.close(fig)
524
525
526def print_conservation(directory):
527 """! Total mass and energy of the first and the last snapshot"""
528 for snapshot in [0, number_of_snapshots(directory) - 1]:
529 t, patches = read_snapshot(directory, snapshot)
530 mass = sum(
531 values[..., 0].sum() * (size[0] / values.shape[0]) ** 2
532 for _, size, values in patches
533 )
534 energy = sum(
535 values[..., 3].sum() * (size[0] / values.shape[0]) ** 2
536 for _, size, values in patches
537 )
538 print("t={:.3f}: mass={:.8f}, energy={:.8f}".format(t, mass, energy))
539
540
542 """! Construct the setup task graph with the refinement criterion and draw it"""
543 peano4.api.Logger().file_name = os.path.join(
544 tempfile.gettempdir(), "13-euler-with-amr-log.md"
545 )
546 with tempfile.TemporaryDirectory() as directory:
547 peano_project = peano4.api.Project(
548 ["illustration"], "illustration", directory=directory
549 )
550 solver = create_solver(adaptive=True)
551 exahype2_project = types.SimpleNamespace(periodic_BC=[True, True, True])
552 setup_simulation = peano4.api.taskgraph.Graph()
553 solver.grid_construction.construct_output(
554 exahype2_project, peano_project, setup_simulation
555 )
556 setup_simulation.visualise(
557 title="Setup task graph with point-wise refinement criterion"
558 )
559 output = os.path.join(Here, "13_euler-with-amr-setup-task-graph.png")
560 plt.gcf().savefig(output, dpi=90)
561 plt.close(plt.gcf())
562 print("Wrote", output)
563
564
565def patch_counts(directory):
566 """! Time stamps and numbers of patches of all snapshots"""
567 times, counts = [], []
568 for snapshot in range(number_of_snapshots(directory)):
569 t, patches = read_snapshot(directory, snapshot)
570 times.append(t)
571 counts.append(len(patches))
572 return times, counts
573
574
576 """! Number of patches per snapshot of the dynamically adaptive run"""
577 times, counts = patch_counts(directory)
578 fig, ax = plt.subplots(figsize=(7, 3.5))
579 ax.step(times, counts, where="post", marker="o", markersize=3)
580 ax.set_xlabel("time")
581 ax.set_ylabel("number of patches")
582 ax.set_title("Patches of the dynamically adaptive mesh")
583 fig.savefig(
584 os.path.join(Here, "13_euler-with-amr-dynamic-patch-count.png"),
585 dpi=100,
586 bbox_inches="tight",
587 )
588 plt.close(fig)
589 print("patch counts: {}".format(list(zip([round(t, 3) for t in times], counts))))
590
591
593 """! Density and patch boundaries right before the first mesh change and at the end"""
594 times, counts = patch_counts(directory)
595 changes = [i for i in range(1, len(counts)) if counts[i] != counts[i - 1]]
596 if not changes:
597 print("the dynamically adaptive mesh has not changed")
598 return
599 file_name = os.path.join(directory, PatchFile)
600 fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
601 for ax, snapshot in zip(axes, [changes[0] - 1, len(counts) - 1]):
603 file_name, snapshot, 0, label="density", ax=ax, clim=(0.0, 0.4)
604 )
605 _, patches = read_snapshot(directory, snapshot)
606 for offset, size, values in patches:
607 ax.add_patch(
608 matplotlib.patches.Rectangle(
609 offset,
610 size[0],
611 size[1],
612 fill=False,
613 edgecolor="white",
614 linewidth=0.3,
615 )
616 )
617 ax.set_xlim(-0.3, 0.3)
618 ax.set_ylim(-0.3, 0.3)
619 ax.set_title(
620 "t={:.2f}: {} patches".format(times[snapshot], counts[snapshot]),
621 fontsize=10,
622 )
623 fig.tight_layout()
624 fig.savefig(
625 os.path.join(Here, "13_euler-with-amr-dynamic-mesh-change.png"),
626 dpi=100,
627 bbox_inches="tight",
628 )
629 plt.close(fig)
630
631
632def gradient_snapshots(directory):
633 """!
634
635 Snapshots for the comparison of the point-wise and the gradient-based
636 criterion: t=0.1, 0.2 and 0.25. We stop before t=0.3, as the shock
637 then reaches the periodic boundary.
638
639 """
640 times, _ = patch_counts(directory)
641 return [
642 min(range(len(times)), key=lambda i: abs(times[i] - t))
643 for t in [0.1, 0.2, 0.25]
644 ]
645
646
647def plot_gradient_profile_comparison(pointwise_directory, gradient_directory):
648 """! Density along the positive x-axis: point-wise against gradient-based criterion"""
649 fig, ax = plt.subplots(figsize=(7, 4))
650 for snapshot, colour in zip(
651 gradient_snapshots(gradient_directory), ["C1", "C2", "C3"]
652 ):
653 t, x, rho = density_along_x_axis(pointwise_directory, snapshot)
654 ax.plot(
655 x,
656 rho,
657 "-",
658 color=colour,
659 linewidth=1.0,
660 label="point-wise, t={:.2f}".format(t),
661 )
662 t, x, rho = density_along_x_axis(gradient_directory, snapshot)
663 ax.plot(
664 x,
665 rho,
666 "o",
667 color=colour,
668 markersize=1.5,
669 label="gradient, t={:.2f}".format(t),
670 )
671 ax.set_xlabel("distance r from the centre (along y=0)")
672 ax.set_ylabel("density")
673 ax.set_title("Point-wise criterion (lines) against gradient criterion (dots)")
674 ax.legend(fontsize=8)
675 fig.savefig(
676 os.path.join(Here, "13_euler-with-amr-gradient-profile.png"),
677 dpi=100,
678 bbox_inches="tight",
679 )
680 plt.close(fig)
681
682
683def plot_gradient_patch_counts(pointwise_directory, gradient_directory):
684 """! Number of patches per snapshot of both dynamically adaptive runs"""
685 fig, ax = plt.subplots(figsize=(7, 3.5))
686 for directory, label in [
687 (pointwise_directory, "point-wise criterion"),
688 (gradient_directory, "gradient criterion"),
689 ]:
690 times, counts = patch_counts(directory)
691 ax.step(times, counts, where="post", marker="o", markersize=3, label=label)
692 print("{}: {}".format(label, list(zip([round(t, 3) for t in times], counts))))
693 ax.set_xlabel("time")
694 ax.set_ylabel("number of patches")
695 ax.set_title("Patches of the dynamically adaptive meshes")
696 ax.legend()
697 fig.savefig(
698 os.path.join(Here, "13_euler-with-amr-gradient-patch-count.png"),
699 dpi=100,
700 bbox_inches="tight",
701 )
702 plt.close(fig)
703
704
705def plot_gradient_mesh(pointwise_directory, gradient_directory):
706 """!
707
708 Density and patch boundaries of both runs at t=0.2
709
710 We zoom into the upper right quadrant, as the boundaries of the fine
711 patches would otherwise hide the solution.
712
713 """
714 snapshot = gradient_snapshots(gradient_directory)[1]
715 fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
716 for ax, directory, label in zip(
717 axes,
718 [pointwise_directory, gradient_directory],
719 ["point-wise criterion", "gradient criterion"],
720 ):
722 os.path.join(directory, PatchFile),
723 snapshot,
724 0,
725 label="density",
726 ax=ax,
727 clim=(0.0, 0.3),
728 )
729 t, patches = read_snapshot(directory, snapshot)
730 for offset, size, values in patches:
731 ax.add_patch(
732 matplotlib.patches.Rectangle(
733 offset,
734 size[0],
735 size[1],
736 fill=False,
737 edgecolor="white",
738 linewidth=0.4,
739 )
740 )
741 ax.set_xlim(0.0, 0.5)
742 ax.set_ylim(0.0, 0.5)
743 ax.set_title(
744 "{}, t={:.2f}: {} patches".format(label, t, len(patches)), fontsize=10
745 )
746 fig.tight_layout()
747 fig.savefig(
748 os.path.join(Here, "13_euler-with-amr-gradient-mesh.png"),
749 dpi=100,
750 bbox_inches="tight",
751 )
752 plt.close(fig)
753
754
755def create_gradient_pictures(dynamic_directory, gradient_directory, skip_run):
756 """!
757
758 Run the gradient-based criterion and compare it to the point-wise one
759
760 Expects the point-wise dynamically adaptive run within dynamic_directory.
761
762 """
763 if not skip_run:
764 print("build and run with gradient criterion in {}".format(gradient_directory))
765 build_and_run(gradient_directory, adaptive=True, gradient=True)
766 plot_gradient_profile_comparison(dynamic_directory, gradient_directory)
767 plot_gradient_patch_counts(dynamic_directory, gradient_directory)
768 plot_gradient_mesh(dynamic_directory, gradient_directory)
769 print_conservation(gradient_directory)
770
771
773 """! Time step sizes of the fourth-order Finite Differences for the point explosion"""
774 times, sizes = [], []
775 with open(os.path.join(directory, "run.log")) as log:
776 for line in log:
777 match = re.search(r"\bt=([0-9.eE+-]+), new dt=([0-9.eE+-]+)", line)
778 if match:
779 times.append(float(match.group(1)))
780 sizes.append(float(match.group(2)))
781 fig, ax = plt.subplots(figsize=(7, 3.5))
782 ax.semilogy(times, sizes, marker="o", markersize=2, linewidth=0.8)
783 ax.set_xlabel("time")
784 ax.set_ylabel("time step size")
785 ax.set_title("Fourth-order Finite Differences for the point explosion")
786 fig.savefig(
787 os.path.join(Here, "13_euler-with-amr-fd4-explosion-time-step-size.png"),
788 dpi=100,
789 bbox_inches="tight",
790 )
791 plt.close(fig)
792 print(
793 "FD4 explosion: {} time steps, last t={}, first dt={}, last dt={}".format(
794 len(times), times[-1], sizes[0], sizes[-1]
795 )
796 )
797
798
799def plot_fd4_snapshots(directory):
800 """! Density of the Gaussian pulse at three time stamps side by side"""
801 file_name = os.path.join(directory, FD4PatchFile)
802 snapshots = number_of_snapshots(directory, FD4PatchFile)
803 fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))
804 for ax, snapshot in zip(
805 axes, [(snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1]
806 ):
808 file_name, snapshot, 0, label="density", ax=ax
809 )
810 fig.tight_layout()
811 fig.savefig(
812 os.path.join(Here, "13_euler-with-amr-fd4-snapshots.png"),
813 dpi=100,
814 bbox_inches="tight",
815 )
816 plt.close(fig)
817
818
819def plot_fd4_profile(directory):
820 """! Density of the Gaussian pulse along the positive x-axis at a few time stamps"""
821 fig, ax = plt.subplots(figsize=(7, 4))
822 snapshots = number_of_snapshots(directory, FD4PatchFile)
823 for snapshot, colour in zip(
824 [0, (snapshots - 1) // 4, (snapshots - 1) // 2, snapshots - 1],
825 ["C0", "C1", "C2", "C3"],
826 ):
827 t, x, rho = density_along_x_axis(directory, snapshot, FD4PatchFile)
828 ax.plot(
829 x,
830 rho,
831 "o-",
832 color=colour,
833 markersize=2,
834 linewidth=0.8,
835 label="t={:.2f}".format(t),
836 )
837 print(
838 "FD4 pulse t={:.2f}: density in [{:.4f},{:.4f}], maximum at r={:.3f}".format(
839 t, rho.min(), rho.max(), x[np.argmax(rho)]
840 )
841 )
842 ax.set_xlabel("distance r from the centre (along y=0)")
843 ax.set_ylabel("density")
844 ax.set_title("Density profile of the Gaussian pulse (FD4)")
845 ax.legend()
846 fig.savefig(
847 os.path.join(Here, "13_euler-with-amr-fd4-profile.png"),
848 dpi=100,
849 bbox_inches="tight",
850 )
851 plt.close(fig)
852
853
855 """! Construct the mesh adaptation task graph of the dynamically adaptive setup and draw it"""
856 peano4.api.Logger().file_name = os.path.join(
857 tempfile.gettempdir(), "13-euler-with-amr-log.md"
858 )
859 with tempfile.TemporaryDirectory() as directory:
860 exahype2_project = exahype2.api.Project(
861 ["illustration"], "illustration", "illustration", directory=directory
862 )
863 exahype2_project.set_global_simulation_parameters(
864 dimensions=2,
865 offset=[-0.5, -0.5],
866 size=[1.0, 1.0],
867 min_end_time=DynamicEndTime,
868 periodic_BC=[True, True, True],
869 )
870 peano_project = peano4.api.Project(
871 ["illustration"], "illustration", directory=directory
872 )
873 solver = create_solver(adaptive=True, dynamic=True)
874 graphs = [peano4.api.taskgraph.Graph() for _ in range(4)]
875 solver.generate_solver_realisation(exahype2_project, peano_project, *graphs)
876 graphs[3].visualise(title="Mesh adaptation and load balancing task graph")
877 output = os.path.join(Here, "13_euler-with-amr-mesh-adaptation-task-graph.png")
878 plt.gcf().savefig(output, dpi=90)
879 plt.close(plt.gcf())
880 print("Wrote", output)
881
882
883if __name__ == "__main__":
884 parser = argparse.ArgumentParser(
885 description="Create the PNGs of the tutorial 13_euler-with-amr.ipynb"
886 )
887 parser.add_argument(
888 "--work-directory",
889 default=None,
890 help="Directory for builds and outputs (default: temporary directory)",
891 )
892 parser.add_argument(
893 "--skip-run",
894 action="store_true",
895 help="Reuse the output of a previous run within the work directory",
896 )
897 parser.add_argument(
898 "--fd4-only",
899 action="store_true",
900 help="Create only the pictures of the section on the fourth-order Finite Differences",
901 )
902 parser.add_argument(
903 "--gradient-only",
904 action="store_true",
905 help="Create only the pictures of the section on the gradient-based refinement",
906 )
907 args = parser.parse_args()
908
909 work_directory = args.work_directory or tempfile.mkdtemp(
910 prefix="13_euler-with-amr-"
911 )
912 dynamic_directory = os.path.join(work_directory, "dynamic-mesh")
913 gradient_directory = os.path.join(work_directory, "gradient-mesh")
914 if args.gradient_only:
915 os.makedirs(dynamic_directory, exist_ok=True)
916 os.makedirs(gradient_directory, exist_ok=True)
917 if not args.skip_run:
918 print(
919 "build and run over dynamically adaptive mesh in {}".format(
920 dynamic_directory
921 )
922 )
923 build_and_run(dynamic_directory, adaptive=True, dynamic=True)
924 create_gradient_pictures(dynamic_directory, gradient_directory, args.skip_run)
925 print("wrote PNGs into {}".format(Here))
926 sys.exit(0)
927
928 fd4_explosion_directory = os.path.join(work_directory, "fd4-explosion")
929 fd4_pulse_directory = os.path.join(work_directory, "fd4-pulse")
930 os.makedirs(fd4_explosion_directory, exist_ok=True)
931 os.makedirs(fd4_pulse_directory, exist_ok=True)
932 if not args.skip_run:
933 print(
934 "build and run FD4 in {} and {}".format(
935 fd4_explosion_directory, fd4_pulse_directory
936 )
937 )
938 build_and_run_fd4(fd4_explosion_directory, InitPointWisely)
939 build_and_run_fd4(fd4_pulse_directory, FD4InitPointWisely)
940 plot_fd4_explosion_time_step_sizes(fd4_explosion_directory)
941 plot_fd4_snapshots(fd4_pulse_directory)
942 plot_fd4_profile(fd4_pulse_directory)
943 if args.fd4_only:
944 print("wrote PNGs into {}".format(Here))
945 sys.exit(0)
946
947 directory = os.path.join(work_directory, "regular-grid")
948 os.makedirs(directory, exist_ok=True)
949 if not args.skip_run:
950 print("build and run in {}".format(directory))
951 build_and_run(directory)
952
953 plot_mesh(directory)
954 plot_snapshots(directory)
955 plot_profile(directory)
956 plot_active_patches(directory)
957
958 adaptive_directory = os.path.join(work_directory, "adaptive-mesh")
959 os.makedirs(adaptive_directory, exist_ok=True)
960 if not args.skip_run:
961 print("build and run over adaptive mesh in {}".format(adaptive_directory))
962 build_and_run(adaptive_directory, adaptive=True)
963 plot_adaptive_mesh(adaptive_directory)
964 plot_adaptive_snapshots(adaptive_directory)
965 plot_profile_comparison(directory, adaptive_directory)
966 print_conservation(adaptive_directory)
968
969 os.makedirs(dynamic_directory, exist_ok=True)
970 if not args.skip_run:
971 print(
972 "build and run over dynamically adaptive mesh in {}".format(
973 dynamic_directory
974 )
975 )
976 build_and_run(dynamic_directory, adaptive=True, dynamic=True)
977 plot_dynamic_patch_count(dynamic_directory)
978 plot_dynamic_mesh_change(dynamic_directory)
979 print_conservation(dynamic_directory)
981
982 os.makedirs(gradient_directory, exist_ok=True)
983 create_gradient_pictures(dynamic_directory, gradient_directory, args.skip_run)
984 print("wrote PNGs into {}".format(Here))
The conservative flux F(Q) of a hyperbolic PDE.
Definition Flux.py:8
Gradient-based refinement criterion: given a one-sided difference quotient of the solution along one ...
Point-wise initial condition: given the physical position of a single degree of freedom,...
The maximum absolute eigenvalue of the flux Jacobian in a given direction, i.e.
ExaHyPE 2 project.
Definition Project.py:15
Point-wise refinement criterion: given the solution Q at a single degree of freedom,...
Abstract base class for all ExaHyPE 2 solvers.
Definition Solver.py:17
Plotting counterpart to RusanovWithExplicitEuler: maps the patch data of a Finite Volume scheme with ...
Finite Volumes with a Rusanov Riemann solver and explicit Euler time stepping, realised over a block-...
Setup phase counterpart to RusanovWithExplicitEuler: sets the initial condition for a Finite Volume s...
Fourth-order central Finite Differences with explicit Runge-Kutta time stepping, realised over a bloc...
Plotting counterpart of the fourth-order Finite Differences with Runge-Kutta time stepping (exahype2....
Setup phase of the fourth-order Finite Differences with Runge-Kutta time stepping (exahype2....
Singleton logger for the Peano 4 Python API.
Definition Logger.py:37
Represents a Peano 4 project.
Definition Project.py:23
Holds a sequence of Task instances.
Definition Graph.py:9
build_and_run(directory, adaptive=False, dynamic=False, gradient=False)
Generate, compile and run the tutorial setup within directory.
patch_counts(directory)
Time stamps and numbers of patches of all snapshots.
plot_gradient_mesh(pointwise_directory, gradient_directory)
Density and patch boundaries of both runs at t=0.2.
plot_gradient_profile_comparison(pointwise_directory, gradient_directory)
Density along the positive x-axis: point-wise against gradient-based criterion.
create_solver(adaptive, dynamic=False, gradient=False)
The tutorial's solver over a regular, an adaptive or a dynamically adaptive mesh.
plot_fd4_explosion_time_step_sizes(directory)
Time step sizes of the fourth-order Finite Differences for the point explosion.
build_and_run_fd4(directory, init_point_wisely)
Generate, compile and run the fourth-order Finite Differences over the regular mesh within directory.
plot_dynamic_mesh_change(directory)
Density and patch boundaries right before the first mesh change and at the end.
plot_gradient_patch_counts(pointwise_directory, gradient_directory)
Number of patches per snapshot of both dynamically adaptive runs.
plot_mesh(directory)
Initial density plus the patch boundaries.
plot_setup_task_graph()
Construct the setup task graph with the refinement criterion and draw it.
print_conservation(directory)
Total mass and energy of the first and the last snapshot.
plot_fd4_profile(directory)
Density of the Gaussian pulse along the positive x-axis at a few time stamps.
read_snapshot(directory, snapshot, patch_file=PatchFile)
Return the time stamp and all patches (offset, size, values) of a snapshot.
plot_profile_comparison(regular_directory, adaptive_directory)
Density along the positive x-axis: regular against adaptive mesh.
plot_profile(directory)
Density along the positive x-axis at a few time stamps.
gradient_snapshots(directory)
Snapshots for the comparison of the point-wise and the gradient-based criterion: t=0....
plot_active_patches(directory)
Highlight the patches which hold steep density gradients at the end.
plot_snapshots(directory)
Density at three time stamps side by side.
plot_mesh_adaptation_task_graph()
Construct the mesh adaptation task graph of the dynamically adaptive setup and draw it.
plot_adaptive_mesh(directory)
Initial density on the adaptive mesh plus the patch boundaries.
create_gradient_pictures(dynamic_directory, gradient_directory, skip_run)
Run the gradient-based criterion and compare it to the point-wise one.
plot_dynamic_patch_count(directory)
Number of patches per snapshot of the dynamically adaptive run.
number_of_snapshots(directory, patch_file=PatchFile)
plot_fd4_snapshots(directory)
Density of the Gaussian pulse at three time stamps side by side.
plot_adaptive_snapshots(directory)
Density at three time stamps on the adaptive mesh plus patch boundaries.
density_along_x_axis(directory, snapshot, patch_file=PatchFile)
Return time stamp plus volume centres and densities along y=0, x>=0.
Interactive visualisation of Peano patch files within Jupyter notebooks.
Definition notebook.py:1
plot_snapshot(file_name, snapshot=-1, unknown=0, clim=None, ax=None, cmap="viridis", label=None)
Plot one unknown of one snapshot.
Definition notebook.py:100
read_patches(patch_file_name)
Return list of (offset, size, values) for all patches of one patch file.
Definition notebook.py:49
read_snapshots(file_name)
Return list of (time stamp, list of patch files) from a patch meta file.
Definition notebook.py:30