5Create the PNG illustrations of the tutorial 522_single-black-hole.ipynb
7The script builds the single black hole setup of the tutorial, runs it,
8extracts the lapse and the conformal factor along the x-axis with Peano's
9PointProbe, and writes all pictures into the directory of this script. It
10runs three setups, one after the other, so their runtimes can be compared:
12- plain: the main run of the tutorial, i.e. exahype2.api.fv.RusanovWithExplicitEuler
13 with 6x6x6 patches and without load balancing;
14- decomposed: the run of the section "Speeding up", i.e. 10x10x10 patches,
15 a domain decomposition and four OpenMP threads;
16- decomposed-constrained: the same with the CCZ4 constraint enforcement
17 after each time step (section "Improving the physics").
19The pictures of the initial data and of the lapse stem from the plain run.
20522_single-black-hole-speeding-up.png compares the plain and the decomposed
21run, 522_single-black-hole-constraints.png the two decomposed runs. Call it
24 python3 tutorials/exahype2/exagrype/illustrations/522_single-black-hole.py
26Peano has to be configured with --with-gsl, as the initial data stems from
27TwoPunctures. All builds and outputs go into a temporary directory (or
28--work-directory). If you pass a work directory, a run which has completed
29before is reused, i.e. you can rerun the script to redraw the pictures
43Here = os.path.dirname(os.path.abspath(__file__))
44PeanoRoot = os.path.abspath(os.path.join(Here,
"..",
"..",
"..",
".."))
45sys.path.insert(0, os.path.join(PeanoRoot,
"src"))
50import matplotlib.pyplot
as plt
59PatchFile =
"solution-CCZ4.peano-patch-file"
65VolumesPerAxis = CellsPerAxis * PatchSize
66TimeInBetweenPlots = 0.5
70LineOffset = DomainSize / VolumesPerAxis / 2.0
74 """! Conformal factor of the Schwarzschild black hole in isotropic coordinates"""
75 return 1.0 + Mass / 2.0 / r
81DecomposedPatchSize = 10
86 (
"plain", PatchSize,
False,
None, exagrype.api.CCZ4Solver.EnforceCCZ4Constraint.No),
92 exagrype.api.CCZ4Solver.EnforceCCZ4Constraint.No,
95 "decomposed-constrained",
99 exagrype.api.CCZ4Solver.EnforceCCZ4Constraint.OnlyFinalSolution,
110 patch_size=PatchSize,
111 load_balancing=False,
116 Generate, compile and run the tutorial setup within directory
118 @param patch_size: int
119 Number of volumes per axis within one patch. The mesh always has
120 CellsPerAxis cells per axis.
122 @param load_balancing: bool
123 If True, decompose the domain with
124 toolbox::loadbalancing::strategies::SpreadOutOnceGridStagnates.
126 @param threads: int or None
127 If not None, run with OMP_NUM_THREADS set to this value.
130 Numerical scheme, i.e. a Finite Volume class from exahype2.api.fv.
132 @param enforce: exagrype.api.CCZ4Solver.EnforceCCZ4Constraint
133 Constraint enforcement.
135 Returns the runtime of the executable in seconds. If the directory holds
136 the result of a previous run, we do not rerun but return the stored
140 runtime_file = os.path.join(directory,
"runtime.txt")
141 if os.path.exists(runtime_file):
142 with open(runtime_file)
as f:
143 return float(f.read())
145 os.makedirs(directory, exist_ok=
True)
147 "build and run single black hole until t={} in {}".format(end_time, directory)
150 namespace=[
"tutorials",
"exagrype",
"singleblackhole"],
151 project_name=
"SingleBlackHole",
153 executable=
"single-black-hole",
157 parameters={
"CCZ4bs": 1.0,
"CCZ4sk": 1.0},
161 exahype2.api.DirichletBoundaryConditions(
162 implementation=
"::exagrype::initialvalues::singleBlackHole(Q, x, t, {});".format(
167 solver.numerical_scheme = scheme(solver, patch_size)
168 solver.enforce_ccz4_constraint(enforce)
170 solver, volume_size=1.01 * DomainSize / CellsPerAxis / patch_size
174 first_plot_time_stamp=0.0,
175 time_in_between_plots=TimeInBetweenPlots,
176 plotter_precision=10,
178 solver.plotter.add_plot_filter(
179 offset=[DomainOffset, 0.1, 0.1], size=[DomainSize, 0.1, 0.1]
181 project.add_solver(solver)
182 project.set_global_simulation_parameters(
184 offset=[DomainOffset] * 3,
185 size=[DomainSize] * 3,
186 min_end_time=end_time,
187 max_end_time=end_time,
188 periodic_BC=[
False,
False,
False],
191 project.set_load_balancing(
192 "toolbox::loadbalancing::strategies::SpreadOutOnceGridStagnates",
193 "new ::exahype2::LoadBalancingConfiguration()",
195 project.set_Peano4_installation(
196 peano_installation, peano4.api.output.CompileMode.RELEASE
198 with contextlib.redirect_stdout(io.StringIO()):
199 peano4_project = project.generate_Peano4_project(
200 verbosity=peano4.api.LoggerVerbosity.TEXT
202 peano4_project.generate()
208 stdout=subprocess.DEVNULL,
209 stderr=subprocess.DEVNULL,
212 with open(os.path.join(directory,
"run.log"),
"w")
as log:
213 environment = dict(os.environ)
214 if threads
is not None:
215 environment[
"OMP_NUM_THREADS"] = str(threads)
217 [
"./single-black-hole"],
223 runtime = time.time() - start
224 with open(runtime_file,
"w")
as f:
225 f.write(str(runtime))
229def probe(directory, point, output_file):
232 Probe all snapshots at one point
234 Returns the time stamps, the lapse and the conformal factor phi.
238 file_name=os.path.join(directory, PatchFile),
240 output_file=os.path.join(directory, output_file),
244 with contextlib.redirect_stdout(io.StringIO()):
245 point_probe.display()
246 with open(os.path.join(directory, output_file))
as csv_file:
247 rows = list(csv.DictReader(csv_file))
249 np.array([
float(row[
"time"])
for row
in rows]),
250 np.array([
float(row[
"lapse"])
for row
in rows]),
251 np.array([
float(row[
"phi"])
for row
in rows]),
255def profile(directory, volumes_per_axis=VolumesPerAxis):
258 Probe lapse and phi in the centre of each volume along the line through the
259 centres of the volumes next to the puncture, i.e. along y=z=h/2
261 Returns the x coordinates, the time stamps and two 2d arrays
262 lapse[snapshot,x] and phi[snapshot,x].
265 h = DomainSize / volumes_per_axis
266 x = DomainOffset + (np.arange(volumes_per_axis) + 0.5) * h
268 for i, x_i
in enumerate(x):
269 t, lapse_over_time, phi_over_time =
probe(
270 directory, [x_i, h / 2.0, h / 2.0],
"profile-{}.csv".format(i)
272 lapse.append(lapse_over_time)
273 phi.append(phi_over_time)
274 return x, t, np.array(lapse).T, np.array(phi).T
278 return np.sqrt(np.asarray(x) ** 2 + 2.0 * line_offset**2)
284 Number of time steps and average wall clock time per time step
286 Both stem from the final report of peano4::DefaultStepReport in run.log,
287 i.e. from the line "TimeStep: ...s (avg=...,#measurements=...".
290 with open(os.path.join(directory,
"run.log"))
as log:
292 if "reportFinalResults" in line
and "TimeStep:" in line:
293 statistics = line.split(
"(avg=")[1]
294 average =
float(statistics.split(
",")[0])
295 count = int(statistics.split(
"#measurements=")[1].split(
",")[0])
296 return count, average
297 raise RuntimeError(f
"no time step statistics in {directory}/run.log")
301 """! Lapse next to the puncture over time, and along the x-axis at the end, for both meshes"""
302 fig, axes = plt.subplots(1, 2, figsize=(11, 4))
303 for (label, (x, t, lapse, volumes_per_axis)), marker
in zip(
304 [(
"54 volumes per axis", coarse), (
"90 volumes per axis", fine)], [
"o",
"s"]
306 h = DomainSize / volumes_per_axis
307 centre = volumes_per_axis // 2
314 label=
"{}, r={:.2f}".format(label,
radius(x[centre], h / 2.0)),
316 axes[1].
plot(x, lapse[-1], marker +
"-", markersize=2, linewidth=1, label=label)
317 axes[0].set_xlabel(
"t")
318 axes[0].set_ylabel(
"lapse")
319 axes[0].set_title(
"Lapse next to the puncture")
320 axes[1].set_xlabel(
"x")
321 axes[1].set_ylabel(
"lapse")
322 axes[1].set_title(
"Lapse along the x-axis at t={:.0f}".format(coarse[1][-1]))
323 axes[0].legend(loc=
"lower right")
324 axes[1].legend(loc=
"lower right")
326 os.path.join(Here,
"522_single-black-hole-speeding-up.png"),
334 """! Initial lapse and conformal factor along the x-axis against the closed form"""
335 h = DomainSize / VolumesPerAxis
336 x_fine = np.linspace(DomainOffset, DomainOffset + DomainSize, 2000)
338 fig, ax = plt.subplots(figsize=(8, 4))
353 label=
r"$\psi^{-2}$",
355 ax.plot(x, lapse[0],
"o", color=
"tab:blue", markersize=3, label=
"lapse (ExaGRyPE)")
356 ax.plot(x, phi[0],
"s", color=
"tab:orange", markersize=3, label=
"phi (ExaGRyPE)")
357 for cell
in range(CellsPerAxis + 1):
359 DomainOffset + cell * PatchSize * h,
365 [], [], color=
"tab:red", linewidth=0.5, linestyle=
"--", label=
"patch boundaries"
367 ax.set_xlabel(
"x (along y=z={:.3f})".format(LineOffset))
368 ax.set_ylabel(
"initial value")
369 ax.set_title(
"Initial data of a single black hole with M={}".format(Mass))
370 ax.legend(loc=
"upper left", bbox_to_anchor=(1.01, 1.0))
372 os.path.join(Here,
"522_single-black-hole-initial-data.png"),
380 """! Lapse along the x-axis for several snapshots"""
381 fig, ax = plt.subplots(figsize=(8, 4))
382 snapshots = sorted(set([0, len(t) // 4, len(t) // 2, 3 * len(t) // 4, len(t) - 1]))
383 colours = plt.cm.viridis(np.linspace(0.0, 0.9, len(snapshots)))
384 for snapshot, colour
in zip(snapshots, colours):
392 label=
"t={:.2f}".format(t[snapshot]),
394 ax.set_xlabel(
"x (along y=z={:.3f})".format(LineOffset))
395 ax.set_ylabel(
"lapse")
396 ax.set_title(
"Lapse along the x-axis over time")
397 ax.legend(loc=
"upper left", bbox_to_anchor=(1.01, 1.0))
399 os.path.join(Here,
"522_single-black-hole-lapse-profile.png"),
407 """! Lapse over time at a few distances from the puncture"""
408 fig, ax = plt.subplots(figsize=(8, 4))
409 for x_probe
in [LineOffset, 1.0 + LineOffset, 3.0 + LineOffset, 8.0 + LineOffset]:
410 i = np.argmin(np.abs(x - x_probe))
417 label=
"r={:.2f}".format(
radius(x[i])),
420 ax.set_ylabel(
"lapse")
421 ax.set_title(
"Lapse over time at several distances from the puncture")
422 ax.legend(loc=
"upper left", bbox_to_anchor=(1.01, 1.0))
424 os.path.join(Here,
"522_single-black-hole-lapse-over-time.png"),
432 """! Lapse and phi next to the puncture over time with and without constraint enforcement"""
433 fig, axes = plt.subplots(1, 2, figsize=(11, 4))
434 for (label, run), marker
in zip(
435 [(
"without enforcement", unconstrained), (
"with enforcement", constrained)],
438 centre = run[
"volumes_per_axis"] // 2
441 run[
"lapse"][:, centre],
449 run[
"phi"][:, centre],
455 centre = unconstrained[
"volumes_per_axis"] // 2
456 r =
radius(unconstrained[
"x"][centre], unconstrained[
"h"] / 2.0)
457 for ax, quantity
in zip(axes, [
"lapse",
"phi"]):
459 ax.set_ylabel(quantity)
460 ax.set_title(
"{} next to the puncture (r={:.2f})".format(quantity, r))
461 axes[1].legend(loc=
"lower right")
463 os.path.join(Here,
"522_single-black-hole-constraints.png"),
470if __name__ ==
"__main__":
471 parser = argparse.ArgumentParser(
472 description=
"Create the PNGs of the tutorial 522_single-black-hole.ipynb"
477 help=
"Directory for builds and outputs (default: temporary directory)",
482 help=
"Configured (--with-gsl) and built Peano installation to link against (default: the repository holding this script)",
485 "--end-time", type=float, default=10.0, help=
"Simulated time span (default: 10)"
487 args = parser.parse_args()
489 with contextlib.ExitStack()
as stack:
490 work_directory = args.work_directory
or stack.enter_context(
491 tempfile.TemporaryDirectory()
494 for name, patch_size, load_balancing, threads, enforce
in Runs:
495 directory = os.path.join(os.path.abspath(work_directory), name)
498 os.path.abspath(args.peano_root),
502 patch_size=patch_size,
503 load_balancing=load_balancing,
506 volumes_per_axis = CellsPerAxis * patch_size
507 x, t, lapse, phi =
profile(directory, volumes_per_axis)
509 h = DomainSize / volumes_per_axis
517 volumes_per_axis=volumes_per_axis,
519 centre = volumes_per_axis // 2
521 "{}: runtime {:.1f}s, {} time steps of {:.3f}s, {} volumes, {:.3e}s per volume update".format(
527 average / volumes_per_axis**3,
531 "{}: next to the puncture (r={:.3f}) lapse {:.4f} -> {:.4f}, phi {:.4f} -> {:.4f}, NaN: {}".format(
533 radius(x[centre], h / 2.0),
538 np.isnan(lapse).any(),
542 "max deviation of decomposed-constrained from decomposed: lapse {:.3e}, phi {:.3e}".format(
545 runs[
"decomposed-constrained"][
"lapse"]
546 - runs[
"decomposed"][
"lapse"]
551 runs[
"decomposed-constrained"][
"phi"]
552 - runs[
"decomposed"][
"phi"]
561 runs[
"plain"][
"lapse"],
565 runs[
"decomposed"][
"x"],
566 runs[
"decomposed"][
"t"],
567 runs[
"decomposed"][
"lapse"],
568 runs[
"decomposed"][
"volumes_per_axis"],
573 x, t, lapse, phi = (runs[
"plain"][key]
for key
in [
"x",
"t",
"lapse",
"phi"])
574 print(
"snapshots at t =", np.round(t, 3))
577 VolumesPerAxis // 2 + 3,
578 VolumesPerAxis // 2 + 9,
582 "r={:.3f}: lapse(t=0)={:.4f} (1/psi={:.4f}), lapse(t={:.2f})={:.4f}, phi(t=0)={:.4f} (psi^-2={:.4f})".format(
592 print(
"NaN in lapse:", np.isnan(lapse).any())
Solver for the Einstein equations in the first-order CCZ4 formulation.
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.
plot_lapse_over_time(x, t, lapse)
Lapse over time at a few distances from the puncture.
radius(x, line_offset=LineOffset)
psi(r)
Conformal factor of the Schwarzschild black hole in isotropic coordinates.
profile(directory, volumes_per_axis=VolumesPerAxis)
Probe lapse and phi in the centre of each volume along the line through the centres of the volumes ne...
plot_constraints(unconstrained, constrained)
Lapse and phi next to the puncture over time with and without constraint enforcement.
build_and_run(directory, peano_installation, end_time, scheme, enforce, patch_size=PatchSize, load_balancing=False, threads=None)
Generate, compile and run the tutorial setup within directory.
plot_speeding_up(coarse, fine)
Lapse next to the puncture over time, and along the x-axis at the end, for both meshes.
plot_lapse_profile(x, t, lapse)
Lapse along the x-axis for several snapshots.
time_steps(directory)
Number of time steps and average wall clock time per time step.
plot_initial_data(x, lapse, phi)
Initial lapse and conformal factor along the x-axis against the closed form.
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.