Peano
Loading...
Searching...
No Matches
521_gauge-wave.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 521_gauge-wave.ipynb
6
7The script builds the gauge wave setup of the tutorial for several volume
8and patch sizes, runs each configuration over one period of the wave,
9extracts the lapse along the x-axis with Peano's PointProbe, and writes all
10pictures into the directory of this script. It also prints the convergence
11table which the tutorial discusses. Call it from anywhere:
12
13 python3 tutorials/exahype2/exagrype/illustrations/521_gauge-wave.py
14
15All builds and outputs go into a temporary directory (or --work-directory).
16If you pass a work directory, runs which have completed before are reused,
17i.e. you can rerun the script to redraw the pictures without recomputing.
18The whole study takes around two hours on an eight-core workstation, most of
19it spent on the three finest runs.
20
21"""
22import argparse
23import contextlib
24import csv
25import io
26import os
27import subprocess
28import sys
29import tempfile
30import time
31
32Here = os.path.dirname(os.path.abspath(__file__))
33PeanoRoot = os.path.abspath(os.path.join(Here, "..", "..", "..", ".."))
34sys.path.insert(0, os.path.join(PeanoRoot, "src"))
35
36import matplotlib
37
38matplotlib.use("Agg")
39import matplotlib.pyplot as plt
40import numpy as np
41
42import peano4.api
43import exahype2.api
44import exagrype.api
46
47
48EndTime = 1.0
49PatchFile = "solution-CCZ4.peano-patch-file"
50Amplitude = 0.1
51
52
58MaxVolumeSizes = {9: 0.12, 27: 0.04, 81: 0.013}
59
60
63Configurations = [
64 (9, 3),
65 (27, 3),
66 (81, 3),
67 (27, 9),
68 (81, 9),
69 (81, 27),
70]
71
72
73Reference = (27, 9)
74
75
76def exact_lapse(x, t):
77 """! Lapse of the gauge wave, i.e. sqrt(H) with H = 1 - A sin(2 pi (x-t))"""
78 return np.sqrt(1.0 - Amplitude * np.sin(2.0 * np.pi * (np.asarray(x) - t)))
79
80
81def build_and_run(volumes_per_axis, patch_size, directory, peano_installation):
82 """!
83
84 Generate, compile and run the tutorial setup within directory
85
86 Returns the runtime of the executable in seconds. If the directory holds
87 the result of a previous run, we do not rerun but return the stored
88 runtime. peano_installation is the configured and built Peano root
89 directory which the generated Makefile links against.
90
91 """
92 runtime_file = os.path.join(directory, "runtime.txt")
93 if os.path.exists(runtime_file):
94 with open(runtime_file) as f:
95 return float(f.read())
96
97 os.makedirs(directory, exist_ok=True)
98 print(
99 "build and run {} volumes per axis with patch size {} in {}".format(
100 volumes_per_axis, patch_size, directory
101 )
102 )
103 project = exahype2.api.Project(
104 namespace=["tutorials", "exagrype", "gaugewave"],
105 project_name="GaugeWave",
106 directory=directory,
107 executable="gauge-wave",
108 )
110 "CCZ4", initial_condition=exagrype.api.initialvalues.gauge_wave()
111 )
112 solver.numerical_scheme = exahype2.api.fv.RusanovWithExplicitEuler(
113 solver, patch_size
114 )
115 solver.grid_construction = exahype2.api.fv.SetupSimulationOverRegularGrid(
116 solver, volume_size=MaxVolumeSizes[volumes_per_axis]
117 )
118 solver.plotter = exahype2.api.fv.PlotAsVTKFile(
119 solver,
120 first_plot_time_stamp=0.0,
121 time_in_between_plots=0.05,
122 plotter_precision=10,
123 )
124 solver.plotter.add_plot_filter([0.0, 0.49, 0.49], [1.0, 0.02, 0.02])
125 project.add_solver(solver)
126 project.set_global_simulation_parameters(
127 dimensions=3,
128 offset=[0.0, 0.0, 0.0],
129 size=[1.0, 1.0, 1.0],
130 min_end_time=EndTime,
131 max_end_time=EndTime,
132 periodic_BC=[True, True, True],
133 )
134 project.set_Peano4_installation(
135 peano_installation, peano4.api.output.CompileMode.RELEASE
136 )
137 with contextlib.redirect_stdout(io.StringIO()):
138 peano4_project = project.generate_Peano4_project(
139 verbosity=peano4.api.LoggerVerbosity.TEXT
140 )
141 peano4_project.generate()
142
143 subprocess.run(
144 ["make", "-j"],
145 cwd=directory,
146 check=True,
147 stdout=subprocess.DEVNULL,
148 stderr=subprocess.DEVNULL,
149 )
150 start = time.time()
151 with open(os.path.join(directory, "run.log"), "w") as log:
152 subprocess.run(["./gauge-wave"], cwd=directory, check=True, stdout=log)
153 runtime = time.time() - start
154 with open(runtime_file, "w") as f:
155 f.write(str(runtime))
156 return runtime
157
158
159def probe(directory, point, output_file):
160 """!
161
162 Probe all snapshots at one point and return (time stamps, lapse)
163
164 This is the Python counterpart of src/tarch/plotter/api/probe.py.
165
166 """
168 file_name=os.path.join(directory, PatchFile),
169 point=point,
170 output_file=os.path.join(directory, output_file),
171 start=0,
172 end=sys.maxsize,
173 )
174 with contextlib.redirect_stdout(io.StringIO()):
175 point_probe.display()
176 with open(os.path.join(directory, output_file)) as csv_file:
177 rows = list(csv.DictReader(csv_file))
178 return (
179 np.array([float(row["time"]) for row in rows]),
180 np.array([float(row["lapse"]) for row in rows]),
181 )
182
183
184def wave_profile(directory, volumes_per_axis):
185 """!
186
187 Probe the lapse in the centre of each volume along the line y=z=0.5
188
189 Returns the x coordinates, the time stamps of all snapshots and a 2d
190 array lapse[snapshot,x].
191
192 """
193 x = (np.arange(volumes_per_axis) + 0.5) / volumes_per_axis
194 lapse = []
195 for i, x_i in enumerate(x):
196 t, lapse_over_time = probe(
197 directory, [x_i, 0.5, 0.5], "profile-{}.csv".format(i)
198 )
199 lapse.append(lapse_over_time)
200 return x, t, np.array(lapse).T
201
202
203def plot_mesh(directory, volumes_per_axis, patch_size):
204 """! Initial lapse along the x-axis with volumes and patches"""
205 x, t, lapse = wave_profile(directory, volumes_per_axis)
206 x_fine = np.linspace(0.0, 1.0, 400)
207 h = 1.0 / volumes_per_axis
208
209 fig, ax = plt.subplots(figsize=(8, 4))
210 ax.plot(x_fine, exact_lapse(x_fine, 0.0), "k-", linewidth=1, label="exact lapse")
211 ax.step(
212 np.append(x - 0.5 * h, 1.0),
213 np.append(lapse[0], lapse[0][-1]),
214 where="post",
215 color="tab:blue",
216 label="volume averages",
217 )
218 for patch in range(volumes_per_axis // patch_size + 1):
219 ax.axvline(patch * patch_size * h, color="tab:red", linewidth=1, linestyle="--")
220 ax.plot(
221 [], [], color="tab:red", linewidth=1, linestyle="--", label="patch boundaries"
222 )
223 ax.set_xlabel("x (along y=z=0.5)")
224 ax.set_ylabel("lapse")
225 ax.set_title(
226 "Initial lapse: {} volumes per axis, patch size {}".format(
227 volumes_per_axis, patch_size
228 )
229 )
230 ax.legend(loc="lower right")
231 fig.savefig(
232 os.path.join(Here, "521_gauge-wave-mesh.png"), dpi=100, bbox_inches="tight"
233 )
234 plt.close(fig)
235
236
237def plot_profile(directory, volumes_per_axis):
238 """! Lapse along the x-axis for three snapshots"""
239 x, t, lapse = wave_profile(directory, volumes_per_axis)
240 x_fine = np.linspace(0.0, 1.0, 400)
241
242 fig, ax = plt.subplots(figsize=(8, 4))
243 for snapshot, colour in zip(
244 [0, len(t) // 2, len(t) - 1], ["tab:blue", "tab:orange", "tab:green"]
245 ):
246 ax.plot(
247 x_fine, exact_lapse(x_fine, t[snapshot]), "-", color=colour, linewidth=1
248 )
249 ax.plot(
250 x,
251 lapse[snapshot],
252 "o",
253 color=colour,
254 markersize=3,
255 label="t={:.2f}".format(t[snapshot]),
256 )
257 ax.set_xlabel("x (along y=z=0.5)")
258 ax.set_ylabel("lapse")
259 ax.set_title("Lapse profile (lines: exact, dots: ExaGRyPE)")
260 ax.legend(loc="upper left", bbox_to_anchor=(1.01, 1.0))
261 fig.savefig(
262 os.path.join(Here, "521_gauge-wave-profile.png"), dpi=100, bbox_inches="tight"
263 )
264 plt.close(fig)
265
266
267def plot_probe(directory):
268 """! Lapse at the centre of the domain over time"""
269 t, lapse = probe(directory, [0.5, 0.5, 0.5], "probe.csv")
270 t_fine = np.linspace(0.0, EndTime, 400)
271
272 fig, ax = plt.subplots(figsize=(8, 4))
273 ax.plot(t_fine, exact_lapse(0.5, t_fine), "k-", linewidth=1, label="exact")
274 ax.plot(t, lapse, "o", markersize=3, label="PointProbe at (0.5,0.5,0.5)")
275 ax.set_xlabel("t")
276 ax.set_ylabel("lapse")
277 ax.set_title("Lapse at the centre of the domain over time")
278 ax.legend(loc="lower right")
279 fig.savefig(
280 os.path.join(Here, "521_gauge-wave-probe.png"), dpi=100, bbox_inches="tight"
281 )
282 plt.close(fig)
283
284
285def errors_over_time(directory, volumes_per_axis):
286 """!
287
288 Maximum and L2 error of the lapse along the x-axis for all snapshots
289
290 Returns the time stamps plus the two errors per snapshot.
291
292 """
293 x, t, lapse = wave_profile(directory, volumes_per_axis)
294 error = lapse - exact_lapse(x[np.newaxis, :], t[:, np.newaxis])
295 return t, np.max(np.abs(error), axis=1), np.sqrt(np.mean(error**2, axis=1))
296
297
298def plot_convergence(results):
299 """!
300
301 Maximum error over volume size for several points in time
302
303 The lines show patch size 3 at several time stamps. The markers for patch
304 sizes 9 and 27 are drawn for the final time stamp only.
305
306 """
307 fig, ax = plt.subplots(figsize=(7, 5))
308 times = [0.05, 0.2, 0.5, 1.0]
309 colours = ["tab:blue", "tab:orange", "tab:green", "tab:red"]
310 for time_stamp, colour in zip(times, colours):
311 data = sorted(
312 (volumes, values) for (volumes, p), values in results.items() if p == 3
313 )
314 h = np.array([1.0 / volumes for volumes, _ in data])
315 error = np.array(
316 [
317 values["max errors"][np.argmin(np.abs(values["t"] - time_stamp))]
318 for _, values in data
319 ]
320 )
321 ax.loglog(h, error, "o-", color=colour, label="t={}".format(time_stamp))
322 for patch_size, marker in [(9, "s"), (27, "^")]:
323 data = [
324 (1.0 / volumes, values["max errors"][-1])
325 for (volumes, p), values in results.items()
326 if p == patch_size
327 ]
328 ax.loglog(
329 [h for h, _ in data],
330 [e for _, e in data],
331 marker,
332 color="tab:red",
333 markerfacecolor="none",
334 markersize=10,
335 label="t=1.0, patch size {}".format(patch_size),
336 )
337 h = np.array([1.0 / 81, 1.0 / 9])
338 reference = results[(81, 3)]["max errors"][1]
339 ax.loglog(h, reference * h / h[0], "k--", linewidth=1, label="first order")
340 ax.set_xlabel("volume size h = 1 / (volumes per axis)")
341 ax.set_ylabel("max error of the lapse")
342 ax.set_title("Convergence of the Finite Volume scheme (lines: patch size 3)")
343 ax.legend(loc="upper left", bbox_to_anchor=(1.01, 1.0))
344 fig.savefig(
345 os.path.join(Here, "521_gauge-wave-convergence.png"),
346 dpi=100,
347 bbox_inches="tight",
348 )
349 plt.close(fig)
350
351
352def order(coarse_error, fine_error):
353 """! Estimated convergence order from two meshes whose volume sizes differ by a factor of three"""
354 return np.log(coarse_error / fine_error) / np.log(3.0)
355
356
357if __name__ == "__main__":
358 parser = argparse.ArgumentParser(
359 description="Create the PNGs of the tutorial 521_gauge-wave.ipynb"
360 )
361 parser.add_argument(
362 "--work-directory",
363 default=None,
364 help="Directory for builds and outputs (default: temporary directory)",
365 )
366 parser.add_argument(
367 "--peano-root",
368 default=PeanoRoot,
369 help="Configured and built Peano installation to link against (default: the repository holding this script)",
370 )
371 args = parser.parse_args()
372
373 with contextlib.ExitStack() as stack:
374 work_directory = args.work_directory or stack.enter_context(
375 tempfile.TemporaryDirectory()
376 )
377 work_directory = os.path.abspath(work_directory)
378
379 def directory_of(volumes_per_axis, patch_size):
380 return os.path.join(
381 work_directory, "h{}-p{}".format(volumes_per_axis, patch_size)
382 )
383
384 results = {}
385 for volumes_per_axis, patch_size in Configurations:
386 directory = directory_of(volumes_per_axis, patch_size)
387 runtime = build_and_run(
388 volumes_per_axis,
389 patch_size,
390 directory,
391 os.path.abspath(args.peano_root),
392 )
393 t, max_errors, l2_errors = errors_over_time(directory, volumes_per_axis)
394 results[(volumes_per_axis, patch_size)] = {
395 "runtime": runtime,
396 "t": t,
397 "max errors": max_errors,
398 "L2 errors": l2_errors,
399 }
400
401 reference_directory = directory_of(*Reference)
402 plot_mesh(reference_directory, *Reference)
403 plot_profile(reference_directory, Reference[0])
404 plot_probe(reference_directory)
405 plot_convergence(results)
406
407 print("Errors after the last time step:")
408 print(
409 "{:>8} {:>6} {:>8} {:>12} {:>8} {:>12} {:>8} {:>10}".format(
410 "volumes",
411 "patch",
412 "t",
413 "max error",
414 "order",
415 "L2 error",
416 "order",
417 "runtime",
418 )
419 )
420 for (volumes_per_axis, patch_size), values in sorted(
421 results.items(), key=lambda item: (item[0][1], item[0][0])
422 ):
423 coarser = results.get((volumes_per_axis // 3, patch_size))
424 order_max = order_l2 = float("nan")
425 if coarser is not None:
426 order_max = order(coarser["max errors"][-1], values["max errors"][-1])
427 order_l2 = order(coarser["L2 errors"][-1], values["L2 errors"][-1])
428 print(
429 "{:>8} {:>6} {:>8.4f} {:>12.4e} {:>8.2f} {:>12.4e} {:>8.2f} {:>9.1f}s".format(
430 volumes_per_axis,
431 patch_size,
432 values["t"][-1],
433 values["max errors"][-1],
434 order_max,
435 values["L2 errors"][-1],
436 order_l2,
437 values["runtime"],
438 )
439 )
440
441 print("Max errors of patch size 3 over time:")
442 print(
443 "{:>8} {:>12} {:>12} {:>12} {:>8} {:>8}".format(
444 "t", "h=1/9", "h=1/27", "h=1/81", "order", "order"
445 )
446 )
447 for snapshot in [1, 2, 4, 10, 20]:
448 errors = [
449 results[(volumes, 3)]["max errors"][snapshot] for volumes in [9, 27, 81]
450 ]
451 print(
452 "{:>8.3f} {:>12.4e} {:>12.4e} {:>12.4e} {:>8.2f} {:>8.2f}".format(
453 results[(81, 3)]["t"][snapshot],
454 *errors,
455 order(errors[0], errors[1]),
456 order(errors[1], errors[2]),
457 )
458 )
Solver for the Einstein equations in the first-order CCZ4 formulation.
Definition CCZ4Solver.py:16
ExaHyPE 2 project.
Definition Project.py:15
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...
Probe the data within a Peano patch file at one point over time.
Definition PointProbe.py:37
wave_profile(directory, volumes_per_axis)
Probe the lapse in the centre of each volume along the line y=z=0.5.
plot_convergence(results)
Maximum error over volume size for several points in time.
plot_probe(directory)
Lapse at the centre of the domain over time.
exact_lapse(x, t)
Lapse of the gauge wave, i.e.
errors_over_time(directory, volumes_per_axis)
Maximum and L2 error of the lapse along the x-axis for all snapshots.
build_and_run(volumes_per_axis, patch_size, directory, peano_installation)
Generate, compile and run the tutorial setup within directory.
plot_mesh(directory, volumes_per_axis, patch_size)
Initial lapse along the x-axis with volumes and patches.
order(coarse_error, fine_error)
Estimated convergence order from two meshes whose volume sizes differ by a factor of three.
directory_of(volumes_per_axis, patch_size)
plot_profile(directory, volumes_per_axis)
Lapse along the x-axis for three snapshots.
exahype2.api.InitPointWisely gauge_wave()
Gauge wave along the x-axis,.
This file is part of the ExaGRyPE project.
Definition __init__.py:1
-single-black-hole