24 parser = argparse.ArgumentParser(description=__doc__)
25 parser.add_argument(
"--ep", type=Path, required=
True)
26 parser.add_argument(
"--mpi", default=
"mpiexec")
27 parser.add_argument(
"--mpi-arg", action=
"append", default=[],
28 help=
"launcher argument; repeat, e.g. --mpi-arg=--oversubscribe")
29 parser.add_argument(
"--ranks", type=int, choices=(1, 2), default=1)
30 parser.add_argument(
"--repetitions", type=int, default=3,
31 help=
"measured repetitions after one warm-up (default: 3)")
32 parser.add_argument(
"--output", type=Path, required=
True,
33 help=
"new or empty directory for all run artifacts")
34 parser.add_argument(
"--fixture", type=Path)
35 parser.add_argument(
"--examples", type=Path)
36 parser.add_argument(
"--param-file", type=Path)
37 args = parser.parse_args()
38 if args.repetitions < 1:
39 parser.error(
"--repetitions must be positive")
40 args.ep = args.ep.expanduser().resolve()
41 args.fixture = (args.fixture
or args.ep.parent /
42 "ep_cook_auxiliary_logarithmic_stress.json").expanduser().resolve()
43 args.examples = (args.examples
or args.ep.parent /
"examples").expanduser().resolve()
44 args.param_file = (args.param_file
or args.ep.parent /
45 "param_file.petsc").expanduser().resolve()
46 for path
in (args.ep, args.fixture, args.param_file):
47 if not path.is_file():
48 parser.error(f
"required file does not exist: {path}")
49 if not os.access(args.ep, os.X_OK):
50 parser.error(f
"ep is not executable: {args.ep}")
51 if not args.examples.is_dir():
52 parser.error(f
"examples directory does not exist: {args.examples}")
53 launcher = shutil.which(args.mpi)
55 parser.error(f
"MPI launcher is not executable: {args.mpi}")
56 args.mpi = str(Path(launcher).resolve())
57 args.output = args.output.expanduser().resolve()
58 if args.output.exists()
and (
not args.output.is_dir()
or any(args.output.iterdir())):
59 parser.error(
"--output must be a new or empty directory")
76 configuration = copy.deepcopy(configuration)
77 mesh =
"cook.cub" if ranks == 1
else "cook_2p.h5m"
78 configuration[
"meshes"][0][
"file_name"] = f
"examples/cook_cantilever/{mesh}"
79 configuration[
"mofem"][
"auxiliary_logarithmic_stress"] = case !=
"direct_condensed"
80 elastic = configuration[
"petsc"][
"elastic"]
81 elastic[
"snes"][
"converged_reason"] =
True
82 elastic[
"mat_mumps_icntl_20"] = 0
83 elastic[
"mat_mumps_icntl_14"] = 800
84 elastic[
"ksp"] = {
"type":
"preonly",
"converged_reason":
True}
85 elastic[
"pc"] = {
"type":
"lu",
"factor_mat_solver_type":
"mumps"}
86 if case.endswith(
"_condensed"):
87 elastic[
"ksp"].update(type=
"fgmres", rtol=1e-12, atol=1e-12)
88 elastic[
"pc"] = {
"type":
"fieldsplit",
"fieldsplit_type":
"schur"}
89 elastic[
"fieldsplit_0"] = {
"ksp": {
"type":
"preonly"}}
90 elastic[
"fieldsplit_1"] = {
91 "pc": {
"type":
"ksp"},
94 "pc": {
"type":
"lu",
"factor_mat_solver_type":
"mumps"},
95 "mat_mumps_icntl_20": 0,
96 "mat_mumps_icntl_14": 800,
132def run_case(args, case, configuration, environment, summary):
133 case_directory = args.output / case
134 case_directory.mkdir()
135 write_json(case_directory /
"input.json", configuration)
136 mesh = args.examples / Path(configuration[
"meshes"][0][
"file_name"]).relative_to(
"examples")
139 "configuration_sha256":
file_hash(case_directory /
"input.json"),
144 summary[
"cases"].append(case_record)
145 for repetition
in range(args.repetitions + 1):
146 run_name =
"warmup" if repetition == 0
else f
"run_{repetition}"
147 directory = case_directory / run_name
149 (directory /
"examples").symlink_to(args.examples, target_is_directory=
True)
150 shutil.copy2(case_directory /
"input.json", directory /
"input.json")
151 shutil.copy2(args.output /
"param_file.petsc", directory /
"param_file.petsc")
153 args.mpi, *args.mpi_arg,
"-np", str(args.ranks), str(args.output /
"ep"),
154 "-json_config",
"input.json",
"-log_view",
":petsc_profile.txt",
155 "-elastic_snes_view_solution",
"binary:accepted.vec",
157 record = {
"directory": str(directory),
"warmup": repetition == 0,
159 case_record[
"runs"].append(record)
160 start = time.monotonic()
161 with (directory /
"run.log").open(
"w")
as output:
162 result = subprocess.run(command, cwd=directory, env=environment,
163 stdout=output, stderr=subprocess.STDOUT)
164 record.update(exit_code=result.returncode, elapsed_seconds=time.monotonic() - start)
165 record.update(
log_diagnostics((directory /
"run.log").read_text(errors=
"replace")))
166 for artifact
in (
"run.log",
"petsc_profile.txt",
"accepted.vec"):
167 path = directory / artifact
169 record.setdefault(
"artifact_sha256", {})[artifact] =
file_hash(path)
171 write_json(args.output /
"profile.json", summary)
172 if result.returncode:
173 raise RuntimeError(f
"{case}/{run_name} failed ({result.returncode}); see {directory / 'run.log'}")
174 if not all((directory / name).is_file()
for name
in (
"petsc_profile.txt",
"accepted.vec")):
175 raise RuntimeError(f
"{case}/{run_name} did not write its PETSc profile and final vector")
177 print(f
"{case} {run_name}: {record['elapsed_seconds']:.3f} s", flush=
True)
178 case_record[
"median_elapsed_seconds"] = statistics.median(
179 run[
"elapsed_seconds"]
for run
in case_record[
"runs"]
if not run[
"warmup"])
180 write_json(args.output /
"profile.json", summary)
185 configuration = json.loads(args.fixture.read_text())
186 args.output.mkdir(parents=
True, exist_ok=
True)
187 shutil.copy2(args.ep, args.output /
"ep")
188 shutil.copy2(args.fixture, args.output /
"fixture.json")
189 shutil.copy2(args.param_file, args.output /
"param_file.petsc")
190 environment = dict(os.environ)
192 thread_options = {name:
"1" for name
in (
193 "OMP_NUM_THREADS",
"OPENBLAS_NUM_THREADS",
"MKL_NUM_THREADS",
194 "BLIS_NUM_THREADS",
"VECLIB_MAXIMUM_THREADS")}
195 environment.update(thread_options)
197 "ranks": args.ranks,
"measured_repetitions": args.repetitions,
198 "source_executable": str(args.ep),
"thread_environment": thread_options,
199 "input_sha256": {name:
file_hash(args.output / name)
200 for name
in (
"ep",
"fixture.json",
"param_file.petsc")},
201 "vector_contents":
"One final accepted solution per run; PETSc overwrites earlier steps.",
204 write_json(args.output /
"profile.json", summary)
205 for case
in (
"auxiliary_explicit",
"auxiliary_condensed",
"direct_condensed"):
207 environment, summary)
208 print(f
"Profiles and median timings: {args.output / 'profile.json'}")
214 except (OSError, ValueError, KeyError, RuntimeError)
as error: