flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
tune_periodic_spheres_multigrid.py
Go to the documentation of this file.
1"""Sweep pressure/velocity multigrid settings for the periodic-sphere benchmark."""
2
3import argparse
4import csv
5import os
6import sys
7from copy import deepcopy
8
9THIS_DIR = os.path.dirname(__file__)
10sys.path.append(THIS_DIR)
11
12from validate_periodic_spheres_n128 import get_reference_k, run_case, warm_up_cuda
13
14
16 name,
17 enable_pressure_mg,
18 pressure_mg_params,
19 enable_velocity_mg,
20 velocity_mg_params,
21 pressure_iter,
22 velocity_iter,
23):
24 """Build one tuning configuration record."""
25 return {
26 "name": name,
27 "enable_pressure_mg": enable_pressure_mg,
28 "pressure_mg_params": pressure_mg_params,
29 "enable_velocity_mg": enable_velocity_mg,
30 "velocity_mg_params": velocity_mg_params,
31 "pressure_iter": pressure_iter,
32 "velocity_iter": velocity_iter,
33 }
34
35
36CONFIGS = [
37 make_config("rbgs_p50_v2", False, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 50, 2),
38 make_config("rbgs_p20_v2", False, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 20, 2),
39 make_config("rbgs_p10_v2", False, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 10, 2),
40 make_config("rbgs_p20_v4", False, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 20, 4),
41 make_config("pmg_default_v2", True, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 50, 2),
42 make_config("pmg_light_v2", True, (4, 1, 1, 16, 1), False, (4, 1, 1, 16, 1), 50, 2),
43 make_config("pmg_light_v4", True, (4, 1, 1, 16, 1), False, (4, 1, 1, 16, 1), 50, 4),
44 make_config("pmg_deep_v1", True, (4, 2, 2, 32, 2), False, (4, 1, 1, 16, 1), 50, 1),
45 make_config("pmg_light_l3_v2", True, (3, 1, 1, 16, 1), False, (3, 1, 1, 16, 1), 50, 2),
46 make_config("pmg_light_l3_v1", True, (3, 1, 1, 16, 1), False, (3, 1, 1, 16, 1), 50, 1),
47 make_config("pmg_light_l3_v4", True, (3, 1, 1, 16, 1), False, (3, 1, 1, 16, 1), 50, 4),
48 make_config("pmg_light_l5_v2", True, (5, 1, 1, 16, 1), False, (5, 1, 1, 16, 1), 50, 2),
49 make_config("pmg_default_l5_v2", True, (5, 2, 2, 32, 2), False, (5, 1, 1, 16, 1), 50, 2),
50 make_config("pmg_mid_l5_v2", True, (5, 1, 1, 16, 2), False, (5, 1, 1, 16, 1), 50, 2),
51 make_config("pmg_light_l5_p20_v2", True, (5, 1, 1, 16, 1), False, (5, 1, 1, 16, 1), 20, 2),
52 make_config("fullmg_fast", True, (4, 1, 1, 16, 1), True, (4, 1, 1, 16, 1), 50, 2),
53 make_config("fullmg_balanced", True, (4, 1, 1, 16, 1), True, (4, 2, 2, 32, 1), 50, 2),
54 make_config("fullmg_inner4", True, (4, 4, 4, 32, 1), True, (4, 4, 4, 32, 1), 50, 2),
55 make_config("fullmg_inner6", True, (4, 6, 6, 32, 1), True, (4, 6, 6, 32, 1), 50, 2),
56 make_config("fullmg_vcycle2", True, (4, 1, 1, 16, 1), True, (4, 1, 1, 16, 2), 50, 2),
57 make_config("fullmg_robust", True, (4, 2, 2, 32, 2), True, (4, 2, 2, 32, 1), 50, 2),
58 make_config("fullmg_balanced_l3", True, (3, 1, 1, 16, 1), True, (3, 2, 2, 32, 1), 50, 2),
59 make_config("fullmg_balanced_l5", True, (5, 1, 1, 16, 1), True, (5, 2, 2, 32, 1), 50, 2),
60 make_config("fullmg_balanced_l5_v4", True, (5, 1, 1, 16, 1), True, (5, 2, 2, 32, 1), 50, 4),
61 make_config("fullmg_robust_l5", True, (5, 2, 2, 32, 2), True, (5, 2, 2, 32, 1), 50, 2),
62]
63
64
65def run_config(phi, res, config, args):
66 """Execute one tuning point and capture failures in the output row."""
67 row = deepcopy(config)
68 row["phi"] = phi
69 row["resolution"] = res
70 row["k_ref"] = get_reference_k(phi)
71 row["status"] = "ok"
72 try:
73 result = run_case(
74 phi,
75 res,
76 config["enable_pressure_mg"],
77 config["pressure_mg_params"],
78 config["enable_velocity_mg"],
79 config["velocity_mg_params"],
80 config["pressure_iter"],
81 config["velocity_iter"],
82 args.outer_iterations,
83 args.outer_tol,
84 args.outer_mode,
85 args.dt,
86 args.max_steps,
87 args.convergence_stride,
88 args.convergence_rel_tol,
89 )
90 row.update(result)
91 row["ref_rel_error"] = abs(row["k_sim"] - row["k_ref"]) / row["k_ref"]
92 row["within_reference_tol"] = row["ref_rel_error"] <= args.reference_rel_tol
93 except Exception as exc:
94 row["status"] = f"failed: {exc}"
95 row["elapsed_s"] = float("nan")
96 row["per_step_s"] = float("nan")
97 row["steps_taken"] = 0
98 row["outer_iterations_mean"] = float("nan")
99 row["outer_iterations_max"] = 0
100 row["k_sim"] = float("nan")
101 row["ref_rel_error"] = float("nan")
102 row["within_reference_tol"] = False
103 return row
104
105
106def write_csv(rows, path):
107 """Persist the sweep results for later analysis."""
108 os.makedirs(os.path.dirname(path), exist_ok=True)
109 fieldnames = [
110 "phi",
111 "resolution",
112 "name",
113 "enable_pressure_mg",
114 "enable_velocity_mg",
115 "pressure_iter",
116 "velocity_iter",
117 "pressure_mg_params",
118 "velocity_mg_params",
119 "elapsed_s",
120 "per_step_s",
121 "steps_taken",
122 "outer_iterations_mean",
123 "outer_iterations_max",
124 "k_sim",
125 "k_ref",
126 "ref_rel_error",
127 "within_reference_tol",
128 "status",
129 ]
130 with open(path, "w", newline="", encoding="utf-8") as handle:
131 writer = csv.DictWriter(handle, fieldnames=fieldnames)
132 writer.writeheader()
133 for row in rows:
134 out = {key: row.get(key) for key in fieldnames}
135 writer.writerow(out)
136
137
138def print_summary(rows, args):
139 """Print the fastest acceptable configurations for each case."""
140 print(
141 f"Tuning setup: resolutions={args.resolutions}, phis={args.phis}, "
142 f"outer_iterations={args.outer_iterations}, outer_tol={args.outer_tol}, "
143 f"outer_mode={args.outer_mode}, "
144 f"max_steps={args.max_steps}"
145 )
146 print()
147 grouped = {}
148 for row in rows:
149 grouped.setdefault((row["resolution"], row["phi"]), []).append(row)
150
151 for (res, phi), group_rows in sorted(grouped.items()):
152 print(f"Case: N={res}, phi={phi}")
153 print(
154 f"{'config':<20} {'time(s)':>10} {'steps':>8} {'outer_mean':>12} "
155 f"{'outer_max':>10} {'K':>10} {'err%':>8} {'ok':>6} {'status':<24}"
156 )
157 best = sorted(
158 group_rows,
159 key=lambda row: (
160 row["status"] != "ok",
161 not row["within_reference_tol"],
162 row["elapsed_s"] if row["elapsed_s"] == row["elapsed_s"] else float("inf"),
163 ),
164 )
165 for row in best:
166 print(
167 f"{row['name']:<20} {row['elapsed_s']:10.3f} {row['steps_taken']:8d} "
168 f"{row['outer_iterations_mean']:12.2f} {row['outer_iterations_max']:10d} "
169 f"{row['k_sim']:10.4f} {100.0 * row['ref_rel_error']:8.2f} "
170 f"{str(row['within_reference_tol']):>6} {row['status']:<24}"
171 )
172 print()
173
174
176 """Parse CLI arguments for the multigrid parameter sweep."""
177 parser = argparse.ArgumentParser(
178 description="Tune RBGS and multigrid settings for periodic spheres at lower resolutions."
179 )
180 parser.add_argument("--phis", type=float, nargs="+", default=[0.001, 0.5236])
181 parser.add_argument("--resolutions", type=int, nargs="+", default=[32, 64])
182 parser.add_argument("--dt", type=float, default=1.0)
183 parser.add_argument("--max-steps", type=int, default=80)
184 parser.add_argument("--convergence-stride", type=int, default=5)
185 parser.add_argument("--convergence-rel-tol", type=float, default=1e-8)
186 parser.add_argument("--outer-iterations", type=int, default=800)
187 parser.add_argument("--outer-tol", type=float, default=1e-6)
188 parser.add_argument("--outer-mode", type=int, default=0)
189 parser.add_argument("--reference-rel-tol", type=float, default=0.02)
190 parser.add_argument(
191 "--configs",
192 nargs="+",
193 default=None,
194 help="Optional list of config names to run.",
195 )
196 parser.add_argument(
197 "--output-csv",
198 default="output/periodic_spheres_multigrid_tuning.csv",
199 )
200 return parser.parse_args()
201
202
203def main():
204 """Run the configured parameter sweep and emit a CSV summary."""
205 args = parse_args()
206 warm_up_cuda()
207 configs = CONFIGS
208 if args.configs is not None:
209 wanted = set(args.configs)
210 configs = [config for config in CONFIGS if config["name"] in wanted]
211 missing = sorted(wanted - {config["name"] for config in configs})
212 if missing:
213 raise ValueError(f"unknown configs requested: {missing}")
214 rows = []
215 for res in args.resolutions:
216 for phi in args.phis:
217 for config in configs:
218 print(
219 f"Running config={config['name']} N={res} phi={phi} "
220 f"outer_tol={args.outer_tol}"
221 )
222 row = run_config(phi, res, config, args)
223 rows.append(row)
224 write_csv(rows, args.output_csv)
225 print_summary(rows, args)
226 print(f"Wrote tuning CSV to {args.output_csv}")
227
228
229if __name__ == "__main__":
230 main()
make_config(name, enable_pressure_mg, pressure_mg_params, enable_velocity_mg, velocity_mg_params, pressure_iter, velocity_iter)