55 from mpi4py
import MPI
60 from peclet
import flow
62 gx, gy, gz = args.grid
63 origin, size = flow.mpi_block(gx, gy, gz, levels=args.decomp_levels)
65 cells = lnx * lny * lnz
66 hi = w.allreduce(cells, op=MPI.MAX)
67 lo = w.allreduce(cells, op=MPI.MIN)
68 split = [w.allreduce(1
if size[k] < (gx, gy, gz)[k]
else 0, op=MPI.SUM)
for k
in range(3)]
73 s = flow.Solver(lnx, lny, lnz)
74 s.set_decomposition(args.decomp_levels)
75 s.init_mpi(gx, gy, gz)
79 s.set_pressure_multigrid(
True, args.levels)
80 s.set_pressure_pcg(
True, 200, 1e-6)
82 s.set_domain_bc(
"-y",
"wall")
83 s.set_domain_bc(
"+y",
"wall")
84 s.set_pressure_geometry(np.asfortranarray(np.full((lnx, lny, lnz), 1e30)))
88 f
"RESULT np={w.size} block={lnx}x{lny}x{lnz} imbalance={hi / lo:.3f} "
89 f
"split=({split[0]},{split[1]},{split[2]})",
110 """Pure-function pre-flight: the hierarchy init_mpi would build, per rank count, with and
111 without telescoping. No mpirun -- this is what makes np=1536 answerable on a laptop."""
112 from peclet
import flow
114 gx, gy, gz = args.grid
116 for tele
in (
False,
True):
117 if args.weights
is not None:
120 rows, align = flow.predict_hierarchy(gx, gy, gz, np_, args.levels, tele,
121 weights=args.weights)
123 print(f
"\n{gx}x{gy}x{gz} np={np_}: rebalance_by_weights -> aligned weighted "
124 f
"ORB, align {align} (a = {align.bit_length() - 1})")
129 rows = flow.predict_hierarchy(gx, gy, gz, np_, args.levels, tele,
130 decomposition_levels=args.decomp_levels)
132 print(f
"\n{gx}x{gy}x{gz} np={np_} levels={args.levels} telescope={'ON' if tele else 'off'}"
133 f
" -> {len(rows)} levels, coarsest {cg[0]}x{cg[1]}x{cg[2]}, "
134 f
"max extent {max(cg)}, bottom on {rows[-1][1]} rank(s)")
135 for L, (g, ranks, blk, rat, t)
in enumerate(rows):
136 print(f
" L{L} global {g[0]:5d}x{g[1]:5d}x{g[2]:5d} ranks {ranks:5d} block0 "
137 f
"{blk[0]:4d}x{blk[1]:4d}x{blk[2]:4d} ratio({rat[0]},{rat[1]},{rat[2]})"
138 f
"{' -> TELESCOPE' if t else ''}")
142 ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
143 ap.add_argument(
"--grid", required=
True, help=
"GNX,GNY,GNZ")
144 ap.add_argument(
"--np", default=
"1", help=
"comma-separated rank counts")
145 ap.add_argument(
"--levels", type=int, default=5, help=
"multigrid levels requested (default 5)")
146 ap.add_argument(
"--mode", default=
"0", help=
"comma-separated: 0/aligned, coarse, or a depth")
147 ap.add_argument(
"--walls", action=
"store_true", help=
"no-slip -y/+y (the channel's domain-BC MG path)")
148 ap.add_argument(
"--orb-only", action=
"store_true", help=
"partition only; no Solver, so any size")
149 ap.add_argument(
"--verbose", action=
"store_true", help=
"also print the per-level dims/ratios")
150 ap.add_argument(
"--mpirun", default=
"mpirun")
151 ap.add_argument(
"--predict", action=
"store_true",
152 help=
"use flow.predict_hierarchy (pure function, no mpirun, any np): print the "
153 "level ladder with and without coarse-level telescoping")
154 ap.add_argument(
"--weights", default=
None,
155 help=
"with --predict: a .npy of per-cell weights (what "
156 "diagnostics.rebalance_by_weights takes: flat x-fastest, or an (nx,ny,nz) "
157 "array) -- predict the hierarchy AFTER that rebalance and log the "
158 "alignment its aligned weighted ORB picks")
159 ap.add_argument(
"--decomp-levels", type=int, default=0,
160 help=
"Solver.set_decomposition(levels) / flow.mpi_block(levels=): 0 = the "
161 "aligned ORB, >= 2 = coarse-first with that depth")
162 ap.add_argument(
"--child", action=
"store_true", help=argparse.SUPPRESS)
163 args = ap.parse_args()
164 if args.weights
is not None and not args.predict:
165 ap.error(
"--weights needs --predict")
166 if getattr(args,
"predict",
False):
167 if isinstance(args.grid, str):
168 args.grid = [int(v)
for v
in args.grid.split(
",")]
169 if isinstance(args.np, str):
170 args.np = [int(v)
for v
in args.np.split(
",")]
171 if args.weights
is not None:
174 w = np.load(args.weights)
176 if tuple(w.shape) != tuple(args.grid):
177 sys.exit(f
"--weights: shape {w.shape} is not the grid {tuple(args.grid)}")
178 w = w.ravel(order=
"F")
179 args.weights = np.asarray(w, dtype=np.float64).ravel().tolist()
181 print(
"=== weighted: diagnostics.rebalance_by_weights(w) (--mode / --decomp-levels "
188 if args.decomp_levels
and str(args.mode) ==
"0":
189 args.mode = str(args.decomp_levels)
190 for spec
in (args.mode
if isinstance(args.mode, list)
else str(args.mode).split(
",")):
192 args.decomp_levels = int(lv)
193 print(f
"=== decomposition mode {label} (set_decomposition({int(lv)})) ===")
196 args.grid = [int(v)
for v
in args.grid.split(
",")]
200 gx, gy, gz = args.grid
201 print(f
"grid {gx}x{gy}x{gz} = {gx * gy * gz / 1e6:.1f} Mcells "
202 f
"halvings x/y/z = {halvings(gx)}/{halvings(gy)}/{halvings(gz)}"
203 f
"{' <-- an axis with 0 halvings NEVER coarsens' if 0 in (halvings(gx), halvings(gy), halvings(gz)) else ''}")
204 print(f
"multigrid levels requested: {args.levels}\n")
205 print(f
"{'mode':<20} {'np':>4} {'block':>18} {'imbalance':>10} {'split x,y,z':>12} {'levels':>7}")
207 for tok
in args.mode.split(
","):
208 label, decomp =
mode_spec(tok.strip(), args.levels)
209 for np_
in [int(v)
for v
in args.np.split(
",")]:
210 env = dict(os.environ, PECLET_FLOW_MG_DEBUG=
"1")
211 cmd = [args.mpirun,
"--oversubscribe",
"-np", str(np_), sys.executable, HERE,
"--child",
212 "--grid",
",".join(map(str, args.grid)),
"--levels", str(args.levels),
213 "--decomp-levels", str(decomp)]
215 cmd.append(
"--walls")
217 cmd.append(
"--orb-only")
219 out = subprocess.run(cmd, env=env, capture_output=
True, text=
True, timeout=900).stdout
220 except subprocess.TimeoutExpired:
221 print(f
"{label:<20} {np_:>4} TIMED OUT")
223 res = re.search(
r"RESULT np=(\d+) block=(\S+) imbalance=(\S+) split=\((\S+)\)", out)
225 print(f
"{label:<20} {np_:>4} FAILED (rerun by hand for the error)")
227 lev = re.search(
r"-> (\d+) levels", out)
228 cf = re.search(
r"coarse-first depth (\d+) \(refine (\S+),", out)
229 note = f
" [coarse-first depth {cf.group(1)}, refine {cf.group(2)}]" if cf
else ""
230 print(f
"{label:<20} {res.group(1):>4} {res.group(2):>18} {res.group(3):>10} "
231 f
"{res.group(4):>12} {lev.group(1) if lev else '-':>7}{note}")
233 for line
in out.splitlines():
234 if line.startswith(
"[mg] L"):
236 print(
"\nreminders: an ODD axis never coarsens (measured 3.2x slower on one GPU);")
237 print(
" in MPI a level coarsens an axis only if EVERY rank's block is even on it,")
238 print(
" so the achievable depth is set by the per-rank block, not the global grid.")