121 parser = argparse.ArgumentParser(
122 description=
"Single Schwarzschild black hole with ExaGRyPE",
123 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
126 scheme = parser.add_argument_group(
"numerical scheme")
129 choices=[
"fv",
"fd4"],
131 help=
"fv: Rusanov Finite Volumes with explicit Euler. fd4: fourth-order Finite Differences with Runge-Kutta.",
136 choices=[1, 2, 3, 4],
138 help=
"Order of the Runge-Kutta time integrator (fd4 only).",
144 help=
"Scaling of the Kreiss-Oliger dissipation (fd4 only). 0 switches the dissipation off.",
152 help=
"Indices of the unknowns which are damped by the Kreiss-Oliger dissipation. Default: all.",
157 help=
"Add the lop-sided advection terms along the shift (fd4 only).",
160 "--enforce-constraints",
161 choices=list(EnforceModes),
163 help=
"When to push the solution back onto the algebraic CCZ4 constraints.",
166 mesh = parser.add_argument_group(
"domain and mesh")
171 help=
"Edge length of the domain. Default: depends on --variant.",
177 help=
"Maximum size of a volume or grid cell. The mesh is a regular 3-adic grid, i.e. the actual size is smaller. Default: depends on --variant.",
183 help=
"Number of additional mesh refinement levels (factor 3 each) around the black hole. 0 yields the regular mesh.",
189 help=
"Radius around the puncture within which the mesh is refined to the finest level.",
195 help=
"Volumes/grid cells per axis per mesh cell. Has to be even, so no volume centre hits the puncture.",
197 mesh.add_argument(
"--mass", type=float, default=1.0, help=
"Mass of the black hole.")
199 time = parser.add_argument_group(
"time and checkpoints")
204 help=
"Final time. Default: depends on --variant.",
207 "--checkpoint-interval",
210 help=
"Time between two binary checkpoints. 0 switches checkpointing off.",
216 help=
"Time between two visualisation snapshots. 0 switches the snapshots off.",
219 "--first-checkpoint",
222 help=
"Time stamp of the first checkpoint and of the first snapshot.",
225 "--checkpoint-precision",
228 help=
"Significant digits per value in the visualisation snapshots. Checkpoints are lossless.",
231 balancing = parser.add_argument_group(
"load balancing")
232 balancing.add_argument(
234 choices=[
"hardcoded",
"spread-out"],
236 help=
"hardcoded: decomposition of the regular mesh, computed offline. spread-out: SpreadOutOnceGridStagnates decides at runtime.",
238 balancing.add_argument(
240 choices=[
"equidistant",
"quantised"],
241 default=
"equidistant",
242 help=
"Chunk sizes of the hardcoded load balancing. equidistant: every tree gets the same number of cells. quantised: chunk sizes are multiples of 3^(k*d) cells, i.e. trees hold (almost) complete subtrees and few virtual cells, but they might be imbalanced.",
244 balancing.add_argument(
246 choices=[
"bottom-up",
"top-down",
"inferred"],
248 help=
"How Peano picks the cells of a split (hardcoded load balancing only).",
250 balancing.add_argument(
253 default=os.cpu_count(),
254 help=
"Number of trees (subdomains) of the hardcoded load balancing. They all live on rank 0, i.e. one tree per thread.",
257 build = parser.add_argument_group(
"build")
260 choices=list(Compilers),
261 default=
"producer-consumer-fusion",
262 help=
"Task graph compiler, i.e. how time step tasks are mapped onto mesh traversals.",
266 choices=list(Variants),
267 default=
"production",
268 help=
"Build variant: compile mode and default size of the setup.",
271 "--verbosity", choices=list(Verbosities), default=
"text"
274 "--output-directory",
276 help=
"Directory the code is generated into and the checkpoints are written to.",
281 help=
"Root of the Peano installation.",
286 help=
"Generate the code but do not compile it.",
291 help=
"Do not run make clean before the build.",
294 arguments = parser.parse_args()
296 for key, value
in Variants[arguments.variant].items():
297 if key !=
"mode" and getattr(arguments, key)
is None:
298 setattr(arguments, key, value)
300 if arguments.patch_size % 2 != 0:
301 parser.error(
"--patch-size has to be even")
302 if arguments.scheme ==
"fv" and (arguments.ko_sigma > 0.0
or arguments.lopsided):
303 parser.error(
"--ko-sigma and --lopsided require --scheme fd4")
304 if arguments.ko_quantities
is not None and arguments.ko_sigma <= 0.0:
305 parser.error(
"--ko-quantities requires --ko-sigma > 0")
306 if arguments.amr_levels < 0:
307 parser.error(
"--amr-levels must not be negative")
308 if arguments.amr_levels > 0
and arguments.scheme ==
"fd4" and arguments.ko_sigma > 0.0:
309 parser.error(
"--amr-levels with --scheme fd4 does not support --ko-sigma yet")
310 if arguments.load_balancing ==
"hardcoded" and arguments.trees < 2:
311 parser.error(
"--trees has to be at least 2 for the hardcoded load balancing")
312 if arguments.checkpoint_interval < 0.0
or arguments.plot_interval < 0.0:
313 parser.error(
"--checkpoint-interval and --plot-interval must not be negative")
399def min_volume_size(arguments):
400 """! Volumes of this size or bigger may still be refined.
402 The regular mesh of --volume-size is the coarsest mesh the benchmark
403 allows (README.dox), so the AMR levels sit on top of its actual volume
404 size. Refining a cell yields volumes strictly smaller than the bound, so
405 the bound is the volume size of the second-finest level, slightly
406 reduced to be robust against rounding. Without AMR levels, the bound
407 would exceed the --volume-size. It is capped there, which is still above the
408 volume size of the regular mesh, i.e. the mesh stays regular.
411 actual_volume_size(arguments) / 3.0 ** (arguments.amr_levels - 1) * 0.99,
412 arguments.volume_size,
426def create_load_balancing(arguments):
427 """! Name and constructor arguments of the load balancing strategy
429 The hardcoded strategy splits the regular mesh into arguments.trees trees
430 of the same number of cells, i.e. the outcome SpreadOutOnceGridStagnates
431 aims for. The splits are computed offline on a synthetic regular tree
432 along the space-filling curve, and all trees live on rank 0. AMR levels
433 do not enter, as the splits fire once the mesh has reached the regular
434 mesh and are bottom-up.
436 if arguments.load_balancing == "spread-out":
438 "toolbox::loadbalancing::strategies::SpreadOutOnceGridStagnates",
439 "new ::exahype2::LoadBalancingConfiguration()",
442 tree = RegularGridGenerator.RegularGridGenerator(
443 max_level=regular_mesh_levels(arguments), dimensions=3
445 if arguments.decomposition == "quantised":
446 decomposition = Decomposition.quantised_splitting(
447 peano_tree=tree, number_of_subtrees=arguments.trees, dimension=3
450 decomposition = Decomposition.equidistant_splitting(
451 peano_tree=tree, number_of_subtrees=arguments.trees
453 SplitTrees.split_tree(
454 tree, decomposition=decomposition, periodic_BC=[False, False, False]
456 destination_ranks = Hardcoded.assign_trees_to_ranks_round_Robin(
457 number_of_subtrees=tree.number_of_subtrees, max_ranks=1
459 configuration, _ = Hardcoded.create_constructor_initialisation_string(
460 tree, destination_ranks, modes=SplitModes[arguments.split_mode]
462 return "toolbox::loadbalancing::strategies::Hardcoded", configuration
466 arguments = parse_arguments()
468 project = exahype2.api.Project(
469 namespace=["benchmarks", "exagrype", "singleblackhole"],
470 project_name="single-black-hole",
471 directory=arguments.output_directory,
472 executable="single-black-hole",
475 solver = exagrype.api.CCZ4Solver(
477 parameters={"CCZ4bs": 1.0, "CCZ4sk": 1.0},
478 initial_condition=exagrype.api.initialvalues.single_black_hole(
482 solver.enforce_ccz4_constraint(EnforceModes[arguments.enforce_constraints])
484 exahype2.api.SommerfeldBoundaryConditions(
485 implementation="::exagrype::initialvalues::flat(Q, x, 0.0);"
488 # The criterion never asks for an erase. The mesh is a function of the
489 # position only, i.e. it is static, and it never coarsens below the
490 # regular mesh anyway. Without AMR levels, the volume size bound keeps
493 exahype2.api.RefineErasePointwisely(
495 const double r2 = tarch::la::norm2Squared(x);
496 if (r2 < {arguments.amr_radius ** 2}) *refinementCommand = ::exahype2::RefinementCommand::Refine;
500 create_numerical_scheme(solver, arguments)
501 project.add_solver(solver)
503 project.compiler = Compilers[arguments.compiler]
505 half_size = arguments.domain_size / 2.0
506 project.set_global_simulation_parameters(
508 offset=[-half_size] * 3,
509 size=[arguments.domain_size] * 3,
510 min_end_time=arguments.end_time,
511 max_end_time=arguments.end_time,
512 periodic_BC=[False, False, False],
515 project.set_load_balancing(*create_load_balancing(arguments))
517 project.set_Peano4_installation(
518 arguments.peano_directory, Variants[arguments.variant]["mode"]
520 peano4_project = project.generate_Peano4_project(
521 verbosity=Verbosities[arguments.verbosity]
523 peano4_project.generate()
525 h = actual_volume_size(arguments)
527 f"Volume size {h:.6g} ({round(arguments.domain_size / h)} volumes per axis), "
528 f"scheme {arguments.scheme}, {arguments.amr_levels} AMR levels, "
529 f"variant {arguments.variant}, compiler {arguments.compiler}, "
530 f"end time {arguments.end_time}"
533 if not arguments.no_build:
534 peano4_project.build(make_clean_first=not arguments.no_clean)