1"""Put the results of run.py side by side.
3 python compare.py [results_dir]
5Prints a table of all runs found in results_dir (default results/) and, for
6every case run with both backends, the speedup and the relative difference of
7the wall fields. The table is also written to results_dir/comparison.md.
8"""
10import glob
11import json
12import os
13import sys
15import numpy as np
18def fmt(x, spec):
19 return "" if x is None else format(x, spec)
22def main():
23 rdir = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")
24 runs = {}
25 for f in sorted(glob.glob(os.path.join(rdir, "*", "summary.json"))):
26 s = json.load(open(f))
27 s["dir"] = os.path.dirname(f)
28 runs[(s["case"], s["backend"])] = s
29 if not runs:
30 print(f"no results in {rdir}")
31 return
33 lines = ["| case | k | points | backend | machine | setup (s) | solve (s) | setup + solve (s) | "
34 "iterations | self-test error | energy mismatch |",
35 "|---|---|---|---|---|---|---|---|---|---|---|"]
36 order = {"small": 0, "medium": 1, "large": 2}
37 for (case, backend), s in sorted(runs.items(), key=lambda kv: (order.get(kv[0][0], 9), kv[0][1])):
38 m = s["machine"]
39 where = m.get("gpu", "").split(",")[0] if backend == "gpu" else f"{m.get('cpu', '?')}, {m.get('cpu_count')} threads"
40 lines.append(f"| {case} | {s['k']:g} | {s['npts']:,} | {backend} | {where} | {s['setup_s']:.2f} | "
41 f"{s['solve_s']:.2f} | {s['setup_plus_solve_s']:.2f} | {s['niter']} | "
42 f"{fmt(s.get('selftest_err'), '.1e')} | {s['energy_mismatch']:.1e} |")
44 lines += ["", "| case | speedup (setup + solve) | wall field difference |", "|---|---|---|"]
45 for case in sorted({c for c, _ in runs}, key=lambda c: order.get(c, 9)):
46 if (case, "gpu") in runs and (case, "fmm3dbie") in runs:
47 g, c = runs[(case, "gpu")], runs[(case, "fmm3dbie")]
48 ug = np.load(os.path.join(g["dir"], "field.npz"))["u_wall"]
49 uc = np.load(os.path.join(c["dir"], "field.npz"))["u_wall"]
50 diff = np.linalg.norm(ug - uc) / np.linalg.norm(uc) if ug.shape == uc.shape else float("nan")
51 lines.append(f"| {case} | {c['setup_plus_solve_s'] / g['setup_plus_solve_s']:.0f}x | {diff:.1e} |")
52 text = "\n".join(lines)
53 print(text)
54 with open(os.path.join(rdir, "comparison.md"), "w") as f:
55 f.write(text + "\n")
58if __name__ == "__main__":
59 main()