23#include <Kokkos_Core.hpp>
36 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
45 const double t3 =
te *
te *
te + 9.0 * ca * (-std::log(slip));
46 const double th =
t3 > 0.0 ? std::cbrt(
t3) : 1.0 *
kDeg;
47 return std::fmin(std::fmax(th, 1.0 *
kDeg), 179.0 *
kDeg);
53 "G1a Cox-Voinov (Afkhami, Zaleski & Bussmann, JCP 228:5370): "
54 "theta^3 = theta_e^3 + 9 Ca ln(Delta/lambda)\n");
55 const double slip = 0.1,
lr = -std::log(slip);
57 for (
double thE : {30.0, 60.0, 90.0, 120.0}) {
58 for (
double ca : {-2
e-2, -5
e-3, 0.0, 5
e-3, 2
e-2, 5
e-2}) {
61 worst = std::fmax(worst, std::fabs(th - ref));
64 if (th > 1.0 *
kDeg && th < 179.0 *
kDeg) {
66 CHECK(std::fabs(
id) <= 1e-14);
72 " theta_e %5.1f -> advancing (Ca=+1e-2) %7.3f deg, receding (Ca=-1e-2) %7.3f\n",
thE,
79 " worst |kernel - host| over the sweep %.3e ; the Ca = -1 (film entrainment) "
80 "branch clamps to %.3f deg\n",
82 CHECK(worst <= 1e-15);
83 CHECK(std::fabs(ent - 1.0 *
kDeg) <= 1e-15);
88 std::printf(
"G1b the sign of U_cl (n_w = +z; the liquid ADVANCES along +t_hat)\n");
89 const double nw[3] = {0.0, 0.0, 1.0};
91 for (
double thApp : {45.0, 90.0, 135.0})
92 for (
double psi : {0.0, 37.0, 90.0, 200.0}) {
104 const double uAdv[3] = {0.3 *
that[0], 0.3 *
that[1], 0.0};
105 const double uRec[3] = {-0.3 *
that[0], -0.3 *
that[1], 0.0};
110 " theta_app recovered from m_f . n_w to %.3e rad; U_cl = +u.t_hat verified over "
111 "3 angles x 4 azimuths\n",
113 CHECK(worst <= 1e-14);
115 const double mpar[3] = {0.0, 0.0, 1.0};
122 std::printf(
"G4a the hysteresis selector (theta_a = 70, theta_r = 50)\n");
123 const double ta = 70.0 *
kDeg,
tr = 50.0 *
kDeg,
lr = -std::log(0.1);
138 const char*
nm[5] = {
"none",
"static",
"PINNED",
"advancing",
"receding"};
139 std::printf(
" theta_app %5.1f Ca %+8.1e -> %-9s theta_imposed %7.3f deg\n",
r.app,
r.ca,
146 CHECK(std::fabs(th -
coxRef(base,
r.ca, 0.1)) <= 1e-15);
154 CHECK(std::fabs(th -
coxRef(60.0, 1e-2, 0.1)) <= 1e-15);
161constexpr int NX = 32,
NY = 8,
NZ = 24;
162constexpr double ZW = 4.25;
163constexpr double XL = 0.5,
XR = 16.5;
166 std::vector<double>
v((std::size_t)
NX *
NY *
NZ);
167 for (
int z = 0;
z <
NZ; ++
z) {
168 const double zc =
z + 0.5,
d = std::fmin(
zc -
ZW, (
NZ -
ZW) -
zc);
169 for (
int y = 0;
y <
NY; ++
y)
170 for (
int x = 0;
x <
NX; ++
x)
171 v[(std::size_t)
x + (std::size_t)y *
NX + (std::size_t)
z *
NX *
NY] =
d;
176 std::vector<double>
v((std::size_t)
NX *
NY *
NZ);
177 for (
int x = 0;
x <
NX; ++
x) {
178 const double lo = std::fmax((
double)x,
XL), hi = std::fmin((
double)x + 1.0,
XR);
179 const double c = std::fmax(hi - lo, 0.0);
180 for (
int z = 0;
z <
NZ; ++
z)
181 for (
int y = 0;
y <
NY; ++
y)
182 v[(std::size_t)
x + (std::size_t)y *
NX + (std::size_t)
z *
NX *
NY] =
c;
188 const double thE = 60.0,
slip = 0.1,
muL = 1.0, sigma = 1.0,
U = 0.02;
190 "G1c the model through the solver, KINEMATIC (%dx%dx%d, flat SDF wall at z = %.2f, liquid "
191 "slab x in [%.1f, %.1f), uniform u = %.3g, theta_e = %.0f, lambda = %.2f cells, mu_l = %.1f, "
193 NX,
NY,
NZ,
ZW,
XL,
XR,
U,
thE, slip,
muL, sigma);
200 s.setVelocity(0, std::vector<double>((std::size_t)
NX *
NY *
NZ,
U));
201 s.setContactAngleDynamic(
thE, slip,
muL, sigma);
203 const auto imposed =
s.getVofDynamicField(0);
204 const auto apparent =
s.getVofDynamicField(1);
205 const auto ucl =
s.getVofDynamicField(2);
206 const auto state =
s.getVofDynamicField(4);
207 const auto at = [&](
const std::vector<double>&
v,
int x,
int y,
int z) {
208 return v[(std::size_t)x + (std::size_t)
y *
NX + (std::size_t)z *
NX *
NY];
213 " x = 16 (m = +x, liquid on the LOW side): U_cl %+.6f theta_app %.4f theta_imposed "
215 " x = 0 (m = -x, liquid on the HIGH side): U_cl %+.6f theta_app %.4f theta_imposed "
217 at(
ucl, 16, 4,
zb),
at(apparent, 16, 4,
zb),
at(imposed, 16, 4,
zb),
at(
ucl, 0, 4,
zb),
218 at(apparent, 0, 4,
zb),
at(imposed, 0, 4,
zb));
221 CHECK(std::fabs(
at(apparent, 16, 4,
zb) - 90.0) <= 1e-9);
225 " Cox-Voinov reference: advancing %.4f deg, receding %.4f deg (theta_e %.1f) -> "
226 "advancing RAISES and receding LOWERS the imposed angle\n",
228 CHECK(std::fabs(
at(imposed, 16, 4,
zb) - adv) <= 1e-10);
235 for (std::size_t i = 0;
i < imposed.size(); ++
i) {
240 worst = std::fmax(worst, std::fabs(imposed[i] - ref));
242 const auto cd =
s.contactAngleDiagnostics();
244 " over all %ld contact cells: max |theta_imposed - CoxVoinov(U_cl)| = %.3e deg "
246 " diagnostics: dynamic %ld pinned %ld advancing %ld receding %ld mean imposed %.4f "
247 "mean apparent %.4f max|Ca_cl| %.4e max|U_cl| %.4e\n",
248 nc, worst,
cd.dynamicCells,
cd.pinnedCells,
cd.advancingCells,
cd.recedingCells,
249 cd.meanImposedTheta,
cd.meanApparentTheta,
cd.maxCaCl,
cd.maxContactSpeed);
250 CHECK(worst <= 1e-10);
252 CHECK(std::fabs(
cd.maxCaCl -
muL *
U / sigma) <= 1e-12);
256 "G4b hysteresis through the solver, same scene at REST (U_cl = 0 by mu_l -> Ca = 0)\n");
261 const HRow hrows[3] = {{120.0, 60.0,
"90 inside [60,120] -> PINNED"},
262 {70.0, 50.0,
"90 above theta_a=70 -> advancing"},
263 {130.0, 110.0,
"90 below theta_r=110 -> receding"}};
271 s2.setContactAngle(
thE);
272 s2.setContactAngleHysteresis(
h.a,
h.r);
273 const auto im =
s2.getVofDynamicField(0);
274 const auto st =
s2.getVofDynamicField(4);
275 const auto ap =
s2.getVofDynamicField(1);
276 const auto d2 =
s2.contactAngleDiagnostics();
278 " theta_a %5.1f theta_r %5.1f : %s | imposed at (16,4,3) %.4f (apparent %.4f, state "
279 "%.0f) | pinned %ld advancing %ld receding %ld\n",
280 h.a,
h.r,
h.what,
at(
im, 16, 4,
zb),
at(ap, 16, 4,
zb),
at(st, 16, 4,
zb),
d2.pinnedCells,
281 d2.advancingCells,
d2.recedingCells);
282 const double want = (
h.a >= 90.0 &&
h.r <= 90.0) ?
at(ap, 16, 4,
zb) : (90.0 >
h.a ?
h.a :
h.r);
283 CHECK(std::fabs(
at(
im, 16, 4,
zb) - want) <= 1e-9);
291 Kokkos::initialize(
argc, argv);
296 if (!std::getenv(
"PECLET_VOF_WETTING_KERNEL_ONLY"))
303 std::printf(
"all vof dynamic-wetting checks passed\n");
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
bool vofWallTangent(const double mfIn[3], const double nwIn[3], double tEps, double that[3], double &cosApp, const VofMetric &g)
The in-wall (tangential) unit direction of the interface and its apparent cosine.
double vofContactLineSpeed(const double u[3], const double that[3])
U_cl = +u .
double coxVoinovAngle(double thetaE, double ca, double logRatio, double thetaMin, double thetaMax)
Cox-Voinov / Afkhami-Zaleski-Bussmann grid-scale apparent angle, in RADIANS.
int vofDynamicContactAngle(double thetaApp, double uCl, double muL, double sigma, double logRatio, bool hyst, double thetaA, double thetaR, double thetaE, double thetaMin, double thetaMax, double &thetaOut, double &caOut)
The whole rung in one pure function: hysteresis selector, then Cox-Voinov on the selected base.
@ kVofDynReceding
theta_app < theta_r: theta_r (+ Cox-Voinov)
@ kVofDynPinned
theta_r <= theta_app <= theta_a: the apparent angle is imposed
@ kVofDynAdvancing
theta_app > theta_a: theta_a (+ Cox-Voinov)
@ kVofDynStatic
Cox-Voinov applied to the static base (no hysteresis configured)
static constexpr double U
flow — VoF rung V6 (WO-V6): the DYNAMIC contact angle (Cox-Voinov with an explicit slip length) and a...