63 c = np.zeros((nx, ny, nz), order=
"F")
65 c[i, :, :] = min(1.0, max(0.0, xg - i))
if liquid_low
else min(1.0, max(0.0, (i + 1) - xg))
70def gate_p0a(nx=64, ny=4, nz=4, x0=32.25, mdot=0.02, dt=1.0, nsteps=1000):
71 s = pf.Solver(nx, ny, nz)
77 s.enable_phase_change(1.0, 1.0, 1.0)
78 s.set_mass_flux_uniform(mdot)
79 worst, lo, hi, clips, redist = 0.0, 1e30, -1e30, 0, 0.0
80 for k
in range(1, nsteps + 1):
81 s.apply_phase_change(dt)
82 x = s.get_vof().sum() / (ny * nz)
83 worst = max(worst, abs(x - (x0 - mdot * dt * k)))
84 d = s.diagnostics.phase_change_diagnostics()
85 lo, hi = min(lo, d[
"min_C"]), max(hi, d[
"max_C"])
86 clips += d[
"deficit_cells"]
87 redist += d[
"redistributed"]
88 print(f
"p0a grid {nx}x{ny}x{nz}, mdot {mdot}, {nsteps} steps")
89 print(f
" max |x_num - (x0 - mdot t/rho_l)| = {worst:.3e} [gate 1e-12]")
90 print(f
" C in [{lo!r}, {hi!r}] cell-crossings clipped {clips}, "
91 f
"|redistributed| {redist:.6g}")
92 return worst < 1e-12
and lo >= 0.0
and hi <= 1.0
and clips > 0
96def gate_p0b(nx=64, ny=4, nz=4, rg=1.0, rl=100.0, mdot=0.01, x0=32.25, nsteps=20):
97 s = pf.Solver(nx, ny, nz)
101 s.set_domain_bc(
"-x",
"wall")
102 s.set_domain_bc(
"+x",
"wall")
103 s.set_pressure_geometry(np.full((nx, ny, nz), 1.0, order=
"F"))
106 s.set_property_model(
"rho",
"linear",
"C", [rg, rl - rg])
107 s.enable_phase_change(rg, rl, 1.0)
108 s.set_mass_flux_uniform(mdot)
109 ug = mdot * (1.0 / rg - 1.0 / rl)
110 sink = np.zeros((nx, ny, nz), order=
"F")
111 sink[nx - 6, :, :] = -ug
112 s.set_divergence_source(sink)
113 s.set_pressure_fcg(
True, 400, 1e-12)
114 for _
in range(nsteps):
116 u, v, w = s.get_u(), s.get_v(), s.get_w()
117 src = s.get_field(
"pc_source") + s.get_field(
"div_source")
118 div = np.zeros_like(u)
119 div[:-1] += u[1:] - u[:-1]
120 div[:, :-1] += v[:, 1:] - v[:, :-1]
121 div[:, :, :-1] += w[:, :, 1:] - w[:, :, :-1]
122 dmax = np.abs(div - src)[:-1, :-1, :-1].max()
123 plateau = u[35:56, 0, 0]
124 liq = np.abs(u[2:30, 0, 0]).max()
125 d = s.diagnostics.phase_change_diagnostics()
126 print(f
"p0b ratio {rl/rg:g}, closed column + balanced sink, {nsteps} steps")
127 print(f
" u_gas = {plateau.mean():.17g} exact {ug:.17g} rel {(plateau.mean()-ug)/ug:+.3e}"
129 print(f
" plateau spread {plateau.max()-plateau.min():.3e}, max|u_liquid| {liq:.3e}")
130 print(f
" interfacial-cell faces u = {u[32,0,0]:.3e}, {u[33,0,0]:.3e} (the LIQUID velocity)")
131 print(f
" max|div(u) - S| = {dmax:.3e} source sum {d['source_sum']:.6g} into "
132 f
"{d['source_cells']} cells, fallback {d['fallback_cells']}")
133 print(f
" pressure iterations {s.diagnostics.last_pressure_iterations()} / cap 400 "
134 f
"(a capped solve makes the run INVALID)")
135 return abs((plateau.mean() - ug) / ug) < 1e-10
and dmax < 1e-12 \
136 and s.diagnostics.last_pressure_iterations() < 400
140def stefan_run(n, st=1.0, alpha=1.0, x0p=0.10, xep=0.25, fo=0.5, ny=4, nz=4):
142 t0 = (x0p / (2 * lam)) ** 2 / alpha
143 te = (xep / (2 * lam)) ** 2 / alpha
145 nsteps = int(round((te - t0) / (fo / d)))
146 dt = (te - t0) / nsteps
147 s = pf.Solver(n, ny, nz)
151 s.set_domain_bc(
"-x",
"wall")
152 s.set_domain_bc(
"+x",
"wall")
153 s.set_pressure_geometry(np.full((n, ny, nz), 1.0, order=
"F"))
155 s.set_vof(
planar_colour(n, ny, nz, x0p * n, liquid_low=
False))
156 t = np.zeros((n, ny, nz), order=
"F")
159 t[i, :, :] = (1.0 - math.erf(lam * x / x0p) / math.erf(lam))
if x < x0p
else 0.0
160 s.add_scalar(
"T", d,
"koren", 60)
161 s.set_scalar_bc(
"T",
"-x",
"dirichlet", 1.0)
162 s.set_scalar_bc(
"T",
"+x",
"dirichlet", 0.0)
164 s.enable_phase_change(1.0, 1.0, 1.0)
165 s.set_phase_change_thermal(
True,
"T", 0.0, d, d, 0.0)
166 for _
in range(nsteps):
167 s.apply_phase_change(dt)
169 layer = n - s.get_vof().sum() / (ny * nz)
170 exact = 2 * lam * math.sqrt(alpha * te) * n
171 return layer, exact, nsteps, lam, s.diagnostics.phase_change_diagnostics()
175 print(
"p1 Stefan problem, St = 1, layer 0.10 -> 0.25 of the domain, Fo = 0.5 (dt ~ h^2)")
179 rel = (layer - exact) / exact
180 errs.append(abs(rel))
181 print(f
" N = {n:4d} steps {nsteps:5d} lambda {lam:.6f} layer {layer:9.5f} cells "
182 f
"(exact {exact:9.5f}) rel {100*rel:+8.4f} % C in [{d['min_C']:.2e}, "
183 f
"{d['max_C']:.6f}] unresolved {d['unresolved']:.2e} fallback {d['fallback_cells']}")
184 for a, b, na, nb
in zip(errs[:-1], errs[1:], ns[:-1], ns[1:]):
185 print(f
" observed order {na} -> {nb}: {math.log2(a/b):.3f}")
187 print(f
" observed order {ns[0]} -> {ns[-1]} (fit): "
188 f
"{math.log2(errs[0]/errs[-1])/math.log2(ns[-1]/ns[0]):.3f}")
189 print(f
" GATE: |rel| at N = {ns[-1]} is {100*errs[-1]:.4f} % [gate 0.5 %; "
190 f
"Malan et al. 2021 report 0.23 %]")
191 return errs[-1] < 0.005
gate_p0b(nx=64, ny=4, nz=4, rg=1.0, rl=100.0, mdot=0.01, x0=32.25, nsteps=20)
stefan_run(n, st=1.0, alpha=1.0, x0p=0.10, xep=0.25, fo=0.5, ny=4, nz=4)