flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_scalar_krylov.cpp
Go to the documentation of this file.
1// The cut-cell scalar BiCGStab (doc/scalar_ibm_design.md §5.1; src/scalar_krylov.hpp): the
2// device-resident pass (WO-9b) against the host-scalar pass, bitwise — the iterate, the iteration
3// count and the true residual — on a non-symmetric periodic advection-diffusion operator, one field
4// and two coupled fields, on a healthy solve and on every breakdown exit (a non-finite (rh, r),
5// (rh, v) or (t, t), and omega = 0), which no healthy problem reaches: the reductions are poisoned
6// at a chosen call, identically for both passes.
7#include <cmath>
8#include <cstdio>
9#include <cstring>
10#include <Kokkos_Core.hpp>
11#include <limits>
12#include <string>
13#include <vector>
14
15#include "flow_ibm.hpp"
16
17#define CHECK(cond) \
18 do { \
19 if (!(cond)) { \
20 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
21 ++g_fail; \
22 } \
23 } while (0)
24
25namespace {
26int g_fail = 0;
27using namespace peclet::flow;
28
29constexpr int N = 12, G = 2;
30const C3 E{N + 2 * G, N + 2 * G, N + 2 * G};
31const std::size_t NE = (std::size_t)E.x * E.y * E.z;
32
36void applyOp(CCField y, CCConst x, double m, double d) {
37 ccFor3(
38 "test_skr_apply", C3{G, G, G}, C3{E.x - G, E.y - G, E.z - G},
39 KOKKOS_LAMBDA(int lx, int ly, int lz) {
40 const int xm = (lx - G + N - 1) % N + G, xp = (lx - G + 1) % N + G;
41 const int ym = (ly - G + N - 1) % N + G, yp = (ly - G + 1) % N + G;
42 const int zm = (lz - G + N - 1) % N + G, zp = (lz - G + 1) % N + G;
43 const long sy = E.x, sz = (long)E.x * E.y;
44 const long i = (long)lx + (long)ly * sy + (long)lz * sz;
45 const long nb[6] = {xm + ly * sy + lz * sz, xp + ly * sy + lz * sz, lx + ym * sy + lz * sz,
46 lx + yp * sy + lz * sz, lx + ly * sy + zm * sz, lx + ly * sy + zp * sz};
47 const double u = 0.7 + 0.3 * Kokkos::sin(0.5 * ly); // inflow from -x, varying in y
48 const double up[6] = {u, 0.0, 0.2, 0.0, 0.0, 0.1};
49 double s = m * x(i);
50 for (int f = 0; f < 6; ++f)
51 s += (d + up[f]) * (x(i) - x(nb[f]));
52 y(i) = s;
53 });
54}
56void couple(CCField yf, CCField ys, CCConst xf, CCConst xs, double w) {
57 ccFor3(
58 "test_skr_couple", C3{G, G, G}, C3{E.x - G, E.y - G, E.z - G},
59 KOKKOS_LAMBDA(int lx, int ly, int lz) {
60 const long i = (long)lx + (long)ly * E.x + (long)lz * (long)E.x * E.y;
61 yf(i) -= w * xs(i);
62 ys(i) -= w * xf(i);
63 });
64}
66void jacobi(CCField zf, CCField zs, CCConst rf, CCConst rs, double df, double ds, bool two) {
67 ccFor3(
68 "test_skr_jacobi", C3{G, G, G}, C3{E.x - G, E.y - G, E.z - G},
69 KOKKOS_LAMBDA(int lx, int ly, int lz) {
70 const long i = (long)lx + (long)ly * E.x + (long)lz * (long)E.x * E.y;
71 zf(i) = df * rf(i);
72 if (two)
73 zs(i) = ds * rs(i);
74 });
75}
77void zeroGhosts(CCField f) {
78 ccFor3(
79 "test_skr_zero_ghosts", C3{0, 0, 0}, E, KOKKOS_LAMBDA(int lx, int ly, int lz) {
80 if (lx >= G && lx < E.x - G && ly >= G && ly < E.y - G && lz >= G && lz < E.z - G)
81 return;
82 f((long)lx + (long)ly * E.x + (long)lz * (long)E.x * E.y) = 0.0;
83 });
84}
85
86struct Run {
87 std::vector<double> xf, xs;
88 int its = 0;
89 double trueRes = 0.0;
90 bool restarted = false;
91};
92
95Run solve(bool resident, bool two, int poisonDot, int poisonDot2, double poison, bool onTt) {
96 auto mk = [](const char* n) { return CCField(n, NE); };
97 ScalarVec b, x, r, rh, p, v, t, z, z2;
98 ScalarVec* all[9] = {&b, &x, &r, &rh, &p, &v, &t, &z, &z2};
99 for (ScalarVec* s : all) {
100 s->f = mk("f");
101 if (two) {
102 s->s = mk("s");
103 s->solid = true;
104 }
105 }
106 { // a fixed right-hand side, x0 = 0
107 auto hb = Kokkos::create_mirror_view(b.f);
108 for (std::size_t k = 0; k < NE; ++k)
109 hb(k) = std::cos(1.3 * (double)k) + 0.25;
110 Kokkos::deep_copy(b.f, hb);
111 if (two)
112 for (std::size_t k = 0; k < NE; ++k)
113 hb(k) = std::sin(0.7 * (double)k);
114 if (two)
115 Kokkos::deep_copy(b.s, hb);
116 zeroGhosts(b.f);
117 if (two)
118 zeroGhosts(b.s);
119 }
120 const double m = 1.0, d = 1.0, ms = 0.3, dsd = 2.0, w = 0.4;
122 ops.e = E;
123 ops.g = G;
124 ops.matvec = [&](const ScalarVec& y, const ScalarVec& xx) {
125 applyOp(y.f, CCConst(xx.f), m, d);
126 if (!two)
127 return;
128 applyOp(y.s, CCConst(xx.s), ms, dsd);
129 couple(y.f, y.s, CCConst(xx.f), CCConst(xx.s), w);
130 };
131 ops.precond = [&](const ScalarVec& zz, const ScalarVec& rr) {
132 jacobi(zz.f, zz.s, CCConst(rr.f), CCConst(rr.s), 1.0 / (m + 6.0 * d + 1.0),
133 1.0 / (ms + 6.0 * dsd + 1.0), two);
134 };
135 int nDot = 0, nDot2 = 0;
136 auto poisonHost = [&](double& val, int& n, int at) {
137 if (++n == at)
138 val = poison;
139 };
140 auto poisonSlot = [&](skr::Slot s, int& n, int at) {
141 if (++n == at)
142 Kokkos::deep_copy(s, poison);
143 };
144 ops.dot = [&](const ScalarVec& a, const ScalarVec& bb) {
145 double s =
146 two ? sco::dotTwoLocal(CCConst(a.f), CCConst(bb.f), CCConst(a.s), CCConst(bb.s), E, G)
147 : sco::dotLocal(CCConst(a.f), CCConst(bb.f), E, G);
149 return s;
150 };
151 ops.dot2 = [&](const ScalarVec& a, const ScalarVec& bb, const ScalarVec& c, double& ab,
152 double& cc) {
153 if (two)
154 sco::dot2TwoLocal(CCConst(a.f), CCConst(bb.f), CCConst(c.f), CCConst(a.s), CCConst(bb.s),
155 CCConst(c.s), E, G, ab, cc);
156 else
157 sco::dot2Local(CCConst(a.f), CCConst(bb.f), CCConst(c.f), E, G, ab, cc);
159 };
160 ops.maxabs = [&](const ScalarVec& a) {
161 const double mf = sco::maxabsLocal(CCConst(a.f), E, G);
162 return two ? std::fmax(mf, sco::maxabsLocal(CCConst(a.s), E, G)) : mf;
163 };
164 ops.removeMean = [](const ScalarVec&) {};
165 struct Slots {
166 Kokkos::View<double*, CCMem> ks;
167 Kokkos::View<double*, Kokkos::HostSpace> pk;
168 } st;
169 if (resident) {
170 residentKrylov(st, ops);
171 ops.dotTo = [&](const ScalarVec& a, const ScalarVec& bb, skr::Slot s) {
172 if (two)
173 sco::dotTwoReduce(CCConst(a.f), CCConst(bb.f), CCConst(a.s), CCConst(bb.s), E, G, s);
174 else
175 sco::dotReduce(CCConst(a.f), CCConst(bb.f), E, G, s);
177 };
178 ops.dot2To = [&](const ScalarVec& a, const ScalarVec& bb, const ScalarVec& c, skr::Slot s1,
179 skr::Slot s2) {
180 if (two)
181 sco::dot2TwoReduce(CCConst(a.f), CCConst(bb.f), CCConst(c.f), CCConst(a.s), CCConst(bb.s),
182 CCConst(c.s), E, G, s1, s2);
183 else
184 sco::dot2Reduce(CCConst(a.f), CCConst(bb.f), CCConst(c.f), E, G, s1, s2);
185 poisonSlot(onTt ? s2 : s1, nDot2, poisonDot2);
186 };
187 ops.maxabsTo = [&](const ScalarVec& a, skr::Slot sf, skr::Slot ss) {
188 sco::maxabsReduce(CCConst(a.f), E, G, Kokkos::Max<double, CCMem>(sf));
189 if (two)
190 sco::maxabsReduce(CCConst(a.s), E, G, Kokkos::Max<double, CCMem>(ss));
191 };
192 }
193 const ScalarKrylovResult res =
194 scalarBiCGStab(ops, b, x, r, rh, p, v, t, z, z2, 200, 1e-12, false);
195 Run out;
196 out.its = res.iterations;
197 out.trueRes = res.trueRes;
198 out.restarted = res.restarted;
199 auto hx = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), x.f);
200 out.xf.assign(hx.data(), hx.data() + NE);
201 if (two) {
202 auto hs = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), x.s);
203 out.xs.assign(hs.data(), hs.data() + NE);
204 }
205 return out;
206}
207
208void compare(const char* tag, bool two, int pd, int pd2, double poison, bool onTt) {
209 const Run h = solve(false, two, pd, pd2, poison, onTt);
210 const Run d = solve(true, two, pd, pd2, poison, onTt);
211 const bool same =
212 h.xf.size() == d.xf.size() &&
213 std::memcmp(h.xf.data(), d.xf.data(), h.xf.size() * sizeof(double)) == 0 &&
214 h.xs.size() == d.xs.size() &&
215 (h.xs.empty() || std::memcmp(h.xs.data(), d.xs.data(), h.xs.size() * sizeof(double)) == 0);
216 const bool resSame = std::memcmp(&h.trueRes, &d.trueRes, sizeof(double)) == 0;
217 std::printf(
218 " %-34s %s: iterations host %3d resident %3d, true residual %.3e / %.3e%s, iterate %s\n",
219 tag, two ? "two fields" : "one field ", h.its, d.its, h.trueRes, d.trueRes,
220 h.restarted ? " (restarted)" : "", same ? "bitwise" : "DIFFERS");
221 CHECK(same);
222 CHECK(h.its == d.its);
223 CHECK(resSame);
224 CHECK(h.restarted == d.restarted);
225}
226} // namespace
227
228int main(int argc, char** argv) {
229 Kokkos::initialize(argc, argv);
230 {
231 const double nan = std::numeric_limits<double>::quiet_NaN();
232 std::printf("scalarBiCGStab: the resident pass against the host-scalar pass (%d^3, g = %d)\n",
233 N, G);
234 for (const bool two : {false, true}) {
235 compare("healthy", two, 0, 0, 0.0, true);
236 compare("(rh, r) NaN at iteration 3", two, 5, 0, nan, true); // dot calls: 2 per iteration
237 compare("(rh, v) NaN at iteration 2", two, 4, 0, nan, true);
238 compare("(rh, r) = 0 at iteration 2", two, 3, 0, 0.0, true);
239 compare("(t, t) NaN at iteration 2", two, 0, 2, nan, true);
240 compare("(t, t) = 0 at iteration 1", two, 0, 1, 0.0, true);
241 compare("omega = 0 ((t, r) = 0) at iteration 3", two, 0, 3, 0.0, false);
242 }
243 }
244 Kokkos::finalize();
245 if (g_fail) {
246 std::fprintf(stderr, "%d check(s) failed\n", g_fail);
247 return 1;
248 }
249 std::printf("all checks passed\n");
250 return 0;
251}
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
double dotTwoLocal(CCConst af, CCConst bf, CCConst as, CCConst bs, C3 e, int g)
void dot2TwoReduce(CCConst af, CCConst bf, CCConst cf, CCConst as, CCConst bs, CCConst cs, C3 e, int g, R1 &&r1, R2 &&r2)
(sum a b, sum c c) over inner cells, both fields, in one pass, into (r1, r2).
void dot2Reduce(CCConst a, CCConst b, CCConst c, C3 e, int g, R1 &&r1, R2 &&r2)
(sum a b, sum c c) over inner cells in one pass, into (r1, r2).
void dot2Local(CCConst a, CCConst b, CCConst c, C3 e, int g, double &ab, double &cc)
void maxabsReduce(CCConst a, C3 e, int g, Red red)
max over inner cells of |a|, through the Max reducer red (Kokkos::Max over a host double or a device ...
double dotLocal(CCConst a, CCConst b, C3 e, int g)
void dot2TwoLocal(CCConst af, CCConst bf, CCConst cf, CCConst as, CCConst bs, CCConst cs, C3 e, int g, double &ab, double &cc)
void dotTwoReduce(CCConst af, CCConst bf, CCConst as, CCConst bs, C3 e, int g, R &&result)
sum over inner cells of af bf + as bs (the two-field dot).
double maxabsLocal(CCConst a, C3 e, int g)
void dotReduce(CCConst a, CCConst b, C3 e, int g, R &&result)
sum over inner cells of a(i) b(i), into result.
Kokkos::View< double, CCMem > Slot
ScalarKrylovResult scalarBiCGStab(Ops &ops, const ScalarVec &b, const ScalarVec &x, const ScalarVec &r, const ScalarVec &rh, const ScalarVec &p, const ScalarVec &v, const ScalarVec &t, const ScalarVec &z, const ScalarVec &z2, int maxit, double rtol, bool singular)
BiCGStab on A x = b.
void residentKrylov(State &st, ScalarKrylovOps &ops)
Arm ops for the resident pass with the persistent slots of st (ks, pk, allocated once).
void ccFor3(const char *name, C3 lo, C3 hi, F f)
Kokkos::View< double *, CCMem > CCField
Kokkos::View< const double *, CCMem > CCConst
The injected pieces as callables (host-side calls that launch kernels; the overhead of the indirectio...
Outcome of one scalarBiCGStab call.
A Krylov vector (§5.1): the fluid field and, for a conjugate scalar, the solid field (WO-7).
static constexpr int N
static constexpr int G
static bool compare(const char *what, const std::vector< Row > &built, const std::vector< Row > &predicted, int rank)
static bool same(double a, double b)
#define CHECK(cond)