2"""Distributed timing of the bubble column from ckpt_t43 (host MPI x OpenMP; the VoF
3step-performance harness, WO-0 -- doc/vof_step_performance_design.md).
5 export OMPI_MCA_hwloc_base_binding_policy=none
6 PYTHONPATH=<PECLET_FLOW_MPI build> OMP_NUM_THREADS=3 OMP_PROC_BIND=false \
7 mpirun -np 8 --bind-to none python tests/study/vof_perf/run_mpi.py N [--warm W] [--cheb]
8 [--rtol R] [--dump out.npz] [--ckpt PATH] [--case-dir DIR]
10Same case as run_peclet.build(), on this rank's ORB block; set_superficial_velocity closes the
11column (a build older than it shifts u on the host: global mean via Allreduce). --ckpt and
12--case-dir default as in prof.py. Prints one line on rank 0: ms/step and mean pressure iterations.
14 --rtol R MG-PCG relative tolerance (default 1e-8: the case's registered tolerance, register
15 2026-10-03; the solver default stays 1e-10)
16 --dump F save u, v, w, p, C, the timed steps' dt (dts) and pressure iterations (iters) in
17 prof.py's format (compare with cmp.py); the fields are gathered onto rank 0 at np > 1,
18 the VoF block colours (col<id>) are stored at np = 1 only
20np = 1 is SINGLE-RANK: at size 1 neither mpi_block nor init_mpi is called (sec. 14.3 H-0 of
21doc/vof_step_performance_design.md). init_mpi at size 1 keeps the distributed path on purpose (the
22register's np = 1 bit-exactness gate needs it), so a one-rank benchmark that called it timed the
23distributed code -- GridHalo self-copies, the box smoother, no single-rank fusions.
33def arg(name, default):
34 return sys.argv[sys.argv.index(name) + 1]
if name
in sys.argv
else default
37CASE_DIR =
arg(
"--case-dir", os.path.expanduser(
38 "~/Codes/peclet-examples/benchmarks/bubble-column/scripts"))
39CKPT =
arg(
"--ckpt", os.path.expanduser(
40 "~/Codes/bubble_column_perf/ckpt_t43.npz"))
41sys.path.insert(0, CASE_DIR)
44import run_peclet
as rp
47WARM = int(sys.argv[sys.argv.index(
"--warm") + 1])
if "--warm" in sys.argv
else 5
48RTOL = float(
arg(
"--rtol", 1e-8))
49DUMP =
arg(
"--dump",
None)
52SINGLE = comm.Get_size() == 1
54NX, NY, NZ = case.NX, case.NY, case.NZ
56step, t, blocks, vel, pres, series = rp.load_ckpt(CKPT)
58 org, siz = (0, 0, 0), (NX, NY, NZ)
60 org, siz = pf.mpi_block(NX, NY, NZ)
61sl = tuple(
slice(o, o + n)
for o, n
in zip(org, siz))
64 s.init_mpi(NX, NY, NZ)
66s.set_mu(S * S * case.MU_L)
67s.set_domain_bc(
"-y",
"wall", (0, 0, 0))
68s.set_domain_bc(
"+y",
"wall", (0, 0, 0))
69s.set_pressure_geometry(np.full(tuple(siz), 10.0, order=
"F"))
71s.set_vof(np.zeros(tuple(siz), order=
"F"))
72s.set_property_model(
"rho",
"linear",
"C", [case.RHO_L, case.RHO_G - case.RHO_L])
73s.set_property_model(
"mu",
"linear",
"C", [S * S * case.MU_L, S * S * (case.MU_G - case.MU_L)])
74s.set_surface_tension(S ** 3 * case.SIGMA)
75s.set_pressure_chebyshev(
True, 800, 1e-10)
76s.enable_vof_blocks_from_colors([b
for b, _
in blocks], [c
for _, c
in blocks])
77s.enable_vof_block_csf()
78vol = comm.allreduce(sum(b[
"volume"]
for b
in s.diagnostics.vof_block_stats()), op=MPI.SUM)
79alpha = vol / (NX * NY * NZ)
80rho_av = case.RHO_L + (case.RHO_G - case.RHO_L) * alpha
81s.set_property_model(
"force_x",
"linear",
"C",
82 [S * (case.RHO_L - rho_av) * case.G, S * (case.RHO_G - case.RHO_L) * case.G])
83_closure = getattr(s,
"set_superficial_velocity",
None)
or getattr(s,
"set_bulk_velocity",
None)
84if _closure
is not None:
86HOST_FLUX = _closure
is None
87if "--cheb" not in sys.argv:
88 s.set_pressure_pcg(
True, 800, RTOL)
89for c, a
in enumerate(vel):
90 s.set_velocity(c, np.asfortranarray(a[sl]))
91s.set_field(
"p", np.asfortranarray(pres[sl]))
92s.diagnostics.set_vof_step_parity(step)
96 L = s.vof_step_limits()
97 dt = min(0.25 * L[
"cfl_dt"], 0.25 * L[
"capillary_dt"])
102 mean = comm.allreduce(float(u.sum()), op=MPI.SUM) / comm.allreduce(u.size, op=MPI.SUM)
103 s.set_velocity(0, np.asfortranarray(u - mean))
110w0 = time.perf_counter()
114 it.append(s.diagnostics.last_pressure_iterations())
116wall = time.perf_counter() - w0
118 print(f
"np {comm.Get_size()} block {list(siz)} steps {N} {1000 * wall / N:.2f} ms/step "
119 f
"pressure iters mean {np.mean(it):.2f} alpha {alpha:.5f}"
120 f
"{' (single-rank)' if SINGLE else ''}", flush=
True)
123 parts = comm.gather((org, np.asarray(a)), root=0)
126 g = np.zeros((NX, NY, NZ), order=
"F")
128 g[o[0]:o[0] + b.shape[0], o[1]:o[1] + b.shape[1], o[2]:o[2] + b.shape[2]] = b
130 fields = {k:
gather(f())
for k, f
in ((
"u", s.get_u), (
"v", s.get_v), (
"w", s.get_w),
131 (
"p",
lambda: s.get_field(
"p")),
132 (
"C",
lambda: s.get_field(
"C")))}
133 cols = ({f
"col{b['id']}": np.asarray(s.vof_block_color(b[
"id"]))
134 for b
in s.diagnostics.vof_block_stats()}
if SINGLE
else {})
136 np.savez(DUMP, **fields, dts=np.array(dts), iters=np.array(it, dtype=np.int64), **cols)
137 print(
"dumped", DUMP, f
"({len(cols)} block colours)", flush=
True)
static std::vector< double > slice(const std::vector< double > &g, int ox, int oy, int oz, int lnx, int lny, int lnz)