46sys.path.insert(0, __import__(
'os').path.dirname(__import__(
'os').path.abspath(__file__)))
108def sphere_fractions(shape, R, c, sub=24):
109 """Volume fractions of a sphere: exact in z, sub x sub subsampled in (x,y)."""
111 ax = (np.arange(nx)[:,
None] + (np.arange(sub)[
None, :] + 0.5) / sub).ravel()
112 ay = (np.arange(ny)[:,
None] + (np.arange(sub)[
None, :] + 0.5) / sub).ravel()
113 X, Y = ax[:,
None], ay[
None, :]
114 half = np.sqrt(np.maximum(R * R - (X - c[0]) ** 2 - (Y - c[1]) ** 2, 0.0))
115 z0, z1 = c[2] - half, c[2] + half
116 C = np.zeros((nx, ny, nz))
118 seg = np.maximum(np.minimum(z1, k + 1) - np.maximum(z0, k), 0.0)
119 C[:, :, k] = seg.reshape(nx, sub, ny, sub).
mean(axis=(1, 3))
120 return np.asfortranarray(C)
123def cylinder_fractions(shape, R, cx, cz, sub=32):
124 """Volume fractions of a cylinder with its axis along y (a 2-D disc), exact in z."""
126 ax = (np.arange(nx)[:,
None] + (np.arange(sub)[
None, :] + 0.5) / sub).ravel()
127 half = np.sqrt(np.maximum(R * R - (ax - cx) ** 2, 0.0))
128 z0, z1 = cz - half, cz + half
129 C = np.zeros((nx, ny, nz))
131 seg = np.maximum(np.minimum(z1, k + 1) - np.maximum(z0, k), 0.0)
132 col = seg.reshape(nx, sub).
mean(axis=1)
133 C[:, :, k] = col[:,
None]
134 return np.asfortranarray(C)
150 print(
"\n" +
"=" * 96)
151 print(
"STATIC DROPLET — the spurious capillary number Ca = mu max|u| / sigma")
153 print(
" The balanced-force identity itself is EXACT (the ctest's P1: max|u| ~ 1e-17 with a")
154 print(
" constant kappa, at every resolution and viscosity). What is left here is the")
155 print(
" curvature error, and Ca is essentially its cell-unit magnitude.\n")
156 rungs = [(16, 4.0), (32, 8.0), (48, 12.0)]
if QUICK
else [(16, 4.0), (32, 8.0), (48, 12.0),
159 STATIC_RESULTS.clear()
161 s = pf.Solver(n, n, n)
164 s.set_pressure_geometry(np.full((n, n, n), 10.0, order=
"F"))
165 s.set_pressure_chebyshev(
True, 500, RTOL
or 1e-14)
167 s.set_vof(sphere_fractions((n, n, n), R, (n / 2 + 0.13, n / 2 + 0.27, n / 2 + 0.11)))
168 s.set_property_model(
"rho",
"linear",
"C", [1.0, 0.0])
169 s.set_surface_tension(1.0)
170 s.set_dt(0.5 * s.capillary_dt())
175 ca = 0.1 *
maxvel(s) / 1.0
176 STATIC_RESULTS.append(dict(n=n, R=R, max_u=
maxvel(s), ca=ca, p_iters_max=h.iters,
177 div_max=h.div, capped=
not h.valid))
178 br, ka = s.vof_curvature_branch(), s.vof_curvature()
179 d = (br > 0.5) & (br < 5.5)
180 dk = np.abs(ka[d] - 2.0 / R)
181 d2 = s.diagnostics.csf_diagnostics()
182 print(f
" D/dx = {2*R:5.1f} Ca = {ca:.3e}"
183 + (f
" ({prev/ca:.2f} x the coarser rung)" if prev
else " ")
184 + f
" dkappa rms {dk.std():.2e} max {dk.max():.2e}"
185 f
" orphan faces {d2['orphan_faces']}")
189 print(
"\n WISP-GUARD ABLATION at 64^3 (set_vof_interface_eps(0) — the rung-V3 predicate):")
190 for eps
in (1e-8, 0.0):
192 s = pf.Solver(n, n, n)
195 s.set_pressure_geometry(np.full((n, n, n), 10.0, order=
"F"))
196 s.set_pressure_chebyshev(
True, 500, RTOL
or 1e-14)
198 s.set_vof(sphere_fractions((n, n, n), R, (n / 2 + 0.13, n / 2 + 0.27, n / 2 + 0.11)))
199 s.set_property_model(
"rho",
"linear",
"C", [1.0, 0.0])
200 s.set_surface_tension(1.0)
201 s.diagnostics.set_vof_interface_eps(eps)
202 s.set_dt(0.5 * s.capillary_dt())
208 except RuntimeError
as e:
209 print(f
" eps = {eps:g}: DIVERGED at step {len(u)} — {str(e)[:70]}")
211 br, ka = s.vof_curvature_branch(), s.vof_curvature()
212 d = (br > 0.5) & (br < 5.5)
213 print(f
" eps = {eps:<6g} max|u| step 1 {u[0]:.3e} -> step 60 {u[-1]:.3e}"
214 f
" cells served {d.sum()} max|kappa| {np.abs(ka[d]).max():.3e} (2/R = {2/R:.4f})")
219 print(
"\n" +
"=" * 96)
220 print(
"CAPILLARY WAVE — dispersion omega^2 = sigma k^3 / (rho1 + rho2) (Prosperetti 1981)")
222 print(
" Quasi-2D: a standing wave of wavelength lambda = Lx along x on a flat interface at")
223 print(
" mid-height, periodic in x and y, walls +-z. Small amplitude (a0 = lambda/100) and")
224 print(
" matched kinematic viscosities, so the inviscid dispersion relation is the leading")
225 print(
" behaviour and the decay rate is 2 nu k^2. Solver units throughout (cell = 1).\n")
226 print(f
" {'nx':>4} {'lambda':>7} {'nu':>8} {'omega_num':>11} {'omega_th':>11} {'err':>8}"
227 f
" {'gamma_num':>10} {'gamma_th':>10} {'err':>8}")
228 rungs = [(32, 0.005)]
if QUICK
else [(32, 0.005), (64, 0.005), (32, 0.02)]
235 k = 2 * math.pi / lam
237 omega_th = math.sqrt(sigma * k ** 3 / (2 * rho))
238 gamma_th = 2 * nu * k * k
242 s_ex, _ = wave_mode(k, nu, sigma, rho, H=nz / 2.0)
243 omega_ex, gamma_ex = s_ex.imag, -s_ex.real
245 s = pf.Solver(nx, ny, nz)
248 s.set_domain_bc(
"-z",
"wall", (0, 0, 0))
249 s.set_domain_bc(
"+z",
"wall", (0, 0, 0))
250 s.set_pressure_geometry(np.full((nx, ny, nz), 10.0, order=
"F"))
251 s.set_pressure_chebyshev(
True, 500, 1e-11)
254 xe = np.arange(nx + 1)
255 zi = nz / 2.0 + a0 * (np.sin(k * xe[1:]) - np.sin(k * xe[:-1])) / (k * 1.0)
256 C = np.zeros((nx, ny, nz))
258 C[:, :, kk] = np.clip(zi - kk, 0.0, 1.0)[:,
None]
259 s.set_vof(np.asfortranarray(C))
260 s.set_property_model(
"rho",
"linear",
"C", [rho, 0.0])
261 s.set_surface_tension(sigma)
262 dt = 0.5 * s.capillary_dt()
265 period = 2 * math.pi / omega_th
266 nsteps = int(2.5 * period / dt)
269 for i
in range(nsteps):
274 col = Cf.sum(axis=2)[:, 0]
275 zint = col - (nz / 2.0)
276 t.append((i + 1) * dt)
277 amp.append(float(np.dot(zint, np.cos(k * (np.arange(nx) + 0.5))) * 2.0 / nx))
282 omega_num = math.pi / np.mean(np.diff(zc))
284 omega_num = float(
"nan")
286 pk = [i
for i
in range(1, len(amp) - 1)
287 if abs(amp[i]) > abs(amp[i - 1])
and abs(amp[i]) > abs(amp[i + 1])]
289 gamma_num = -math.log(abs(amp[pk[-1]]) / abs(amp[pk[0]])) / (t[pk[-1]] - t[pk[0]])
291 gamma_num = float(
"nan")
292 print(f
" {nx:>4} {lam:>7.0f} {nu:>8.4g} {omega_num:>11.5f} {omega_th:>11.5f}"
293 f
" {100*(omega_num/omega_th-1):>7.2f}% {gamma_num:>10.3e} {gamma_th:>10.3e}"
294 f
" {100*(gamma_num/gamma_th-1):>7.1f}%")
295 print(f
" EXACT viscous two-fluid mode: omega {omega_ex:.5f} (sim {100*(omega_num/omega_ex-1):+.2f}%), "
296 f
"gamma {gamma_ex:.3e} (sim {100*(gamma_num/gamma_ex-1):+.1f}%)")
297 print(f
" a0 = {a0:.3f} cells, {nsteps} steps over 2.5 periods; {h}")
302 print(
"\n" +
"=" * 96)
303 print(
"OSCILLATING DROPLET — Lamb (1932) art. 275 mode n = 2")
305 print(
" omega_n^2 = n(n-1)(n+1)(n+2) sigma / (R^3 ((n+1) rho_in + n rho_out))")
306 print(
" A prolate perturbation of a sphere, amplitude 5% of R, released from rest; the")
307 print(
" frequency is read off the zero crossings of the n = 2 moment of the colour field.\n")
308 print(f
" {'n':>4} {'R':>5} {'phi':>7} {'ratio':>7} {'omega_num':>11} {'omega_th':>11}"
315 rungs = [(32, 8.0, 1.0), (48, 8.0, 1.0)]
if QUICK
else [(32, 8.0, 1.0), (48, 12.0, 1.0),
316 (48, 8.0, 1.0), (64, 8.0, 1.0),
318 for n, R, ratio
in rungs:
320 rho_in, rho_out = ratio, 1.0
324 omega_th = math.sqrt(nn * (nn - 1) * (nn + 1) * (nn + 2) * sigma
325 / (R ** 3 * ((nn + 1) * rho_in + nn * rho_out)))
328 b = R / math.sqrt(1 + eps)
329 cx = cy = cz = n / 2 + 0.137
331 ax = (np.arange(n)[:,
None] + (np.arange(sub)[
None, :] + 0.5) / sub).ravel()
332 X, Y = ax[:,
None], ax[
None, :]
333 q = 1.0 - ((X - cx) ** 2 + (Y - cy) ** 2) / b ** 2
334 half = a * np.sqrt(np.maximum(q, 0.0))
335 C = np.zeros((n, n, n))
337 seg = np.maximum(np.minimum(cz + half, kk + 1) - np.maximum(cz - half, kk), 0.0)
338 C[:, :, kk] = seg.reshape(n, sub, n, sub).
mean(axis=(1, 3))
340 s = pf.Solver(n, n, n)
343 s.set_pressure_geometry(np.full((n, n, n), 10.0, order=
"F"))
344 s.set_pressure_chebyshev(
True, 500, 1e-11)
346 s.set_vof(np.asfortranarray(C))
347 s.set_property_model(
"rho",
"linear",
"C", [rho_out, rho_in - rho_out])
348 s.set_surface_tension(sigma)
349 dt = 0.5 * s.capillary_dt()
351 period = 2 * math.pi / omega_th
352 nsteps = int(2.5 * period / dt)
353 xs = (np.arange(n) + 0.5) - cx
354 ys = (np.arange(n) + 0.5) - cy
355 zs = (np.arange(n) + 0.5) - cz
356 XX = xs[:,
None,
None] ** 2
357 YY = ys[
None, :,
None] ** 2
358 ZZ = zs[
None,
None, :] ** 2
361 for i
in range(nsteps):
367 m = float((Cf * (2 * ZZ - XX - YY)).sum() / v)
368 t.append((i + 1) * dt)
371 m2 = np.array(m2) - np.mean(np.array(m2)[len(m2) // 2:])
373 omega_num = math.pi / np.mean(np.diff(zc))
if len(zc) >= 3
else float(
"nan")
374 phi = (4 / 3) * math.pi * R ** 3 / n ** 3
375 print(f
" {n:>4} {R:>5.1f} {100*phi:>6.2f}% {ratio:>7.0f} {omega_num:>11.5f}"
376 f
" {omega_th:>11.5f} {100*(omega_num/omega_th-1):>7.2f}%")
379 s_ex, _ = drop_mode(nn, R, mu / rho_in, sigma, rho_in)
380 print(f
" EXACT viscous mode: omega {s_ex.imag:.5f} (sim {100*(omega_num/s_ex.imag-1):+.2f}%), "
381 f
"gamma {-s_ex.real:.3e}; the residual after the viscous shift is the OPEN inviscid deficit")
383 print(
" (mpmath missing: exact viscous drop mode not computed)")
384 print(f
" {nsteps} steps over 2.5 periods, dt = {dt:.4g}; {h}")
389 1: dict(rho1=1000.0, rho2=100.0, mu1=10.0, mu2=1.0, g=0.98, sigma=24.5,
391 2: dict(rho1=1000.0, rho2=1.0, mu1=10.0, mu2=0.1, g=0.98, sigma=1.96,
398 print(
"\n" +
"=" * 96)
399 print(f
"HYSING RISING BUBBLE, case {case} (Hysing et al., IJNMF 60:1259, 2009), nx = {nx}")
401 print(
" LATERAL BC. The benchmark prescribes FREE-SLIP side walls, which this solver does not")
402 print(
" have (mac_bc.hpp offers periodic / no-slip / Dirichlet / outflow). The lateral")
403 print(
" condition here is PERIODIC — and for a bubble on the centreline that is not an")
404 print(
" approximation: mirroring about x = 0 and x = 1 puts images at -0.5 and 1.5, i.e. at")
405 print(
" spacing 1, and the mirror of a laterally symmetric bubble IS its translate. Case 1")
406 print(
" stays symmetric, so the two conditions are equivalent there; case 2 develops skirts")
407 print(
" and filaments that break the symmetry, so for case 2 it is an approximation.")
408 print(
" NOT reported: circularity. It needs the PLIC interface length, which the solver does")
409 print(
" not expose today; y_c(3) and max v_rise are the two the reference tabulates that can")
410 print(
" be computed from the colour field alone.\n")
415 R = sc.len_to_cells(0.25)
416 s = pf.Solver(nx, ny, nz)
418 s.set_mu(sc.mu(p[
"mu1"]))
419 s.set_domain_bc(
"-z",
"wall", (0, 0, 0))
420 s.set_domain_bc(
"+z",
"wall", (0, 0, 0))
421 s.set_pressure_geometry(np.full((nx, ny, nz), 10.0, order=
"F"))
422 s.set_pressure_chebyshev(
True, 600, 1e-12)
425 C = 1.0 - cylinder_fractions((nx, ny, nz), R, nx / 2.0, sc.len_to_cells(0.5))
426 s.set_vof(np.asfortranarray(C))
427 s.set_property_model(
"rho",
"linear",
"C", [p[
"rho2"], p[
"rho1"] - p[
"rho2"]])
428 s.set_property_model(
"mu",
"linear",
"C", [sc.mu(p[
"mu2"]), sc.mu(p[
"mu1"] - p[
"mu2"])])
429 s.set_surface_tension(sc.sigma(p[
"sigma"]))
433 s.enable_vof_momentum(p[
"rho2"], p[
"rho1"])
435 s.set_property_model(
"force_z",
"linear",
"C",
436 [-sc.bodyforce(p[
"rho2"] * p[
"g"]),
437 -sc.bodyforce((p[
"rho1"] - p[
"rho2"]) * p[
"g"])])
439 dt = 0.5 * s.capillary_dt()
441 lim = s.vof_step_limits()
442 print(f
" h = 1/{nx}, dt_sigma = {s.capillary_dt():.4e} s")
443 print(f
" step limits at t=0: WY CFL dt {lim['cfl_dt']:.4g}, capillary dt "
444 f
"{lim['capillary_dt']:.4g} -> {'CAPILLARY' if lim['capillary_binds'] else 'CFL'} binds")
445 print(f
" ADAPTIVE dt = 0.4 x min(WY CFL, capillary), re-picked every 10 steps: the bubble")
446 print(f
" accelerates, so the WY cap takes over from the capillary limit part-way through.")
447 zs = (np.arange(nz) + 0.5) / sc.s
449 vmax, tvmax, yc = 0.0, 0.0, 0.0
456 L = s.vof_step_limits()
457 dt = 0.4 * min(L[
"cfl_dt"], L[
"capillary_dt"])
459 ncap += int(L[
"capillary_binds"])
460 ncfl += int(
not L[
"capillary_binds"])
469 ycm = float((B.sum(axis=(0, 1)) * zs).sum() / vb)
471 vrise = float((B * w).sum() / vb) / sc.s
473 vmax, tvmax = vrise, t
476 print(f
" t = {t:5.3f} dt = {dt:.3e} y_c = {ycm:.4f} v_rise = {vrise:.4f} "
477 f
"V/V0 = {vb/ (math.pi*0.25**2*sc.s**2*ny):.6f}")
478 print(f
" measured: v_rise max {vmax:.4f} at t = {tvmax:.3f}; y_c(3) = {yc:.4f}")
479 print(f
" reference: v_rise max {p['ref_vmax']:.4f} at t = {p['ref_tvmax']:.3f}; "
480 f
"y_c(3) = {p['ref_yc']:.4f}")
481 print(f
" deviation: v_rise {100*(vmax/p['ref_vmax']-1):+.1f} %, "
482 f
"y_c {100*(yc/p['ref_yc']-1):+.1f} %")
483 print(f
" {nsteps} steps to t = {T}; the binding limit was CAPILLARY on {ncap} of the "
484 f
"{ncap+ncfl} dt re-picks and the WY CFL on {ncfl}")
485 print(f
" {h} ({time.time()-t0:.0f} s)")
490 print(
"\n" +
"=" * 96)
491 print(
"FALLING DROP — the terminal velocity WO-K deferred, re-run with surface tension")
493 print(
" WO-K reached 9 % of Hadamard-Rybczynski and named an under-resolved momentum solve")
494 print(
" as the suspected cause. BOTH halves of that turn out to be wrong, and the gate as")
495 print(
" written measured the wrong quantity:")
497 print(
" (1) A periodic box driven by a ZERO-MEAN body force conserves total MOMENTUM, not")
498 print(
" total volume flux. The box's mean velocity is a free mode of the projection, and")
499 print(
" with rho_l/rho_g = 800 at phi = 1.6 % the light ambient recoils at ~13 x the")
500 print(
" drop's speed (rho_g (1-phi) U_a = -rho_l phi U_d). So the LAB-FRAME drop velocity")
501 print(
" is a near-cancellation of two much larger numbers and is not the settling")
502 print(
" velocity. The quantity Hadamard-Rybczynski predicts is U_drop - U_ambient.")
503 print(
" (2) Driving the momentum smoother to a tolerance instead of a fixed sweep count does")
504 print(
" NOT rescue the lab-frame number -- it moves it further, precisely because that")
505 print(
" number is a cancellation. Both are reported below.")
507 print(
" Also corrected here: Hadamard-Rybczynski is U = (2/3)(d rho) g R^2/mu_o *")
508 print(
" (mu_o + mu_i)/(2 mu_o + 3 mu_i) -- the denominator carries 3 mu_i, and the rigid")
509 print(
" limit mu_i -> inf must return Stokes' 2 (d rho) g R^2/(9 mu_o).\n")
510 rho_l, rho_g = 800.0, 1.0
517 return (2.0 / 3.0) * (rho_l - rho_g) * g * R * R / mo * (1 + lam) / (2 + 3 * lam)
519 def run(n, D, cap, rtol, label):
521 phi = (4 / 3) * math.pi * R ** 3 / n ** 3
522 s = pf.Solver(n, n, n)
525 s.set_pressure_geometry(np.full((n, n, n), 10.0, order=
"F"))
526 s.set_pressure_chebyshev(
True, 400, 1e-12)
528 s.set_vof(sphere_fractions((n, n, n), R, (n / 2 + 0.13, n / 2 + 0.27, n / 2 + 0.11)))
529 s.set_property_model(
"rho",
"linear",
"C", [rho_g, rho_l - rho_g])
530 s.set_property_model(
"mu",
"linear",
"C", [mu_out, mu_ratio * mu_out - mu_out])
531 s.set_surface_tension(sigma)
532 s.enable_vof_momentum(rho_g, rho_l)
533 s.set_property_model(
"force_z",
"linear",
"C",
534 [g * (rho_l - rho_g) * phi, -g * (rho_l - rho_g)])
536 s.diagnostics.set_velocity_solver_params(cap, rtol, 2)
538 s.diagnostics.set_velocity_solver_params(cap)
539 dt = 0.5 * s.capillary_dt()
541 tau = 2.0 * R * R * rho_l / (9.0 * mu_out)
542 nsteps = int((2.0
if QUICK
else 6.0) * tau / dt)
546 for i
in range(nsteps):
549 sweeps = max(sweeps, s.diagnostics.last_step_timers()[
"momentum_sweeps"])
552 ud = float((C * w).sum() / C.sum())
553 ua = float(((1 - C) * w).sum() / (1 - C).sum())
554 hist.append((ud, ua))
555 tail = hist[-max(10, nsteps // 20):]
556 Ud = float(np.mean([a
for a, _
in tail]))
557 Ua = float(np.mean([b
for _, b
in tail]))
559 U0 = hr(R, mu_out, mu_ratio)
564 K = 1.0 / (1.0 - 1.7601 * phi ** (1 / 3) + phi - 1.5593 * phi ** 2)
566 print(f
" D/h {D:.0f} phi {phi:.4f} U_HR {U0:.4e} K {K:.3f} U_HR/K {U0/K:.4e}")
567 print(f
" U_drop(lab) {Ud:.4e} -> {Ud/(-U0/K):.4f} x "
568 f
"U_ambient {Ua:.4e} recoil ratio {-Ua/Ud:.1f}")
569 print(f
" U_rel = U_drop - U_ambient = {Urel:.4e} -> "
570 f
"**{Urel/(-U0/K):.4f} x (U_HR/K)**")
571 print(f
" dt {dt:.3g} = 0.5 dt_sigma, {nsteps} steps = "
572 f
"{nsteps*dt/tau:.1f} tau, max sweeps/step {sweeps}; {h}")
573 return Urel / (-U0 / K)
575 print(
" (a) the momentum-solve hypothesis, at D/h = 15:")
576 run(48, 15.0, 20, 0.0,
"fixed 20 sweeps (WO-K's setting)")
577 run(48, 15.0, 400, 1e-4,
"tolerance 1e-4, cap 400")
579 run(48, 15.0, 2000, 1e-6,
"tolerance 1e-6, cap 2000")
580 print(
"\n (b) resolution, with the momentum solve converged (tolerance 1e-4):")
581 for n, D
in ((48, 10.0), (48, 15.0), (64, 20.0))
if not QUICK
else ((48, 10.0), (48, 15.0),):
582 run(n, D, 400, 1e-4, f
"n = {n}, D/h = {D:.0f}")
587 print(
"\n" +
"=" * 96)
588 print(
"STEP ECONOMICS — does the capillary dt or the Weymouth-Yue CFL bind?")
590 print(
" A pore-scale configuration in physical units: water/air (sigma = 0.072 N/m,")
591 print(
" rho 1000/1.2, mu 1e-3/1.8e-5), a pore of diameter d driven at a capillary number")
592 print(
" Ca = mu U / sigma. The two explicit limits are")
593 print(
" capillary dt < sqrt((rho1+rho2) h^3 / (4 pi sigma))")
594 print(
" transport dt < CFL_WY h / |u|, CFL_WY = 0.25 (Weymouth's 3-D bound)")
595 print(
" so their ratio is dt_cfl/dt_sigma = (CFL/|u'|) sqrt(4 pi sigma' / (rho_sum))")
596 print(
" dt_sigma shrinks as h^{3/2} and dt_cfl only as h, so REFINING makes the capillary")
597 print(
" limit MORE binding, not less: their ratio grows as h^{-1/2}.\n")
598 rho1, rho2 = 1000.0, 1.2
601 print(f
" {'d [um]':>8} {'cells/d':>8} {'Ca':>8} {'U [m/s]':>10} {'dt_sig [s]':>12}"
602 f
" {'dt_cfl [s]':>12} {'binds':>10} {'steps for 1 pore':>18}")
603 for d_um
in (50.0, 200.0):
604 for cells
in (16, 32, 64):
605 for Ca
in (1e-6, 1e-4, 1e-2):
607 sc =
Scale(cells / d)
608 U = Ca * sigma_p / mu1
609 sig_s = sc.sigma(sigma_p)
610 dt_sig = math.sqrt((rho1 + rho2) * 1.0 / (4 * math.pi * sig_s))
611 dt_cfl = 0.25 / (sc.s * U)
612 binds =
"capillary" if dt_sig < dt_cfl
else "CFL"
614 nst = t_pore / min(dt_sig, dt_cfl)
615 print(f
" {d_um:>8.0f} {cells:>8d} {Ca:>8.0e} {U:>10.3e} {dt_sig:>12.3e}"
616 f
" {dt_cfl:>12.3e} {binds:>10} {nst:>18.3e}")
617 print(
"\n And the same read-off from the solver itself on the static-droplet case:")
619 s = pf.Solver(n, n, n)
622 s.set_pressure_geometry(np.full((n, n, n), 10.0, order=
"F"))
623 s.set_pressure_chebyshev(
True, 400, 1e-12)
625 s.set_vof(sphere_fractions((n, n, n), R, (n / 2 + 0.13, n / 2 + 0.27, n / 2 + 0.11)))
626 s.set_property_model(
"rho",
"linear",
"C", [1.0, 999.0])
627 s.set_surface_tension(50.0)
628 s.set_dt(0.5 * s.capillary_dt())
630 s.set_state(np.full((n, n, n), U, order=
"F"), np.zeros((n, n, n), order=
"F"),
631 np.zeros((n, n, n), order=
"F"))
632 lim = s.vof_step_limits()
633 print(f
" uniform |u| = {U}: WY CFL dt {lim['cfl_dt']:.4g}, capillary dt "
634 f
"{lim['capillary_dt']:.4g}, binding {lim['binding']:.4g} "
635 f
"({'capillary' if lim['capillary_binds'] else 'CFL'})")