5Create the PNG illustrations of the tutorial 523_higher-order-fd.ipynb
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:
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
25- fd4-lop-ko-8.0-subset: the same with the dissipation on Q0-Q22 and
26 Q53-Q54 only (DissipationSubset).
28The dictionary Runs holds the configurations.
32 python3 tutorials/exahype2/exagrype/illustrations/523_higher-order-fd.py
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.
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"))
59import matplotlib.pyplot
as plt
68PatchFile =
"solution-CCZ4.peano-patch-file"
74VolumesPerAxis = CellsPerAxis * PatchSize
75H = DomainSize / VolumesPerAxis
77TimeInBetweenPlots = 0.5
81DissipationSubset = list(range(0, 23)) + [53, 54]
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),
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": (
108 r"FD4, lopsided, KO $\sigma=8$ (Q0-Q22, Q53-Q54)",
114 """! The numerical scheme, grid construction and plotter of one run"""
122 sigma, lopsided, dissipative_quantities = Runs[name]
123 Kernel = exahype2.api.rkfd4.CentralDifferencesWithRungeKutta.RightHandSideKernel
127 right_hand_side_kernel=Kernel.CentralDifferencesWithLopsidedAdvection,
128 advection_velocity=exagrype.api.CCZ4Solver.ADVECTION_VELOCITY,
129 advected_quantities=solver.lopsided_advection_quantities(),
132 solver.numerical_scheme = (
134 solver, PatchSize, rk_order=4, **arguments
142 kreiss_oliger_sigma=sigma,
143 quantities=dissipative_quantities,
146 solver.grid_construction = package.SetupSimulationOverRegularGrid(
147 solver, volume_size=1.01 * H
149 solver.plotter = package.PlotAsVTKFile(
151 first_plot_time_stamp=0.0,
152 time_in_between_plots=TimeInBetweenPlots,
153 plotter_precision=10,
155 solver.plotter.add_plot_filter(
156 offset=[DomainOffset, 0.1, 0.1], size=[DomainSize, 0.1, 0.1]
163 Generate, compile and run one setup within directory
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.
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"
176 os.makedirs(directory, exist_ok=
True)
177 print(
"build and run {} until t={} in {}".format(name, end_time, directory))
179 namespace=[
"tutorials",
"exagrype",
"higherorderfd"],
180 project_name=
"HigherOrderFD",
182 executable=
"single-black-hole",
186 parameters={
"CCZ4bs": 1.0,
"CCZ4sk": 1.0},
190 exahype2.api.SommerfeldBoundaryConditions(
191 implementation=
"::exagrype::initialvalues::flat(Q, x, 0.0);"
195 project.add_solver(solver)
196 project.set_global_simulation_parameters(
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],
204 project.set_load_balancing(
205 "toolbox::loadbalancing::strategies::SpreadOutOnceGridStagnates",
206 "new ::exahype2::LoadBalancingConfiguration()",
208 project.set_Peano4_installation(
209 peano_installation, peano4.api.output.CompileMode.RELEASE
211 with contextlib.redirect_stdout(io.StringIO()):
212 peano4_project = project.generate_Peano4_project(
213 verbosity=peano4.api.LoggerVerbosity.TEXT
215 peano4_project.generate()
218 [
"make",
"-j{}".format(Threads)],
221 stdout=subprocess.DEVNULL,
222 stderr=subprocess.DEVNULL,
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"],
232 stderr=subprocess.STDOUT,
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
242def probe(directory, point, output_file):
245 Probe all snapshots at one point
247 Returns the time stamps, the lapse and the conformal factor phi.
251 file_name=os.path.join(directory, PatchFile),
253 output_file=os.path.join(directory, output_file),
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))
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]),
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
274 Returns the x coordinates, the time stamps and two 2d arrays
275 lapse[snapshot,x] and phi[snapshot,x].
278 x = DomainOffset + (np.arange(VolumesPerAxis) + 0.5) * H
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)
284 lapse.append(lapse_over_time)
285 phi.append(phi_over_time)
286 snapshots = min(len(entry)
for entry
in lapse)
290 np.array([entry[:snapshots]
for entry
in lapse]).T,
291 np.array([entry[:snapshots]
for entry
in phi]).T,
296 return np.sqrt(np.asarray(x) ** 2 + 2.0 * (H / 2.0) ** 2)
302 Number of time steps and average wall clock time per time step
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.
309 with open(os.path.join(directory,
"run.log"))
as 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
322 Lapse next to the puncture over time (left) and along the x-axis at the
323 last snapshot all runs share (right)
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
330 colour, marker, label = Style[name]
334 run[
"lapse"][:, centre],
350 axes[0].set_xlabel(
"t")
351 axes[0].set_ylabel(
"lapse")
353 "Lapse next to the puncture (r={:.2f})".format(
354 radius(runs[names[0]][
"x"][centre])
357 axes[1].set_xlabel(
"x (along y=z={:.2f})".format(H / 2.0))
358 axes[1].set_ylabel(
"lapse")
360 "Lapse along the x-axis at t={:.1f}".format(runs[names[0]][
"t"][last])
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")
374 Fourier analysis of the fourth-order central differences and of the
375 Kreiss-Oliger dissipation
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.
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")
392 (8.0 * np.sin(theta) - np.sin(2.0 * theta)) / 6.0,
396 label=
"fourth-order central difference",
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")
404 (4.0 * np.sin(theta[1:] / 2.0) ** 2) ** 3 / 64.0,
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)
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$"])
417 os.path.join(Here,
"523_higher-order-fd-fourier.png"),
424if __name__ ==
"__main__":
425 parser = argparse.ArgumentParser(
426 description=
"Create the PNGs of the tutorial 523_higher-order-fd.ipynb"
431 help=
"Directory for builds and outputs (default: temporary directory)",
436 help=
"Configured (--with-gsl) and built Peano installation to link against (default: the repository holding this script)",
439 "--end-time", type=float, default=10.0, help=
"Simulated time span (default: 10)"
445 help=
"Runs to perform (default: all, i.e. {})".format(
" ".join(Runs)),
447 args = parser.parse_args()
451 with contextlib.ExitStack()
as stack:
452 work_directory = args.work_directory
or stack.enter_context(
453 tempfile.TemporaryDirectory()
456 for name
in args.runs:
457 directory = os.path.join(os.path.abspath(work_directory), name)
459 name, directory, os.path.abspath(args.peano_root), args.end_time
462 runs[name] = dict(x=x, t=t, lapse=lapse, phi=phi)
463 centre = VolumesPerAxis // 2
466 "{}: {}, runtime {:.1f}s, {}".format(
468 "terminated" if success
else "FAILED",
471 "{} time steps of {:.3f}s".format(*statistics)
473 else "no time step statistics"
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(
486 np.nanmin(lapse[-1]),
487 np.isnan(lapse).any(),
490 for i
in [centre, centre + 5, centre + 15, VolumesPerAxis - 1]:
492 "{}: r={:.2f}: lapse {}".format(
495 " ".join(
"{:.3f}".format(value)
for value
in lapse[::4, i]),
499 if "fv" in runs
and "fd4" in runs:
501 runs, [
"fv",
"fd4"],
"523_higher-order-fd-fv-vs-fd4.png"
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:
507 [
"fv",
"fd4"] + ko_runs,
508 "523_higher-order-fd-kreiss-oliger.png",
511 name
for name
in Style
if name.startswith(
"fd4-lop")
and name
in runs
513 if "fv" in runs
and "fd4" in runs
and lopsided_runs:
516 [
"fv",
"fd4"] + lopsided_runs,
517 "523_higher-order-fd-lopsided.png",
Solver for the Einstein equations in the first-order CCZ4 formulation.
Fourth-order Finite Differences with Kreiss-Oliger dissipation.
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.
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.
Fourth-order Finite Differences with explicit Runge-Kutta time stepping over block-structured meshes.