Peano
Loading...
Searching...
No Matches
studies-single-black-hole.py
Go to the documentation of this file.
1"""
2
3Run the numerical scheme and the performance study of the single black hole
4
5The script drives single-black-hole.py: For each configuration of a study, it
6generates the code into its own subdirectory of --work-directory, builds it,
7runs the executable and appends one row to results-<study>.csv in the work
8directory. The rows hold the configuration, the wall time of the build, the
9wall time of the run, the exit code, the seconds in the setup and in the time
10stepping sweeps (from Peano's step report), the number of sweeps, the last
11time stamp and the log file of the run. The console output of every run goes
12into <configuration>/run-<threads>-threads-<repetition>.log.
13
14 python3 studies-single-black-hole.py schemes --variant profile
15 python3 studies-single-black-hole.py performance --variant profile --threads 1 2 4 8
16
17The studies are:
18
19- schemes: Which numerical schemes get through the evolution of the black
20 hole? Runs the Finite Volumes on the regular mesh and with one
21 additional level of adaptive mesh refinement around the puncture (two levels
22 need more than 11 GB and hours even for this radius), and the fourth-order Finite
23 Differences with Runge-Kutta orders 2 to 4, with and without lopsided
24 advection and Kreiss-Oliger dissipation. A configuration which blows up
25 yields a non-zero exit code or the wall time of the run is much shorter
26 than the others. The Finite Differences do not support adaptive mesh
27 refinement yet.
28- performance: How fast is the code? Runs the Finite Volumes on the regular
29 mesh and with one level of adaptive mesh refinement for all the task
30 graph compilers, and for every thread count passed in --threads. The
31 thread count is set through OMP_NUM_THREADS, i.e. it is meaningful for
32 OpenMP builds of Peano. Use --repetitions to see the noise.
33
34The studies write binary checkpoints every 10 time units and visualisation
35snapshots every 1 time unit, as single-black-hole.py does by default. Analyse
36the snapshots with probe-single-black-hole.py afterwards. Pass
37--extra-arguments "--checkpoint-interval 0 --plot-interval 0" to switch the
38output off, e.g. for timings.
39
40Both studies need a Peano build with GSL, as the initial data comes from
41TwoPunctures. Use --dry-run to only list the configurations.
42
43"""
44
45import argparse
46import csv
47import os
48import re
49import shlex
50import subprocess
51import time
52
53
54HERE = os.path.dirname(os.path.abspath(__file__))
55
56Compilers = ["sequential", "fusion-only", "producer-consumer-sequential", "producer-consumer-fusion"]
57
58# Name and arguments of single-black-hole.py for each configuration
59SchemeStudy = {
60 "fv-regular": "--scheme fv",
61 "fv-amr-1": "--scheme fv --amr-levels 1 --amr-radius 1",
62 "fd4-rk2": "--scheme fd4 --rk-order 2",
63 "fd4-rk3": "--scheme fd4 --rk-order 3",
64 "fd4-rk4": "--scheme fd4 --rk-order 4",
65 "fd4-rk4-lopsided": "--scheme fd4 --rk-order 4 --lopsided",
66 "fd4-rk4-lopsided-ko": "--scheme fd4 --rk-order 4 --lopsided --ko-sigma 8.0",
67 "fd4-rk4-amr-1": "--scheme fd4 --rk-order 4 --amr-levels 1 --amr-radius 1",
68}
69
70PerformanceStudy = {
71 f"{mesh}-{compiler}": f"--scheme fv {amr} --compiler {compiler}"
72 for compiler in Compilers
73 for mesh, amr in [("regular", ""), ("amr-1", "--amr-levels 1 --amr-radius 1")]
74}
75
76
78 parser = argparse.ArgumentParser(
79 description="Numerical scheme and performance study of the single black hole",
80 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
81 )
82 parser.add_argument("study", choices=["schemes", "performance"])
83 parser.add_argument(
84 "--variant",
85 choices=["debug", "profile", "production"],
86 default="profile",
87 help="Build variant of single-black-hole.py.",
88 )
89 parser.add_argument("--work-directory", default="studies")
90 parser.add_argument(
91 "--peano-directory",
92 default=os.path.abspath(os.path.join(HERE, "..", "..", "..")),
93 )
94 parser.add_argument(
95 "--threads",
96 type=int,
97 nargs="+",
98 default=[0],
99 help="Thread counts of the runs. 0 leaves the thread count to the runtime.",
100 )
101 parser.add_argument("--repetitions", type=int, default=1)
102 parser.add_argument(
103 "--timeout",
104 type=int,
105 default=None,
106 help="Seconds after which a run is killed.",
107 )
108 parser.add_argument(
109 "--only",
110 nargs="+",
111 default=None,
112 help="Names of the configurations to run. Default: all.",
113 )
114 parser.add_argument(
115 "--extra-arguments",
116 default="",
117 help="Additional arguments for single-black-hole.py, e.g. --end-time 1.",
118 )
119 parser.add_argument("--skip-build", action="store_true")
120 parser.add_argument("--dry-run", action="store_true")
121 return parser.parse_args()
122
123
124def build(name, configuration, arguments, directory):
125 """! Generate and compile one configuration. Returns the wall time or None if it failed."""
126 command = (
127 ["python3", os.path.join(HERE, "single-black-hole.py")]
128 + shlex.split(configuration)
129 + [
130 "--variant", arguments.variant,
131 "--verbosity", "text",
132 "--output-directory", directory,
133 "--peano-directory", arguments.peano_directory,
134 ]
135 + shlex.split(arguments.extra_arguments)
136 )
137 start = time.time()
138 with open(os.path.join(directory, "build.log"), "w") as log:
139 result = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT)
140 return time.time() - start if result.returncode == 0 else None
141
142
143def run(arguments, directory, threads, repetition):
144 """! Run the executable once. Returns (exit code, wall time)."""
145 environment = dict(os.environ)
146 if threads > 0:
147 environment["OMP_NUM_THREADS"] = str(threads)
148 log_name = f"run-{threads}-threads-{repetition}.log"
149 start = time.time()
150 with open(os.path.join(directory, log_name), "w") as log:
151 try:
152 result = subprocess.run(
153 ["./single-black-hole"],
154 cwd=directory,
155 env=environment,
156 stdout=log,
157 stderr=subprocess.STDOUT,
158 timeout=arguments.timeout,
159 )
160 exit_code = result.returncode
161 except subprocess.TimeoutExpired:
162 exit_code = "timeout"
163 return exit_code, time.time() - start, log_name
164
165
166def parse_log(path):
167 """! Metrics Peano's step report writes at the end of a run
168
169 Returns the seconds of the setup, the seconds spent in all time stepping
170 sweeps, the number of sweeps, and the final time stamp. Entries are None
171 if the run did not get that far.
172 """
173 setup = sweeps = sweep_seconds = final_time = None
174 with open(path, errors="replace") as log:
175 for line in log:
176 match = re.search(r"reportFinalResults\‍(\‍)\s+info\s+(\w+): ([0-9.e+-]+)s\s+\‍(avg=([0-9.e+-]+),#measurements=(\d+)", line)
177 if match:
178 if match.group(1) == "SetupSimulation":
179 setup = float(match.group(2))
180 elif match.group(1) == "TimeStep":
181 sweeps = int(match.group(4))
182 sweep_seconds = float(match.group(3)) * sweeps
183 match = re.search(r"t=([0-9.e+-]+), new dt=", line)
184 if match:
185 final_time = float(match.group(1))
186 return setup, sweep_seconds, sweeps, final_time
187
188
189def main():
190 arguments = parse_arguments()
191 study = SchemeStudy if arguments.study == "schemes" else PerformanceStudy
192 if arguments.only is not None:
193 unknown = set(arguments.only) - set(study)
194 if unknown:
195 raise SystemExit(f"Unknown configurations {sorted(unknown)}, available: {sorted(study)}")
196 study = {name: study[name] for name in arguments.only}
197
198 if arguments.dry_run:
199 for name, configuration in study.items():
200 print(f"{name}: single-black-hole.py {configuration} --variant {arguments.variant}")
201 return
202
203 os.makedirs(arguments.work_directory, exist_ok=True)
204 result_file = os.path.join(arguments.work_directory, f"results-{arguments.study}.csv")
205 with open(result_file, "w", newline="") as handle:
206 writer = csv.writer(handle)
207 writer.writerow(
208 ["name", "arguments", "threads", "repetition", "build_seconds", "exit_code", "run_seconds",
209 "setup_seconds", "time_stepping_seconds", "sweeps", "final_time", "log"]
210 )
211 for name, configuration in study.items():
212 directory = os.path.abspath(os.path.join(arguments.work_directory, name))
213 os.makedirs(directory, exist_ok=True)
214 build_seconds = None
215 if arguments.skip_build:
216 build_seconds = 0.0
217 else:
218 print(f"Build {name}")
219 build_seconds = build(name, configuration, arguments, directory)
220 if build_seconds is None:
221 print(f" build failed, see {directory}/build.log")
222 writer.writerow([name, configuration, "", "", "", "build failed", "", "", "", "", "", "build.log"])
223 handle.flush()
224 continue
225 for threads in arguments.threads:
226 for repetition in range(arguments.repetitions):
227 print(f"Run {name}, {threads} threads, repetition {repetition}")
228 exit_code, run_seconds, log_name = run(arguments, directory, threads, repetition)
229 print(f" exit code {exit_code}, {run_seconds:.1f}s")
230 metrics = parse_log(os.path.join(directory, log_name))
231 writer.writerow(
232 [name, configuration, threads, repetition, f"{build_seconds:.1f}",
233 exit_code, f"{run_seconds:.1f}", *metrics, log_name]
234 )
235 handle.flush()
236 print(f"Wrote {result_file}")
237
238
239if __name__ == "__main__":
240 main()
build(name, configuration, arguments, directory)
Generate and compile one configuration.
run(arguments, directory, threads, repetition)
Run the executable once.
parse_log(path)
Metrics Peano's step report writes at the end of a run.