Peano
Loading...
Searching...
No Matches
12_higher-order-solvers.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 12_higher-order-solvers.ipynb
6
7The script builds the Euler setup of the tutorials 10_euler and
812_higher-order-solvers with the first-order Finite Volumes and with the
9fourth-order Finite Differences plus Runge-Kutta schemes of order one to
10four, runs them on three meshes, and writes the following pictures into
11the directory of this script:
12
13- 12_higher-order-solvers-convergence.png: error over mesh width;
14- 12_higher-order-solvers-profile.png: density along y=0 after one period;
15- 12_higher-order-solvers-instability.png: the error over time of the
16 explicit Euler time stepping (Runge-Kutta of order one).
17
18The PDE terms are injected from Python, i.e. we do not need any hand-written
19C++ file. Call it from anywhere:
20
21 python3 tutorials/exahype2/illustrations/12_higher-order-solvers.py
22
23All builds and outputs go into a temporary directory (or --work-directory).
24A run whose directory already holds the output is not repeated, so you can
25rerun the script with the same --work-directory after you have changed a
26plot. The script builds 20 executables, i.e. it runs for roughly 40 minutes.
27
28"""
29import argparse
30import math
31import os
32import subprocess
33import sys
34import tempfile
35
36Here = os.path.dirname(os.path.abspath(__file__))
37PeanoRoot = os.path.abspath(os.path.join(Here, "..", "..", ".."))
38sys.path.insert(0, os.path.join(PeanoRoot, "src"))
39
40import matplotlib
41
42matplotlib.use("Agg")
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 p = 1.0;
83 constexpr double u = 1.0;
84 const double rho = 1.0 + 0.2 * std::sin(2.0 * M_PI * (x[0] + 0.1) / 0.2);
85 Q[0] = rho;
86 Q[1] = rho * u;
87 Q[2] = 0.0;
88 Q[3] = p / (gamma - 1) + 0.5 * rho * u * u;
89"""
90
91DomainOffset = -0.1
92DomainWidth = 0.2
93
94# Volume sizes and the resulting number of grid cells per axis, as each mesh
95# cell hosts 9x9 volumes or grid cells
96VolumeSizes = [(0.0075, 27), (0.0025, 81), (0.00083, 243)]
97
98ConvergenceEndTime = 0.05
99PeriodEndTime = 0.2
100
101# Runge-Kutta orders and the colours of the fourth-order Finite Differences
102RungeKuttaColours = {1: "C3", 2: "C1", 3: "C2", 4: "C0"}
103
104
106 """! Density of the entropy wave advected with u=1 over [-0.1,0.1]"""
107 return 1.0 + 0.2 * np.sin(
108 2.0 * np.pi * (np.asarray(x) - t - DomainOffset) / DomainWidth
109 )
110
111
112def solver_name(rk_order):
113 """! Name of the solver: EulerFV for rk_order None, EulerFD4 otherwise"""
114 return "EulerFV" if rk_order is None else "EulerFD4"
115
116
117def patch_file(directory, rk_order):
118 return os.path.join(
119 directory, "solution-{}.peano-patch-file".format(solver_name(rk_order))
120 )
121
122
124 rk_order, volume_size, end_time, time_in_between_plots, directory, peano_root
125):
126 """!
127
128 Generate, compile and run one setup within directory
129
130 @param rk_order: int or None
131 None selects the Finite Volumes of the tutorial 10_euler, an integer
132 the fourth-order Finite Differences with the Runge-Kutta scheme of this
133 order.
134
135 @param peano_root: str
136 Configured and built Peano directory the executable links against.
137
138 """
139 if os.path.exists(patch_file(directory, rk_order)):
140 print("reuse {}".format(directory))
141 return
142 print("build and run {}".format(directory))
143 os.makedirs(directory, exist_ok=True)
144 project = exahype2.api.Project(
145 namespace=["tutorials", "exahype2", "higherorder"],
146 project_name="HigherOrder",
147 directory=directory,
148 executable="higher-order",
149 )
150 solver = exahype2.api.Solver(solver_name(rk_order), 4, 0)
151 solver.add_user_solver_includes("#include <cmath>\n#include <algorithm>\n")
152 solver.add_pde_term(exahype2.api.Flux(implementation=Flux, stateless=False))
153 solver.add_pde_term(
154 exahype2.api.MaxEigenvalue(implementation=MaxEigenvalue, stateless=False)
155 )
156 solver.add_pde_term(
157 exahype2.api.InitPointWisely(implementation=InitPointWisely, stateless=False)
158 )
159 if rk_order is None:
160 solver.numerical_scheme = exahype2.api.fv.RusanovWithExplicitEuler(solver, 9)
161 solver.grid_construction = exahype2.api.fv.SetupSimulationOverRegularGrid(
162 solver, volume_size=volume_size
163 )
164 solver.plotter = exahype2.api.fv.PlotAsVTKFile(
165 solver,
166 first_plot_time_stamp=0.0,
167 time_in_between_plots=time_in_between_plots,
168 )
169 else:
171 solver, patch_size=9, rk_order=rk_order
172 )
173 solver.grid_construction = exahype2.api.rkfd4.SetupSimulationOverRegularGrid(
174 solver, volume_size=volume_size
175 )
176 # The errors of the finer meshes are far below the default precision
177 solver.plotter = exahype2.api.rkfd4.PlotAsVTKFile(
178 solver,
179 first_plot_time_stamp=0.0,
180 time_in_between_plots=time_in_between_plots,
181 plotter_precision=14,
182 )
183 project.add_solver(solver)
184 project.set_global_simulation_parameters(
185 dimensions=2,
186 offset=[DomainOffset, DomainOffset],
187 size=[DomainWidth, DomainWidth],
188 min_end_time=end_time,
189 max_end_time=end_time,
190 periodic_BC=[True, True, True],
191 )
192 project.set_Peano4_installation(peano_root, peano4.api.output.CompileMode.RELEASE)
193 peano4_project = project.generate_Peano4_project(
194 verbosity=peano4.api.LoggerVerbosity.TEXT
195 )
196 peano4_project.generate()
197 subprocess.run(["make", "-j"], cwd=directory, check=True, stdout=subprocess.DEVNULL)
198 subprocess.run(
199 ["./higher-order"], cwd=directory, check=True, stdout=subprocess.DEVNULL
200 )
201
202
203def snap(offset, size):
204 """!
205
206 Snap a patch's offset and size onto the regular mesh over the domain
207
208 The patch files hold offsets and sizes with seven significant digits
209 only. Below an error of roughly 1e-7, this would dominate the error.
210
211 """
212 size = np.array([DomainWidth / round(DomainWidth / s) for s in size])
213 offset = np.round((np.array(offset) - DomainOffset) / size) * size + DomainOffset
214 return offset, size
215
216
217def errors(directory, rk_order):
218 """! Return a list of (time stamp, max error, rms error) of the density per snapshot"""
219 result = []
220 for time_stamp, files in tarch.plotter.api.notebook.read_snapshots(
221 patch_file(directory, rk_order)
222 ):
223 squared_errors = []
224 for file_name in files:
225 for offset, size, values in tarch.plotter.api.notebook.read_patches(
226 file_name
227 ):
228 offset, size = snap(offset, size)
229 dofs = values.shape[0]
230 x = offset[0] + (np.arange(dofs) + 0.5) * size[0] / dofs
231 squared_errors.append(
232 (values[:, :, 0] - exact_density(x, time_stamp)) ** 2
233 )
234 squared_errors = np.concatenate([e.flatten() for e in squared_errors])
235 result.append(
236 (
237 time_stamp,
238 math.sqrt(np.max(squared_errors)),
239 math.sqrt(np.mean(squared_errors)),
240 )
241 )
242 return result
243
244
245def density_along_x_axis(directory, rk_order, snapshot):
246 """! Return time stamp plus grid cell centres and densities along y=0"""
248 patch_file(directory, rk_order)
249 )[snapshot]
250 x, rho = [], []
251 for file_name in files:
252 for offset, size, values in tarch.plotter.api.notebook.read_patches(file_name):
253 offset, size = snap(offset, size)
254 dofs = values.shape[0]
255 if offset[1] <= 0.0 < offset[1] + size[1]:
256 row = int((0.0 - offset[1]) / (size[1] / dofs))
257 x += list(offset[0] + (np.arange(dofs) + 0.5) * size[0] / dofs)
258 rho += list(values[row, :, 0])
259 order = np.argsort(x)
260 return time_stamp, np.array(x)[order], np.array(rho)[order]
261
262
263def label(rk_order):
264 if rk_order is None:
265 return "Finite Volumes (Rusanov, explicit Euler)"
266 return "FD4 with Runge-Kutta of order {}".format(rk_order)
267
268
270 """!
271
272 Max error at t=ConvergenceEndTime over the grid cells per axis
273
274 @param runs: dict
275 Maps rk_order (None for Finite Volumes) onto a list of
276 (grid cells per axis, directory).
277
278 """
279 fig, ax = plt.subplots(figsize=(7, 5))
280 for rk_order, directories in runs.items():
281 cells = [c for c, _ in directories]
282 max_errors = [errors(d, rk_order)[-1][1] for _, d in directories]
283 style = {"marker": "o", "linestyle": "-"}
284 if rk_order is None:
285 style = {"marker": "s", "linestyle": "--"}
286 elif rk_order == 3:
287 # Runge-Kutta of order three and four yield the same errors. Big
288 # hollow markers keep order three visible behind order four.
289 style = {
290 "marker": "o",
291 "linestyle": "--",
292 "markersize": 12,
293 "markerfacecolor": "none",
294 }
295 ax.loglog(
296 cells,
297 max_errors,
298 color="k" if rk_order is None else RungeKuttaColours[rk_order],
299 label=label(rk_order),
300 **style,
301 )
302 # reference slopes, anchored at the coarsest mesh
303 cells = np.array([c for _, c in VolumeSizes], dtype=float)
304 for order, anchor in [(1, 1.5e-1), (2, 1e-5), (4, 1e-4)]:
305 ax.loglog(
306 cells,
307 anchor * (cells[0] / cells) ** order,
308 ":",
309 color="grey",
310 linewidth=1,
311 )
312 ax.annotate(
313 "order {}".format(order),
314 (cells[-1], anchor * (cells[0] / cells[-1]) ** order),
315 textcoords="offset points",
316 xytext=(5, -3),
317 color="grey",
318 )
319 ax.set_xticks(cells)
320 ax.set_xticklabels([str(int(c)) for c in cells])
321 ax.set_xlim(20, 400)
322 ax.set_xlabel("grid cells (volumes) per axis")
323 ax.set_ylabel("max |density - exact density| at t={}".format(ConvergenceEndTime))
324 ax.set_title("Convergence for the entropy wave")
325 ax.legend(loc="lower left", fontsize="small")
326 fig.savefig(
327 os.path.join(Here, "12_higher-order-solvers-convergence.png"),
328 dpi=100,
329 bbox_inches="tight",
330 )
331 plt.close(fig)
332
333
334def plot_profile(runs):
335 """!
336
337 Density along y=0 after one period on the coarsest mesh
338
339 @param runs: list
340 List of (rk_order, directory).
341
342 """
343 fig, ax = plt.subplots(figsize=(7, 4))
344 x_exact = np.linspace(DomainOffset, DomainOffset + DomainWidth, 400)
345 ax.plot(x_exact, exact_density(x_exact, 0.0), "k-", linewidth=1, label="exact")
346 for rk_order, directory in runs:
347 t, x, rho = density_along_x_axis(directory, rk_order, -1)
348 ax.plot(
349 x,
350 rho,
351 "s" if rk_order is None else "o",
352 color="grey" if rk_order is None else RungeKuttaColours[rk_order],
353 markersize=3,
354 label=label(rk_order),
355 )
356 ax.set_xlabel("x")
357 ax.set_ylabel("density")
358 ax.set_title(
359 "Density along y=0 at t={} on {}x{} grid cells".format(
360 PeriodEndTime, VolumeSizes[0][1], VolumeSizes[0][1]
361 )
362 )
363 ax.legend(loc="lower left", fontsize="small")
364 fig.savefig(
365 os.path.join(Here, "12_higher-order-solvers-profile.png"),
366 dpi=100,
367 bbox_inches="tight",
368 )
369 plt.close(fig)
370
371
373 """!
374
375 Max error over time for the explicit Euler time stepping vs the others
376
377 @param runs: list
378 List of (rk_order, directory) on the same mesh.
379
380 """
381 fig, (left, right) = plt.subplots(1, 2, figsize=(12, 4))
382 for rk_order, directory in runs:
383 e = np.array(errors(directory, rk_order))
384 left.semilogy(
385 e[1:, 0],
386 e[1:, 1],
387 "-",
388 color=RungeKuttaColours[rk_order],
389 label=label(rk_order),
390 )
391 left.set_xlabel("t")
392 left.set_ylabel("max |density - exact density|")
393 left.set_title("Error over time on {0}x{0} grid cells".format(VolumeSizes[1][1]))
394 left.legend(loc="upper left", fontsize="small")
395
396 rk_order, directory = runs[0]
397 t, x, rho = density_along_x_axis(directory, rk_order, -1)
398 right.plot(
399 x,
400 rho - exact_density(x, t),
401 "-",
402 color=RungeKuttaColours[rk_order],
403 linewidth=1,
404 )
405 right.set_xlabel("x")
406 right.set_ylabel("density - exact density")
407 right.set_title("Error along y=0 at t={:.3f}, {}".format(t, label(rk_order)))
408 fig.savefig(
409 os.path.join(Here, "12_higher-order-solvers-instability.png"),
410 dpi=100,
411 bbox_inches="tight",
412 )
413 plt.close(fig)
414
415
416if __name__ == "__main__":
417 parser = argparse.ArgumentParser(
418 description="Create the PNGs of the tutorial 12_higher-order-solvers.ipynb"
419 )
420 parser.add_argument(
421 "--work-directory",
422 default=None,
423 help="Directory for builds and outputs (default: temporary directory)",
424 )
425 parser.add_argument(
426 "--peano-root",
427 default=PeanoRoot,
428 help="Configured and built Peano directory (default: the repository this script resides in)",
429 )
430 parser.add_argument(
431 "--pictures",
432 nargs="+",
433 default=["convergence", "profile", "instability"],
434 choices=["convergence", "profile", "instability"],
435 help="Pictures to create (default: all)",
436 )
437 args = parser.parse_args()
438
439 work_directory = args.work_directory or tempfile.mkdtemp(
440 prefix="12_higher-order-solvers-"
441 )
442
443 def directory(name):
444 return os.path.join(work_directory, name)
445
446 if "convergence" in args.pictures:
447 runs = {}
448 for rk_order in [None, 1, 2, 3, 4]:
449 runs[rk_order] = []
450 for volume_size, cells in VolumeSizes:
452 "convergence-{}-{}".format(
453 "fv" if rk_order is None else "rk{}".format(rk_order), cells
454 )
455 )
457 rk_order,
458 volume_size,
459 ConvergenceEndTime,
460 ConvergenceEndTime,
461 d,
462 args.peano_root,
463 )
464 runs[rk_order].append((cells, d))
465 plot_convergence(runs)
466
467 if "profile" in args.pictures:
468 runs = []
469 for rk_order in [None, 4]:
470 d = directory(
471 "profile-{}".format(
472 "fv" if rk_order is None else "rk{}".format(rk_order)
473 )
474 )
476 rk_order,
477 VolumeSizes[0][0],
478 PeriodEndTime,
479 PeriodEndTime,
480 d,
481 args.peano_root,
482 )
483 runs.append((rk_order, d))
484 plot_profile(runs)
485
486 if "instability" in args.pictures:
487 runs = []
488 for rk_order in [1, 2, 4]:
489 d = directory("instability-rk{}".format(rk_order))
491 rk_order, VolumeSizes[1][0], PeriodEndTime, 0.005, d, args.peano_root
492 )
493 runs.append((rk_order, d))
494 plot_instability(runs)
495
496 print("wrote PNGs into {}".format(Here))
The conservative flux F(Q) of a hyperbolic PDE.
Definition Flux.py:8
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
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....
exact_density(x, t)
Density of the entropy wave advected with u=1 over [-0.1,0.1].
density_along_x_axis(directory, rk_order, snapshot)
Return time stamp plus grid cell centres and densities along y=0.
plot_profile(runs)
Density along y=0 after one period on the coarsest mesh.
snap(offset, size)
Snap a patch's offset and size onto the regular mesh over the domain.
build_and_run(rk_order, volume_size, end_time, time_in_between_plots, directory, peano_root)
Generate, compile and run one setup within directory.
plot_convergence(runs)
Max error at t=ConvergenceEndTime over the grid cells per axis.
solver_name(rk_order)
Name of the solver: EulerFV for rk_order None, EulerFD4 otherwise.
plot_instability(runs)
Max error over time for the explicit Euler time stepping vs the others.
errors(directory, rk_order)
Return a list of (time stamp, max error, rms error) of the density per snapshot.
Interactive visualisation of Peano patch files within Jupyter notebooks.
Definition notebook.py:1
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