64 """A prescribed field built as the DISCRETE gradient of a potential.
66 Taking finite differences of a sampled potential -- rather than sampling an analytic velocity --
67 makes the discrete divergence exactly the discrete Laplacian of that potential, so the singular
68 all-Neumann pressure problem is compatible to machine precision by construction. Sampling an
69 analytic velocity instead leaves a small incompatible component, and the solve then stalls at a
70 residual floor and never reaches its tolerance (it looks like divergence, and is not).
72 The potential uses cos(m*pi*y), whose y-derivative vanishes at both walls, so the wall-normal
73 velocity is zero there as the wall boundary condition requires. Wavelengths are fixed fractions
74 of the box, so every resolution samples the SAME continuous problem -- the point of the test.
77 x = (np.arange(nx) + 0.5)[:,
None,
None] / nx
78 y = (np.arange(ny) + 0.5)[
None, :,
None] / ny
79 z = (np.arange(nz) + 0.5)[
None,
None, :] / nz
82 elif kind ==
"broadband":
85 modes = [(m, m, m)
for m
in (1, 2, 4, 8, 16)]
87 raise SystemExit(f
"unknown --rhs {kind!r} (smooth|broadband)")
88 phi = np.zeros((nx, ny, nz))
89 for kx, my, kz
in modes:
90 phi += (1.0 / kx) * np.cos(tp * kx * x) * np.cos(my * np.pi * y) * np.cos(tp * kz * z)
92 u = np.roll(phi, -1, axis=0) - phi
93 w = np.roll(phi, -1, axis=2) - phi
94 v = np.zeros_like(phi)
95 v[:, :-1, :] = phi[:, 1:, :] - phi[:, :-1, :]
97 return (np.asfortranarray(u), np.asfortranarray(v), np.asfortranarray(w))
101 ap = argparse.ArgumentParser()
102 ap.add_argument(
"--n", default=
"32,48,64", help=
"wall-normal cell counts (the box is refined)")
103 ap.add_argument(
"--aspect", default=
"6,1,2", help=
"box shape as x,y,z multiples of the y extent")
104 ap.add_argument(
"--rhs", default=
"smooth", help=
"smooth | broadband")
105 ap.add_argument(
"--levels", type=int, default=10)
106 ap.add_argument(
"--bottom", default=
"auto", help=
"auto | smoother | agglomerated")
107 ap.add_argument(
"--rtol", type=float, default=1e-10, help=
"tight, so counts resolve clearly")
108 ap.add_argument(
"--periodic-y", action=
"store_true", help=
"drop the walls (contrast case)")
109 args = ap.parse_args()
110 ax, ay, az = (float(v)
for v
in args.aspect.split(
","))
112 from peclet
import flow
114 print(f
"box {ax:g}:{ay:g}:{az:g} rhs={args.rhs} bottom={args.bottom} "
115 f
"levels<={args.levels} rtol={args.rtol:g} "
116 f
"{'PERIODIC y (no walls)' if args.periodic_y else 'walls on -y/+y'}")
117 print(f
"{'grid':>18} {'Mcells':>8} {'h (rel)':>8} {'pressure iterations':>20}")
119 for n
in (int(v)
for v
in args.n.split(
",")):
120 nx, ny, nz = int(round(ax / ay * n)), n, int(round(az / ay * n))
121 s = flow.Solver(nx, ny, nz)
125 s.set_advection(
False)
126 s.set_incremental_pressure(
False)
127 s.set_pressure_warmstart(
False)
128 s.set_pressure_multigrid(
True, args.levels)
129 s.set_pressure_pcg(
True, 500, args.rtol)
130 s.set_pressure_bottom(args.bottom)
131 if not args.periodic_y:
132 s.set_domain_bc(2, 1)
133 s.set_domain_bc(3, 1)
134 s.set_pressure_geometry(np.asfortranarray(np.full((nx, ny, nz), 1e30)))
135 s.set_state(*
velocity(nx, ny, nz, args.rhs))
137 it = s.last_pressure_iterations()
139 print(f
"{f'{nx}x{ny}x{nz}':>18} {nx * ny * nz / 1e6:8.2f} {first / n:8.3f} {it:20.1f}")
141 print(
"\nA resolution-independent multigrid holds the count flat as h shrinks.")