Peano
Loading...
Searching...
No Matches
523_higher-order-fd.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 523_higher-order-fd.ipynb
6
7The script builds the single black hole setup of the tutorial with several
8numerical schemes, runs them one after the other, extracts the lapse and
9the conformal factor along the x-axis with Peano's PointProbe, and writes
10all pictures into the directory of this script. All runs use Sommerfeld
11boundary conditions, a domain decomposition and four OpenMP threads, i.e.
12the final setup of 522_single-black-hole.ipynb, over 9x9x9 mesh cells with
1310x10x10 volumes or grid cells each:
14
15- fv: exahype2.api.fv.RusanovWithExplicitEuler;
16- fd4: exahype2.api.rkfd4.CentralDifferencesWithRungeKutta with the classic
17 fourth-order Runge-Kutta scheme and without dissipation;
18- fd4-ko-{sigma}: exagrype.api.FD4WithKODissipation with the Kreiss-Oliger
19 dissipation scaled by sigma, for sigma 0.3, 1 and 8;
20- fd4-lop: the fourth-order Finite Differences with the kernel
21 CentralDifferencesWithLopsidedAdvection, i.e. with the advection terms
22 which exagrype::ccz4::ncp() omits;
23- fd4-lop-ko-8.0: the same with the Kreiss-Oliger dissipation (sigma=8) on
24 all unknowns;
25- fd4-lop-ko-8.0-subset: the same with the dissipation on Q0-Q22 and
26 Q53-Q54 only (DissipationSubset).
27
28The dictionary Runs holds the configurations.
29
30Call it from anywhere:
31
32 python3 tutorials/exahype2/exagrype/illustrations/523_higher-order-fd.py
33
34Peano has to be configured with --with-gsl, as the initial data stems from
35TwoPunctures. All builds and outputs go into a temporary directory (or
36--work-directory). If you pass a work directory, a run which has completed
37before is reused, i.e. you can rerun the script to redraw the pictures
38without recomputing. A run which crashes or produces NaNs is reported, and
39its output up to that point is used.
40
41"""
42import argparse
43import contextlib
44import csv
45import io
46import os
47import subprocess
48import sys
49import tempfile
50import time
51
52Here = os.path.dirname(os.path.abspath(__file__))
53PeanoRoot = os.path.abspath(os.path.join(Here, "..", "..", "..", ".."))
54sys.path.insert(0, os.path.join(PeanoRoot, "src"))
55
56import matplotlib
57
58matplotlib.use("Agg")
59import matplotlib.pyplot as plt
60import numpy as np
61
62import peano4.api
63import exahype2.api
64import exagrype.api
66
67
68PatchFile = "solution-CCZ4.peano-patch-file"
69Mass = 1.0
70DomainOffset = -9.0
71DomainSize = 18.0
72CellsPerAxis = 9
73PatchSize = 10
74VolumesPerAxis = CellsPerAxis * PatchSize
75H = DomainSize / VolumesPerAxis
76Threads = 4
77TimeInBetweenPlots = 0.5
78
79
81DissipationSubset = list(range(0, 23)) + [53, 54]
82
83
85Runs = {
86 "fv": (None, False, None),
87 "fd4": (None, False, None),
88 "fd4-ko-0.3": (0.3, False, None),
89 "fd4-ko-1.0": (1.0, False, None),
90 "fd4-ko-8.0": (8.0, False, None),
91 "fd4-lop": (None, True, None),
92 "fd4-lop-ko-8.0": (8.0, True, None),
93 "fd4-lop-ko-8.0-subset": (8.0, True, DissipationSubset),
94}
95
96
97Style = {
98 "fv": ("tab:blue", "o", "Finite Volumes"),
99 "fd4": ("tab:red", "s", "FD4, no dissipation"),
100 "fd4-ko-0.3": ("tab:green", "^", r"FD4, KO $\sigma=0.3$"),
101 "fd4-ko-1.0": ("tab:purple", "v", r"FD4, KO $\sigma=1$"),
102 "fd4-ko-8.0": ("tab:orange", "D", r"FD4, KO $\sigma=8$"),
103 "fd4-lop": ("tab:brown", "P", "FD4, lopsided, no dissipation"),
104 "fd4-lop-ko-8.0": ("tab:pink", "X", r"FD4, lopsided, KO $\sigma=8$ (all)"),
105 "fd4-lop-ko-8.0-subset": (
106 "tab:olive",
107 "*",
108 r"FD4, lopsided, KO $\sigma=8$ (Q0-Q22, Q53-Q54)",
109 ),
110}
111
112
113def create_scheme(name, solver):
114 """! The numerical scheme, grid construction and plotter of one run"""
115 if name == "fv":
116 package = exahype2.api.fv
117 solver.numerical_scheme = exahype2.api.fv.RusanovWithExplicitEuler(
118 solver, PatchSize
119 )
120 else:
121 package = exahype2.api.rkfd4
122 sigma, lopsided, dissipative_quantities = Runs[name]
123 Kernel = exahype2.api.rkfd4.CentralDifferencesWithRungeKutta.RightHandSideKernel
124 arguments = {}
125 if lopsided:
126 arguments = dict(
127 right_hand_side_kernel=Kernel.CentralDifferencesWithLopsidedAdvection,
128 advection_velocity=exagrype.api.CCZ4Solver.ADVECTION_VELOCITY,
129 advected_quantities=solver.lopsided_advection_quantities(),
130 )
131 if sigma is None:
132 solver.numerical_scheme = (
134 solver, PatchSize, rk_order=4, **arguments
135 )
136 )
137 else:
138 solver.numerical_scheme = exagrype.api.FD4WithKODissipation(
139 solver,
140 PatchSize,
141 rk_order=4,
142 kreiss_oliger_sigma=sigma,
143 quantities=dissipative_quantities,
144 **arguments,
145 )
146 solver.grid_construction = package.SetupSimulationOverRegularGrid(
147 solver, volume_size=1.01 * H
148 )
149 solver.plotter = package.PlotAsVTKFile(
150 solver,
151 first_plot_time_stamp=0.0,
152 time_in_between_plots=TimeInBetweenPlots,
153 plotter_precision=10,
154 )
155 solver.plotter.add_plot_filter(
156 offset=[DomainOffset, 0.1, 0.1], size=[DomainSize, 0.1, 0.1]
157 )
158
159
160def build_and_run(name, directory, peano_installation, end_time):
161 """!
162
163 Generate, compile and run one setup within directory
164
165 Returns the runtime of the executable in seconds and whether it
166 terminated successfully. If the directory holds the result of a
167 previous run, we do not rerun but return the stored values.
168
169 """
170 runtime_file = os.path.join(directory, "runtime.txt")
171 if os.path.exists(runtime_file):
172 with open(runtime_file) as f:
173 runtime, success = f.read().split()
174 return float(runtime), success == "True"
175
176 os.makedirs(directory, exist_ok=True)
177 print("build and run {} until t={} in {}".format(name, end_time, directory))
178 project = exahype2.api.Project(
179 namespace=["tutorials", "exagrype", "higherorderfd"],
180 project_name="HigherOrderFD",
181 directory=directory,
182 executable="single-black-hole",
183 )
185 "CCZ4",
186 parameters={"CCZ4bs": 1.0, "CCZ4sk": 1.0},
187 initial_condition=exagrype.api.initialvalues.single_black_hole(mass=Mass),
188 )
189 solver.add_pde_term(
190 exahype2.api.SommerfeldBoundaryConditions(
191 implementation="::exagrype::initialvalues::flat(Q, x, 0.0);"
192 )
193 )
194 create_scheme(name, solver)
195 project.add_solver(solver)
196 project.set_global_simulation_parameters(
197 dimensions=3,
198 offset=[DomainOffset] * 3,
199 size=[DomainSize] * 3,
200 min_end_time=end_time,
201 max_end_time=end_time,
202 periodic_BC=[False, False, False],
203 )
204 project.set_load_balancing(
205 "toolbox::loadbalancing::strategies::SpreadOutOnceGridStagnates",
206 "new ::exahype2::LoadBalancingConfiguration()",
207 )
208 project.set_Peano4_installation(
209 peano_installation, peano4.api.output.CompileMode.RELEASE
210 )
211 with contextlib.redirect_stdout(io.StringIO()):
212 peano4_project = project.generate_Peano4_project(
213 verbosity=peano4.api.LoggerVerbosity.TEXT
214 )
215 peano4_project.generate()
216
217 subprocess.run(
218 ["make", "-j{}".format(Threads)],
219 cwd=directory,
220 check=True,
221 stdout=subprocess.DEVNULL,
222 stderr=subprocess.DEVNULL,
223 )
224 start = time.time()
225 with open(os.path.join(directory, "run.log"), "w") as log:
226 environment = dict(os.environ)
227 environment["OMP_NUM_THREADS"] = str(Threads)
228 result = subprocess.run(
229 ["./single-black-hole"],
230 cwd=directory,
231 stdout=log,
232 stderr=subprocess.STDOUT,
233 env=environment,
234 )
235 runtime = time.time() - start
236 success = result.returncode == 0
237 with open(runtime_file, "w") as f:
238 f.write("{} {}".format(runtime, success))
239 return runtime, success
240
241
242def probe(directory, point, output_file):
243 """!
244
245 Probe all snapshots at one point
246
247 Returns the time stamps, the lapse and the conformal factor phi.
248
249 """
251 file_name=os.path.join(directory, PatchFile),
252 point=point,
253 output_file=os.path.join(directory, output_file),
254 start=0,
255 end=sys.maxsize,
256 )
257 with contextlib.redirect_stdout(io.StringIO()):
258 point_probe.display()
259 with open(os.path.join(directory, output_file)) as csv_file:
260 rows = list(csv.DictReader(csv_file))
261 return (
262 np.array([float(row["time"]) for row in rows]),
263 np.array([float(row["lapse"]) for row in rows]),
264 np.array([float(row["phi"]) for row in rows]),
265 )
266
267
268def profile(directory):
269 """!
270
271 Probe lapse and phi in the centre of each volume or grid cell along the
272 line y=z=h/2, i.e. through the centres next to the puncture
273
274 Returns the x coordinates, the time stamps and two 2d arrays
275 lapse[snapshot,x] and phi[snapshot,x].
276
277 """
278 x = DomainOffset + (np.arange(VolumesPerAxis) + 0.5) * H
279 lapse, phi = [], []
280 for i, x_i in enumerate(x):
281 t, lapse_over_time, phi_over_time = probe(
282 directory, [x_i, H / 2.0, H / 2.0], "profile-{}.csv".format(i)
283 )
284 lapse.append(lapse_over_time)
285 phi.append(phi_over_time)
286 snapshots = min(len(entry) for entry in lapse)
287 return (
288 x,
289 t[:snapshots],
290 np.array([entry[:snapshots] for entry in lapse]).T,
291 np.array([entry[:snapshots] for entry in phi]).T,
292 )
293
294
295def radius(x):
296 return np.sqrt(np.asarray(x) ** 2 + 2.0 * (H / 2.0) ** 2)
297
298
299def time_steps(directory):
300 """!
301
302 Number of time steps and average wall clock time per time step
303
304 Both stem from the final report of peano4::DefaultStepReport in run.log,
305 i.e. from the line "TimeStep: ...s (avg=...,#measurements=...". Returns
306 None if the run did not terminate regularly.
307
308 """
309 with open(os.path.join(directory, "run.log")) as log:
310 for line in log:
311 if "reportFinalResults" in line and "TimeStep:" in line:
312 statistics = line.split("(avg=")[1]
313 average = float(statistics.split(",")[0])
314 count = int(statistics.split("#measurements=")[1].split(",")[0])
315 return count, average
316 return None
317
318
319def plot_over_time_and_profile(runs, names, file_name):
320 """!
321
322 Lapse next to the puncture over time (left) and along the x-axis at the
323 last snapshot all runs share (right)
324
325 """
326 fig, axes = plt.subplots(1, 2, figsize=(11, 4))
327 centre = VolumesPerAxis // 2
328 last = min(len(runs[name]["t"]) for name in names) - 1
329 for name in names:
330 colour, marker, label = Style[name]
331 run = runs[name]
332 axes[0].plot(
333 run["t"],
334 run["lapse"][:, centre],
335 marker + "-",
336 color=colour,
337 markersize=3,
338 linewidth=1,
339 label=label,
340 )
341 axes[1].plot(
342 run["x"],
343 run["lapse"][last],
344 marker + "-",
345 color=colour,
346 markersize=2,
347 linewidth=1,
348 label=label,
349 )
350 axes[0].set_xlabel("t")
351 axes[0].set_ylabel("lapse")
352 axes[0].set_title(
353 "Lapse next to the puncture (r={:.2f})".format(
354 radius(runs[names[0]]["x"][centre])
355 )
356 )
357 axes[1].set_xlabel("x (along y=z={:.2f})".format(H / 2.0))
358 axes[1].set_ylabel("lapse")
359 axes[1].set_title(
360 "Lapse along the x-axis at t={:.1f}".format(runs[names[0]]["t"][last])
361 )
362 # Runs which collapse write nonsensical values into their last snapshot
363 for ax in axes:
364 ax.set_ylim(0.0, 1.0)
365 axes[0].set_xlim(0.0, max(runs[name]["t"][-1] for name in names))
366 axes[1].legend(loc="lower right")
367 fig.savefig(os.path.join(Here, file_name), dpi=100, bbox_inches="tight")
368 plt.close(fig)
369
370
372 """!
373
374 Fourier analysis of the fourth-order central differences and of the
375 Kreiss-Oliger dissipation
376
377 A Fourier mode exp(i k x) on a grid with spacing h is characterised by
378 theta = k h between 0 (smooth) and pi (changes sign from grid cell to
379 grid cell). The left picture shows the wave number which the
380 fourth-order central difference "sees", i.e. theta_eff = (8 sin theta -
381 sin 2 theta) / 6, against the exact one. The right picture shows the
382 rate h/sigma * lambda with which the Kreiss-Oliger term
383 sigma/(64 h) * (Q_{i-3} - 6 Q_{i-2} + ... + Q_{i+3}) damps the mode
384 along one axis, i.e. (4 sin^2(theta/2))^3 / 64.
385
386 """
387 theta = np.linspace(0.0, np.pi, 400)
388 fig, axes = plt.subplots(1, 2, figsize=(11, 4))
389 axes[0].plot(theta, theta, "--", color="black", linewidth=1, label="exact")
390 axes[0].plot(
391 theta,
392 (8.0 * np.sin(theta) - np.sin(2.0 * theta)) / 6.0,
393 "-",
394 color="tab:red",
395 linewidth=2,
396 label="fourth-order central difference",
397 )
398 axes[0].set_xlabel(r"$\theta = k h$")
399 axes[0].set_ylabel("effective wave number times h")
400 axes[0].set_title("How the derivative sees a Fourier mode")
401 axes[0].legend(loc="upper left")
402 axes[1].semilogy(
403 theta[1:],
404 (4.0 * np.sin(theta[1:] / 2.0) ** 2) ** 3 / 64.0,
405 "-",
406 color="tab:green",
407 linewidth=2,
408 )
409 axes[1].set_xlabel(r"$\theta = k h$")
410 axes[1].set_ylabel(r"damping rate $\times\, h / \sigma$")
411 axes[1].set_title("Kreiss-Oliger damping per axis")
412 axes[1].set_ylim(1e-6, 2.0)
413 for ax in axes:
414 ax.set_xticks([0.0, np.pi / 4, np.pi / 2, 3 * np.pi / 4, np.pi])
415 ax.set_xticklabels(["0", r"$\pi/4$", r"$\pi/2$", r"$3\pi/4$", r"$\pi$"])
416 fig.savefig(
417 os.path.join(Here, "523_higher-order-fd-fourier.png"),
418 dpi=100,
419 bbox_inches="tight",
420 )
421 plt.close(fig)
422
423
424if __name__ == "__main__":
425 parser = argparse.ArgumentParser(
426 description="Create the PNGs of the tutorial 523_higher-order-fd.ipynb"
427 )
428 parser.add_argument(
429 "--work-directory",
430 default=None,
431 help="Directory for builds and outputs (default: temporary directory)",
432 )
433 parser.add_argument(
434 "--peano-root",
435 default=PeanoRoot,
436 help="Configured (--with-gsl) and built Peano installation to link against (default: the repository holding this script)",
437 )
438 parser.add_argument(
439 "--end-time", type=float, default=10.0, help="Simulated time span (default: 10)"
440 )
441 parser.add_argument(
442 "--runs",
443 nargs="+",
444 default=list(Runs),
445 help="Runs to perform (default: all, i.e. {})".format(" ".join(Runs)),
446 )
447 args = parser.parse_args()
448
450
451 with contextlib.ExitStack() as stack:
452 work_directory = args.work_directory or stack.enter_context(
453 tempfile.TemporaryDirectory()
454 )
455 runs = {}
456 for name in args.runs:
457 directory = os.path.join(os.path.abspath(work_directory), name)
458 runtime, success = build_and_run(
459 name, directory, os.path.abspath(args.peano_root), args.end_time
460 )
461 x, t, lapse, phi = profile(directory)
462 runs[name] = dict(x=x, t=t, lapse=lapse, phi=phi)
463 centre = VolumesPerAxis // 2
464 statistics = time_steps(directory)
465 print(
466 "{}: {}, runtime {:.1f}s, {}".format(
467 name,
468 "terminated" if success else "FAILED",
469 runtime,
470 (
471 "{} time steps of {:.3f}s".format(*statistics)
472 if statistics
473 else "no time step statistics"
474 ),
475 )
476 )
477 print(
478 "{}: snapshots until t={:.2f}, next to the puncture (r={:.3f}) lapse {:.4f} -> {:.4f}, phi {:.4f} -> {:.4f}, min lapse at t_end {:.4f}, NaN: {}".format(
479 name,
480 t[-1],
481 radius(x[centre]),
482 lapse[0, centre],
483 lapse[-1, centre],
484 phi[0, centre],
485 phi[-1, centre],
486 np.nanmin(lapse[-1]),
487 np.isnan(lapse).any(),
488 )
489 )
490 for i in [centre, centre + 5, centre + 15, VolumesPerAxis - 1]:
491 print(
492 "{}: r={:.2f}: lapse {}".format(
493 name,
494 radius(x[i]),
495 " ".join("{:.3f}".format(value) for value in lapse[::4, i]),
496 )
497 )
498
499 if "fv" in runs and "fd4" in runs:
501 runs, ["fv", "fd4"], "523_higher-order-fd-fv-vs-fd4.png"
502 )
503 ko_runs = [name for name in Style if name.startswith("fd4-ko") and name in runs]
504 if "fv" in runs and "fd4" in runs and ko_runs:
506 runs,
507 ["fv", "fd4"] + ko_runs,
508 "523_higher-order-fd-kreiss-oliger.png",
509 )
510 lopsided_runs = [
511 name for name in Style if name.startswith("fd4-lop") and name in runs
512 ]
513 if "fv" in runs and "fd4" in runs and lopsided_runs:
515 runs,
516 ["fv", "fd4"] + lopsided_runs,
517 "523_higher-order-fd-lopsided.png",
518 )
Solver for the Einstein equations in the first-order CCZ4 formulation.
Definition CCZ4Solver.py:16
Fourth-order Finite Differences with Kreiss-Oliger dissipation.
ExaHyPE 2 project.
Definition Project.py:15
Finite Volumes with a Rusanov Riemann solver and explicit Euler time stepping, realised over a block-...
Fourth-order central Finite Differences with explicit Runge-Kutta time stepping, realised over a bloc...
Probe the data within a Peano patch file at one point over time.
Definition PointProbe.py:37
plot_over_time_and_profile(runs, names, file_name)
Lapse next to the puncture over time (left) and along the x-axis at the last snapshot all runs share ...
time_steps(directory)
Number of time steps and average wall clock time per time step.
plot_fourier_analysis()
Fourier analysis of the fourth-order central differences and of the Kreiss-Oliger dissipation.
build_and_run(name, directory, peano_installation, end_time)
Generate, compile and run one setup within directory.
profile(directory)
Probe lapse and phi in the centre of each volume or grid cell along the line y=z=h/2,...
create_scheme(name, solver)
The numerical scheme, grid construction and plotter of one run.
exahype2.api.InitPointWisely single_black_hole(float mass=1.0)
Single Schwarzschild black hole at the origin.
This file is part of the ExaGRyPE project.
Definition __init__.py:1
Fourth-order Finite Differences with explicit Runge-Kutta time stepping over block-structured meshes.
Definition __init__.py:1
-lift-drop-statistics
-single-black-hole