flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
mg_bottom_direct.hpp
Go to the documentation of this file.
1
40#pragma once
41
42#include <Kokkos_Core.hpp>
43
44#include "mac_cutcell.hpp"
45
46namespace peclet::flow {
47
48// Unrolling the single-accumulator dot loops keeps several independent loads in flight; it changes
49// no operation and no order (bitwise-neutral).
50#if defined(__CUDACC__) || defined(__HIPCC__)
51#define PECLET_BOTTOM_UNROLL _Pragma("unroll 16")
52#else
53#define PECLET_BOTTOM_UNROLL
54#endif
55// The host factor's lane loops (one output per iteration, no reduction): `omp simd` on a host
56// compiler. With -ffp-contract=off (the host flags) vectorizing them changes no bit.
57#if defined(__CUDACC__) || defined(__HIPCC__)
58#define PECLET_BOTTOM_HOST_SIMD
59#else
60#define PECLET_BOTTOM_HOST_SIMD PECLET_FLOW_OMP_SIMD
61#endif
62
63inline constexpr int kBottomMaxPlane = 192; // b cap (§13.9 Q-D5)
64inline constexpr int kBottomTile = 16; // Cholesky tile (compile time: it fixes the order)
65inline constexpr double kBottomPivotTol = 1e-6;
66inline constexpr int kBottomAttempts = 3; // shifts 0, 1e-4, 1e-2
68 return attempt == 0 ? 0.0 : (attempt == 1 ? 1e-4 : 1e-2);
69}
70
71// The factor ordering, decided on the host at hierarchy build from the domain BC flags (§13.4.1).
72// Plane k = the coordinate along the slow axis s (P = n_s planes); the in-plane index is
73// q = c[a0] + n[a0] * c[a1] with a0 < a1 the other two axes; the factor-order index is k*b + q.
75 int s = -1; // slow axis; -1 = no axis gives b <= kBottomMaxPlane (ineligible)
76 int a0 = 0, a1 = 1;
77 int P = 0, b = 0;
78 int n[3] = {0, 0, 0}; // the bottom's inner dims
79 int bcf[6] = {0, 0, 0, 0, 0, 0}; // per face (-x,+x,-y,+y,-z,+z): 1 = non-periodic (excluded)
80 int border = 0; // periodic slow axis with P >= 3
81 KOKKOS_INLINE_FUNCTION bool valid() const { return s >= 0; }
82};
83
84// §13.4.1: the candidates are the non-periodic axes (all three if there are none); the longest
85// wins, a tie to the higher index (z > y > x). If its plane b = n / n_s exceeds kBottomMaxPlane,
86// the remaining axes are tried in decreasing length (the same tie rule); none fits -> s = -1.
87inline BottomPlanes bottomChoosePlanes(C3 inner, const int bc[6]) {
89 p.n[0] = inner.x;
90 p.n[1] = inner.y;
91 p.n[2] = inner.z;
92 int per[3];
93 for (int f = 0; f < 6; ++f)
94 p.bcf[f] = bc[f] != 0 ? 1 : 0;
95 for (int a = 0; a < 3; ++a)
96 per[a] = (bc[2 * a] == 0 && bc[2 * a + 1] == 0) ? 1 : 0;
97 const long ncell = (long)inner.x * inner.y * inner.z;
98 auto longer = [&](int a, int c) { return p.n[a] > p.n[c] || (p.n[a] == p.n[c] && a > c); };
99 int first = -1;
100 const bool anyWall = !per[0] || !per[1] || !per[2];
101 for (int a = 0; a < 3; ++a)
102 if ((!anyWall || !per[a]) && (first < 0 || longer(a, first)))
103 first = a;
104 auto fits = [&](int a) { return p.n[a] > 0 && ncell / p.n[a] <= kBottomMaxPlane; };
105 int s = -1;
106 if (fits(first)) {
107 s = first;
108 } else {
109 int r[2], m = 0;
110 for (int a = 0; a < 3; ++a)
111 if (a != first)
112 r[m++] = a;
113 if (longer(r[1], r[0])) {
114 const int t = r[0];
115 r[0] = r[1];
116 r[1] = t;
117 }
118 for (int i = 0; i < 2 && s < 0; ++i)
119 if (fits(r[i]))
120 s = r[i];
121 }
122 if (s < 0)
123 return p;
124 p.s = s;
125 p.a0 = (s == 0) ? 1 : 0;
126 p.a1 = (s == 2) ? 1 : 2;
127 p.P = p.n[s];
128 p.b = (int)(ncell / p.P);
129 p.border = (per[s] && p.P >= 3) ? 1 : 0;
130 return p;
131}
132
133// The direct engine's device storage and its M (§13.4.4, §13.4.7). Ghost width 1 (single rank).
134template <class FacReal>
136 using Mat = Kokkos::View<FacReal***, Kokkos::LayoutRight, CCMem>;
137 using Sq = Kokkos::View<FacReal**, Kokkos::LayoutRight, CCMem>;
139 C3 ext{0, 0, 0};
140 Mat Q; // [P][b][b]: Q(k, j, i) = (Sigma_k^{-1})_{ij} (symmetric, stored both ways)
141 Mat Y; // [P-1][b][b] (border only): Y(k, j, i) = Y_k(i, j)
142 Sq e; // [P][b]: e(k, q) = A~((q,k), (q,k-1)); e(0, .) the wrap coupling (border only)
143 Sq Sg, L, W; // [b][b] factor scratch: Sigma (lower), L, W = L^{-1} (both lower)
144 Kokkos::View<double*, CCMem> s; // [n] the scaling, factor order
145 Kokkos::View<FacReal*, CCMem> g, u; // [n] substitution vectors
146 // [0] facOk, [1] restarts, [2 + a] attempt a's fail flag (one slot per attempt: a flag that
147 // was reset for the next attempt could be read by a thread still leaving the last one)
148 Kokkos::View<int*, CCMem> stat;
149
151 BottomDirect d;
152 d.pl = p;
153 d.ext = ext;
154 if (!p.valid())
155 return d;
156 const int n = p.P * p.b;
157 d.Q = Mat("peclet::flow::mg_bottom_Q", p.P, p.b, p.b);
158 if (p.border)
159 d.Y = Mat("peclet::flow::mg_bottom_Y", p.P - 1, p.b, p.b);
160 d.e = Sq("peclet::flow::mg_bottom_e", p.P, p.b);
161 d.Sg = Sq("peclet::flow::mg_bottom_sigma", p.b, p.b);
162 d.L = Sq("peclet::flow::mg_bottom_L", p.b, p.b);
163 d.W = Sq("peclet::flow::mg_bottom_W", p.b, p.b);
164 d.s = Kokkos::View<double*, CCMem>("peclet::flow::mg_bottom_s", n);
165 d.g = Kokkos::View<FacReal*, CCMem>("peclet::flow::mg_bottom_g", n);
166 d.u = Kokkos::View<FacReal*, CCMem>("peclet::flow::mg_bottom_u", n);
167 d.stat = Kokkos::View<int*, CCMem>("peclet::flow::mg_bottom_stat", 2 + kBottomAttempts);
168 return d;
169 }
170 KOKKOS_INLINE_FUNCTION int n() const { return pl.P * pl.b; }
171 // factor index f -> inner coordinates (0-based) and the flat ext index
172 KOKKOS_INLINE_FUNCTION void coords(int f, int c[3]) const {
173 const int k = f / pl.b, q = f - k * pl.b;
174 const int c1 = q / pl.n[pl.a0];
175 c[pl.s] = k;
176 c[pl.a0] = q - c1 * pl.n[pl.a0];
177 c[pl.a1] = c1;
178 }
179 KOKKOS_INLINE_FUNCTION int extIndex(const int c[3]) const {
180 return (c[0] + 1) + (c[1] + 1) * ext.x + (c[2] + 1) * ext.x * ext.y;
181 }
182 KOKKOS_INLINE_FUNCTION int extOf(int f) const {
183 int c[3];
184 coords(f, c);
185 return extIndex(c);
186 }
187 KOKKOS_INLINE_FUNCTION int factorIndex(const int c[3]) const {
188 return c[pl.s] * pl.b + c[pl.a0] + pl.n[pl.a0] * c[pl.a1];
189 }
190 // Face f6 (0..5 = -x, +x, -y, +y, -z, +z) of the cell at c (ext index i): its coefficient (the
191 // A2 face form: the low face of i, or of i's +axis neighbour) and the neighbour's coordinates,
192 // wrapped on a periodic axis; false where it crosses a non-periodic domain face (GraphAMG's
193 // exclusion: no partner, and no part of the re-summed diagonal).
194 template <class OpV>
195 KOKKOS_INLINE_FUNCTION bool face(const OpV& AFX, const OpV& AFY, const OpV& AFZ, const int c[3],
196 int i, int f6, int nb[3], double& af) const {
197 const int a = f6 >> 1, side = f6 & 1;
198 nb[0] = c[0];
199 nb[1] = c[1];
200 nb[2] = c[2];
201 int v = c[a] + (side ? 1 : -1);
202 if (v < 0 || v >= pl.n[a]) {
203 if (pl.bcf[f6])
204 return false;
205 v = (v < 0) ? v + pl.n[a] : v - pl.n[a];
206 }
207 nb[a] = v;
208 const int st = (a == 0) ? 1 : (a == 1 ? ext.x : ext.x * ext.y);
209 const int slotIdx = side ? i + st : i;
210 af = (a == 0) ? (double)AFX(slotIdx) : (a == 1 ? (double)AFY(slotIdx) : (double)AFZ(slotIdx));
211 return true;
212 }
213
214 // M (§13.4.4): z = s * x~ on the fluid cells (comp >= 0), 0 on the other inner cells, where
215 // x~ = (A~')^{-1} FacReal(s r) by the stored factor. With `keep`, z's old inner values go there
216 // first (the FCG's z_prev), fused into the cast phase. The caller removes the component means.
217 template <class Member, class RV, class ZV, class CV>
218 KOKKOS_INLINE_FUNCTION void apply(const Member& t, const RV& r, const ZV& z, const CV& comp,
219 const ZV* keep) const {
220 const int P = pl.P, b = pl.b, nn = P * b;
221 const int Pt = pl.border ? P - 1 : P;
222 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, nn), [&](int f) {
223 const int ei = extOf(f);
224 if (keep)
225 (*keep)(ei) = z(ei);
226 u(f) = (FacReal)(s(f) * r(ei));
227 });
228 t.team_barrier();
229 for (int k = 0; k < Pt; ++k) { // forward
230 const int o = k * b, op = o - b;
231 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b), [&](int i) {
232 FacReal acc = 0;
233 if (k == 0) {
235 for (int j = 0; j < b; ++j)
236 acc += Q(k, j, i) * u(o + j);
237 } else {
239 for (int j = 0; j < b; ++j)
240 acc += Q(k, j, i) * (u(o + j) - e(k, j) * g(op + j));
241 }
242 g(o + i) = acc;
243 });
244 t.team_barrier();
245 }
246 for (int k = Pt - 2; k >= 0; --k) { // backward (in place: x~ overwrites g)
247 const int o = k * b, on = o + b;
248 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b), [&](int i) {
249 FacReal acc = 0;
251 for (int j = 0; j < b; ++j)
252 acc += Q(k, j, i) * (e(k + 1, j) * g(on + j));
253 g(o + i) = g(o + i) - acc;
254 });
255 t.team_barrier();
256 }
257 if (pl.border) { // x~_B = Q_B (r~_B - e_0 u_0 - e_{P-1} u_{P-2}); x~_k = u_k - Y_k x~_B
258 const int oB = (P - 1) * b, oL = (P - 2) * b;
259 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b), [&](int i) {
260 FacReal acc = 0;
262 for (int j = 0; j < b; ++j)
263 acc += Q(P - 1, j, i) * ((u(oB + j) - e(0, j) * g(j)) - e(P - 1, j) * g(oL + j));
264 g(oB + i) = acc;
265 });
266 t.team_barrier();
267 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, oB), [&](int f) {
268 const int k = f / b, i = f - k * b;
269 FacReal acc = 0;
271 for (int j = 0; j < b; ++j)
272 acc += Y(k, j, i) * g(oB + j);
273 g(f) = g(f) - acc;
274 });
275 t.team_barrier();
276 }
277 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, nn), [&](int f) {
278 const int ei = extOf(f);
279 z(ei) = (comp(ei) >= 0) ? s(f) * (double)g(f) : 0.0;
280 });
281 t.team_barrier();
282 }
283};
284
285// The factor launch (§13.4.3): one team, TeamPolicy(1, T). Inputs: the bottom level's face form,
286// its component labels (comp, ext-indexed; -1 = solid) and the per-component augmentation (kc =
287// the last plane, aug = 1/m_c; BottomLabelKernel). Output: D.Q, D.Y, D.e, D.s and D.stat.
288template <class FacReal, class OpV>
290 using Member = Kokkos::TeamPolicy<CCExec>::member_type;
291 // Level-0 (team) scratch, a bitwise-neutral staging of the factor's working set (§13.4.7): Ls =
292 // the current tile column of L (rows j0..b-1, the tile's columns; padded to kBottomTile + 1 so a
293 // warp's column reads hit distinct banks), St = the current diagonal tile of Sigma.
294 using SMat = Kokkos::View<FacReal**, Kokkos::LayoutRight, typename CCExec::scratch_memory_space,
295 Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
296 static std::size_t scratchBytes(int b) {
297 return SMat::shmem_size(b, kBottomTile + 1) + SMat::shmem_size(kBottomTile, kBottomTile + 1);
298 }
301 Kokkos::View<const int*, CCMem> comp, kc;
302 Kokkos::View<const double*, CCMem> aug;
303 double tauPiv0 = kBottomPivotTol; // the first attempt's pivot floor (a test hook raises it)
304 // A host backend runs the host schedule (hostAssemble / hostInvert / hostBorder) unless this
305 // test hook asks for the team algorithm, its bitwise oracle (ctest `bottom_direct` U8).
306 static constexpr bool kHostFactor = std::is_same_v<CCMem, Kokkos::HostSpace>;
308
309 // s (§13.4.1): the re-summed diagonal and the scaling, one thread per cell
310 KOKKOS_INLINE_FUNCTION void scaling(const Member& t) const {
311 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, D.n()), [&](int f) {
312 int c[3], nb[3];
313 D.coords(f, c);
314 const int i = D.extIndex(c);
315 double sum = 0.0, af = 0.0;
316 for (int f6 = 0; f6 < 6; ++f6)
317 if (D.face(AFX, AFY, AFZ, c, i, f6, nb, af))
318 sum += af;
319 const double d = -sum;
320 D.s(f) = (comp(i) >= 0 && d > 0.0) ? 1.0 / Kokkos::sqrt(d) : 0.0;
321 });
322 t.team_barrier();
323 }
324 // e (§13.4.1): e(k, q) accumulates, over cell (q,k)'s faces in order, s_i AF s_j for the faces
325 // whose neighbour lies in plane k-1 (mod P) != k -- for k = 0 only on a border axis. With P = 2
326 // on a periodic axis both faces of plane 1 land in e(1, .) and e(0, .) stays 0.
328 const int P = D.pl.P;
329 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, D.n()), [&](int f) {
330 int c[3], nb[3];
331 D.coords(f, c);
332 const int i = D.extIndex(c), k = c[D.pl.s], q = f - k * D.pl.b;
333 const int km = (k - 1 + P) % P;
334 double v = 0.0, af = 0.0;
335 if (km != k && (k >= 1 || D.pl.border)) {
336 const double si = D.s(f);
337 for (int f6 = 0; f6 < 6; ++f6)
338 if (D.face(AFX, AFY, AFZ, c, i, f6, nb, af) && nb[D.pl.s] == km)
339 v += si * af * D.s(D.factorIndex(nb));
340 }
341 D.e(k, q) = (FacReal)v;
342 });
343 t.team_barrier();
344 }
345 // A^_k(i, j) in FP64 (§13.4.1-2): diagonal 1, + s_i AF s_j over cell i's faces whose neighbour
346 // is in-plane cell j (in order), + 1/m_c on component c's last plane, + delta on the diagonal.
347 KOKKOS_INLINE_FUNCTION double entry(int k, int i, int j, double delta) const {
348 const int b = D.pl.b, fi = k * b + i, fj = k * b + j;
349 int c[3], nb[3];
350 D.coords(fi, c);
351 const int ei = D.extIndex(c);
352 double v = (i == j) ? 1.0 : 0.0, af = 0.0;
353 const double si = D.s(fi);
354 for (int f6 = 0; f6 < 6; ++f6)
355 if (D.face(AFX, AFY, AFZ, c, ei, f6, nb, af) && D.factorIndex(nb) == fj)
356 v += si * af * D.s(fj);
357 const int ci = comp(ei);
358 if (ci >= 0 && ci < (int)kc.extent(0) && kc(ci) == k && comp(D.extOf(fj)) == ci)
359 v += aug(ci);
360 if (i == j)
361 v += delta;
362 return v;
363 }
364 // Sigma's lower triangle for plane k: mode 0 = A^_k; 1 = A^_k - diag(e_k) Q_{k-1} diag(e_k);
365 // 2 = the border A^_{P-1} - diag(e_0) Y_0 - diag(e_{P-1}) Y_{P-2}.
366 KOKKOS_INLINE_FUNCTION void assemble(const Member& t, int k, int mode, double delta,
367 const SMat& St) const {
368 const int b = D.pl.b, P = D.pl.P;
369 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b * b), [&](int idx) {
370 const int i = idx / b, j = idx - i * b;
371 if (j > i)
372 return;
373 FacReal a = (FacReal)entry(k, i, j, delta);
374 if (mode == 1)
375 a = a - D.e(k, i) * D.Q(k - 1, i, j) * D.e(k, j);
376 else if (mode == 2)
377 a = (a - D.e(0, i) * D.Y(0, j, i)) - D.e(P - 1, i) * D.Y(P - 2, j, i);
378 D.Sg(i, j) = a;
379 if (i < kBottomTile)
380 St(i, j) = a; // the first diagonal tile
381 });
382 t.team_barrier();
383 }
384 // Q(k) = Sigma^{-1} (§13.4.3, "Dense SPD inverse"). Returns false (team-uniform) on a pivot
385 // that is not > tauPiv (a NaN pivot included). Sigma's lower triangle is read from D.Sg and its
386 // current diagonal tile from St; the current tile column of L is kept in Ls (and stored to D.L
387 // for W). Each stored scalar is the same expression, in the same order, as without the staging.
388 KOKKOS_INLINE_FUNCTION bool invert(const Member& t, int k, double tauPiv, int flag,
389 const SMat& Ls, const SMat& St) const {
390 const int b = D.pl.b;
391 const auto& S = D.Sg;
392 const auto& L = D.L;
393 const auto& W = D.W;
394 for (int j0 = 0; j0 < b; j0 += kBottomTile) {
395 const int j1 = (j0 + kBottomTile < b) ? j0 + kBottomTile : b;
396 const int nc = j1 - j0;
397 // 1. the diagonal tile, column by column: row r of the tile (one thread) computes L(r, c)
398 // for every column c < r and, right after L(r, r - 1), its own pivot L(r, r) -- each
399 // scalar the single-thread column Cholesky's expression in its order; one barrier per
400 // column publishes the column.
401 for (int c = -1; c < nc; ++c) {
402 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, nc), [&](int r) {
403 if (r <= c)
404 return;
405 const int i = j0 + r;
406 if (c >= 0) { // L(i, j) for j = j0 + c
407 const int j = j0 + c;
408 FacReal a2 = 0;
409 for (int m = 0; m < c; ++m)
410 a2 += Ls(i, m) * Ls(j, m);
411 const FacReal lij = (St(r, c) - a2) / Ls(j, c);
412 Ls(i, c) = lij;
413 L(i, j) = lij;
414 }
415 if (r == c + 1) { // the pivot of column r: every L(i, m < r) is this thread's own
416 FacReal acc = 0;
417 for (int m = 0; m < r; ++m)
418 acc += Ls(i, m) * Ls(i, m);
419 const FacReal p = St(r, r) - acc;
420 if (!((double)p > tauPiv))
421 D.stat(flag) = 1;
422 const FacReal lii = Kokkos::sqrt(p);
423 Ls(i, r) = lii;
424 L(i, i) = lii;
425 }
426 });
427 t.team_barrier();
428 }
429 if (D.stat(flag))
430 return false;
431 if (j1 < b) {
432 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, j1, b), [&](int i) { // 2. the panel
433 for (int c = 0; c < nc; ++c) {
434 const int j = j0 + c;
435 FacReal acc = 0;
436 for (int m = 0; m < c; ++m)
437 acc += Ls(i, m) * Ls(j, m);
438 const FacReal lij = (S(i, j) - acc) / Ls(j, c);
439 Ls(i, c) = lij;
440 L(i, j) = lij;
441 }
442 });
443 t.team_barrier();
444 const int nt = b - j1; // 3. the trailing update, i >= j below the tile
445 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, nt * nt), [&](int idx) {
446 const int i = j1 + idx / nt, j = j1 + (idx - (idx / nt) * nt);
447 if (j > i)
448 return;
449 FacReal acc = 0;
450 for (int m = 0; m < nc; ++m)
451 acc += Ls(i, m) * Ls(j, m);
452 const FacReal v = S(i, j) - acc;
453 S(i, j) = v;
454 if (i < j1 + kBottomTile)
455 St(i - j1, j - j1) = v; // the next diagonal tile
456 });
457 t.team_barrier();
458 }
459 }
460 // W = L^{-1}: W(j, j) = 1 / L(j, j), W(i, j) = -(sum_{m=j}^{i-1} L(i, m) W(m, j)) / L(i, i) in
461 // ascending m, by row blocks of kBottomTile: (1) every thread takes one (row i of the block,
462 // column j left of it) and sums the terms m < i0 into A(r, j) (Ls's storage); (2) thread j
463 // continues the SAME accumulator over the block's own rows (the L tile staged in St, its new
464 // W(m, j) in registers) -- each W(i, j) is the column recurrence's expression in its order.
465 const SMat A(Ls.data(), kBottomTile, b);
466 for (int i0 = 0; i0 < b; i0 += kBottomTile) {
467 const int i1 = (i0 + kBottomTile < b) ? i0 + kBottomTile : b;
468 const int nr = i1 - i0;
469 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, nr * i0 + nr * nr), [&](int idx) {
470 if (idx < nr * i0) {
471 const int r = idx / i0, j = idx - r * i0, i = i0 + r;
472 FacReal acc = 0;
474 for (int m = j; m < i0; ++m)
475 acc += L(i, m) * W(m, j);
476 A(r, j) = acc;
477 } else {
478 const int q = idx - nr * i0, r = q / nr, c = q - r * nr;
479 if (c <= r)
480 St(r, c) = L(i0 + r, i0 + c);
481 }
482 });
483 t.team_barrier();
484 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, i1), [&](int j) {
485 FacReal wb[kBottomTile]; // W(i0 + r, j) of this block, this column
486 for (int r = 0; r < kBottomTile; ++r) {
487 if (r >= nr)
488 break;
489 const int i = i0 + r;
490 if (j > i) {
491 wb[r] = 0;
492 continue;
493 }
494 if (j == i) {
495 wb[r] = FacReal(1) / St(r, r);
496 W(i, j) = wb[r];
497 continue;
498 }
499 FacReal acc = (j < i0) ? A(r, j) : FacReal(0);
500 const int m0 = (j > i0) ? j : i0;
501 for (int m = m0; m < i; ++m)
502 acc += St(r, m - i0) * wb[m - i0];
503 wb[r] = -acc / St(r, r);
504 W(i, j) = wb[r];
505 }
506 });
507 t.team_barrier();
508 }
509 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b * b), [&](int idx) { // Q = W^T W
510 const int i = idx / b, j = idx - i * b;
511 if (j > i)
512 return;
513 FacReal acc = 0;
515 for (int m = i; m < b; ++m)
516 acc += W(m, i) * W(m, j);
517 D.Q(k, i, j) = acc;
518 D.Q(k, j, i) = acc;
519 });
520 t.team_barrier();
521 return true;
522 }
523 // The border's Y = T^{-1} B by the solve's own substitution, one column c per (c, i) pair:
524 // B's block 0 = diag(e_0), block P-2 = diag(e_{P-1}).
525 KOKKOS_INLINE_FUNCTION void border(const Member& t) const {
526 const int P = D.pl.P, b = D.pl.b;
527 for (int k = 0; k <= P - 2; ++k) { // forward
528 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b * b), [&](int idx) {
529 const int c = idx / b, i = idx - c * b;
530 FacReal acc = 0;
532 for (int j = 0; j < b; ++j) {
533 FacReal bj = 0;
534 if (j == c)
535 bj = (k == 0 ? D.e(0, j) : FacReal(0)) + (k == P - 2 ? D.e(P - 1, j) : FacReal(0));
536 const FacReal br = (k == 0) ? bj : bj - D.e(k, j) * D.Y(k - 1, c, j);
537 acc += D.Q(k, j, i) * br;
538 }
539 D.Y(k, c, i) = acc;
540 });
541 t.team_barrier();
542 }
543 for (int k = P - 3; k >= 0; --k) { // backward
544 Kokkos::parallel_for(Kokkos::TeamThreadRange(t, b * b), [&](int idx) {
545 const int c = idx / b, i = idx - c * b;
546 FacReal acc = 0;
548 for (int j = 0; j < b; ++j)
549 acc += D.Q(k, j, i) * (D.e(k + 1, j) * D.Y(k + 1, c, j));
550 D.Y(k, c, i) = D.Y(k, c, i) - acc;
551 });
552 t.team_barrier();
553 }
554 }
555
556 // --- The host schedule (§14 H-1, A(b)) ------------------------------------------------------
557 // The same scalars as assemble / invert / border, each the same expression with its operands in
558 // the same order; what changes is who computes what between barriers. Scratch layouts (host
559 // only): Sg holds Sigma's lower triangle by COLUMNS, S[j b + i] = Sigma(i, j) for i >= j; L holds
560 // L by columns, Lt[m b + i] = L(i, m); W is row-major W(i, j) as on a device. A lane loop runs
561 // over independent outputs (rows i, or columns j), each lane continuing its own accumulator in
562 // ascending order, so vectorizing it reorders nothing. A thread takes a contiguous share of the
563 // outputs, balanced by their cost (hostShare), or every T-th column where columns are far apart.
564
565 // acc[i] += x[m b + i] * y[m b] for m = m0 .. m1-1 IN THAT ORDER, each lane i in [lo, hi): four
566 // m per pass over the lanes, so acc is loaded and stored once per four terms -- each lane still
567 // adds its terms one at a time in ascending m.
569 const FacReal* y, int b, int m0, int m1, int lo,
570 int hi) {
571 int m = m0;
572 for (; m + 4 <= m1; m += 4) {
573 const FacReal *x0 = x + m * b, *x1 = x0 + b, *x2 = x1 + b, *x3 = x2 + b;
574 const FacReal y0 = y[m * b], y1 = y[(m + 1) * b], y2 = y[(m + 2) * b], y3 = y[(m + 3) * b];
576 for (int i = lo; i < hi; ++i) {
577 FacReal a = acc[i];
578 a += x0[i] * y0;
579 a += x1[i] * y1;
580 a += x2[i] * y2;
581 a += x3[i] * y3;
582 acc[i] = a;
583 }
584 }
585 for (; m < m1; ++m) {
586 const FacReal* xm = x + m * b;
587 const FacReal ym = y[m * b];
589 for (int i = lo; i < hi; ++i)
590 acc[i] += xm[i] * ym;
591 }
592 }
593 // [lo, hi): this thread's contiguous share of [0, n) when index i costs w(i) -- equal prefix
594 // shares of the total. Contiguous, not round-robin: neighbouring outputs share cache lines.
595 template <class Wf>
596 KOKKOS_INLINE_FUNCTION static void hostShare(const Member& t, int n, const Wf& w, int& lo,
597 int& hi) {
598 const int rank = t.team_rank(), T = t.team_size();
599 double tot = 0.0;
600 for (int i = 0; i < n; ++i)
601 tot += w(i);
602 lo = n;
603 hi = n;
604 double cum = 0.0;
605 for (int i = 0; i < n; ++i) {
606 if (lo == n && cum >= tot * rank / T)
607 lo = i;
608 if (cum >= tot * (rank + 1) / T) {
609 hi = i;
610 break;
611 }
612 cum += w(i);
613 }
614 if (lo > hi)
615 lo = hi;
616 }
617 // assemble: the team version's entry() per (i, j) re-scans cell i's six faces for every j; here
618 // a row's dense values start at entry()'s initial value and each face adds its term to its own
619 // partner j in face order -- per (i, j) the same additions in the same order -- then the
620 // augmentation, then delta on the diagonal, then the cast and the mode's update.
621 KOKKOS_INLINE_FUNCTION void hostAssemble(const Member& t, int k, int mode, double delta) const {
622 const int b = D.pl.b, P = D.pl.P, s = D.pl.s;
623 FacReal* S = D.Sg.data();
624 int lo, hi;
625 hostShare(t, b, [](int i) { return (double)(i + 1); }, lo, hi);
626 for (int i = lo; i < hi; ++i) {
627 double v[kBottomMaxPlane];
628 for (int j = 0; j < i; ++j)
629 v[j] = 0.0;
630 v[i] = 1.0;
631 const int fi = k * b + i;
632 int c[3], nb[3];
633 D.coords(fi, c);
634 const int ei = D.extIndex(c);
635 const double si = D.s(fi);
636 double af = 0.0;
637 for (int f6 = 0; f6 < 6; ++f6)
638 if (D.face(AFX, AFY, AFZ, c, ei, f6, nb, af) && nb[s] == k) {
639 const int fj = D.factorIndex(nb);
640 if (fj - k * b <= i)
641 v[fj - k * b] += si * af * D.s(fj);
642 }
643 const int ci = comp(ei);
644 if (ci >= 0 && ci < (int)kc.extent(0) && kc(ci) == k)
645 for (int j = 0; j <= i; ++j)
646 if (comp(D.extOf(k * b + j)) == ci)
647 v[j] += aug(ci);
648 v[i] += delta;
649 for (int j = 0; j <= i; ++j) {
650 FacReal a = (FacReal)v[j];
651 if (mode == 1)
652 a = a - D.e(k, i) * D.Q(k - 1, i, j) * D.e(k, j);
653 else if (mode == 2)
654 a = (a - D.e(0, i) * D.Y(0, j, i)) - D.e(P - 1, i) * D.Y(P - 2, j, i);
655 S[j * b + i] = a;
656 }
657 }
658 t.team_barrier();
659 }
660 // invert: per tile, ONE thread factors the diagonal tile (row by row: L(i, j) for j < i, then
661 // the pivot) and the panel below it (column by column, a lane per row), one barrier; the team
662 // applies the trailing update (a column per thread, a lane per row), one barrier. Then W = L^{-1}
663 // by rows (a lane per column; each thread a contiguous column range, rows in order) and Q = W^T W
664 // by rows (a lane per column) -- one barrier each. The lane loops are long (up to b), so the
665 // accumulators' loads and stores pipeline instead of forming a latency chain.
666 KOKKOS_INLINE_FUNCTION bool hostInvert(const Member& t, int k, double tauPiv, int flag) const {
667 const int b = D.pl.b, rank = t.team_rank(), T = t.team_size();
668 FacReal* S = D.Sg.data();
669 FacReal* Lt = D.L.data();
670 FacReal* W = D.W.data();
671 for (int j0 = 0; j0 < b; j0 += kBottomTile) {
672 const int j1 = (j0 + kBottomTile < b) ? j0 + kBottomTile : b;
673 const int nc = j1 - j0;
674 Kokkos::single(Kokkos::PerTeam(t), [&]() {
675 for (int r = 0; r < nc; ++r) { // 1. the diagonal tile
676 const int i = j0 + r;
677 for (int c = 0; c < r; ++c) {
678 const int j = j0 + c;
679 FacReal a2 = 0;
680 for (int m = 0; m < c; ++m)
681 a2 += Lt[(j0 + m) * b + i] * Lt[(j0 + m) * b + j];
682 Lt[j * b + i] = (S[j * b + i] - a2) / Lt[j * b + j];
683 }
684 FacReal acc = 0;
685 for (int m = 0; m < r; ++m)
686 acc += Lt[(j0 + m) * b + i] * Lt[(j0 + m) * b + i];
687 const FacReal p = S[i * b + i] - acc;
688 if (!((double)p > tauPiv))
689 D.stat(flag) = 1;
690 Lt[i * b + i] = Kokkos::sqrt(p);
691 }
692 if (D.stat(flag))
693 return;
694 for (int c = 0; c < nc; ++c) { // 2. the panel, rows j1..b-1
695 const int j = j0 + c;
698 for (int i = j1; i < b; ++i)
699 acc[i] = 0;
700 hostAccumulate(acc, Lt + j0 * b, Lt + j0 * b + j, b, 0, c, j1, b);
701 const FacReal ljj = Lt[j * b + j];
702 FacReal* Lj = Lt + j * b;
703 const FacReal* Sj = S + j * b;
705 for (int i = j1; i < b; ++i)
706 Lj[i] = (Sj[i] - acc[i]) / ljj;
707 }
708 });
709 t.team_barrier();
710 if (D.stat(flag))
711 return false;
712 if (j1 < b) {
713 for (int j = j1 + rank; j < b; j += T) { // 3. the trailing update, column j, rows j..b-1
716 for (int i = j; i < b; ++i)
717 acc[i] = 0;
718 hostAccumulate(acc, Lt + j0 * b, Lt + j0 * b + j, b, 0, nc, j, b);
719 FacReal* Sj = S + j * b;
721 for (int i = j; i < b; ++i)
722 Sj[i] = Sj[i] - acc[i];
723 }
724 t.team_barrier();
725 }
726 }
727 // W(i, j) = -(sum_{m=j}^{i-1} L(i, m) W(m, j)) / L(i, i), W(i, i) = 1 / L(i, i): row i of this
728 // thread's columns [ja, jz) (equal shares of the (b - j)^2 work) sums m = ja .. i-1 in order,
729 // lane j taking the term m once m >= j -- each lane's sum is m = j .. i-1, ascending.
730 int ja, jz;
731 hostShare(t, b, [&](int j) { return (double)(b - j) * (b - j); }, ja, jz);
732 for (int i = ja; i < b; ++i) {
734 const int jt = (i < jz) ? i : jz; // lanes ja .. jt-1 lie left of the diagonal
736 for (int j = ja; j < jt; ++j)
737 acc[j] = 0;
738 int m = ja;
739 for (; m + 4 <= i; m += 4) { // terms m .. m+3: all four for the lanes j <= m, ...
740 hostAccumulate(acc, W, Lt + i, b, m, m + 4, ja, (m + 1 < jz) ? m + 1 : jz);
741 for (int d = 1; d < 4 && m + d < jz; ++d) // ... m+d .. m+3 for lane m+d (its first)
742 for (int q = m + d; q < m + 4; ++q)
743 acc[m + d] += Lt[q * b + i] * W[q * b + m + d];
744 }
745 for (; m < i; ++m)
746 hostAccumulate(acc, W, Lt + i, b, m, m + 1, ja, (m + 1 < jz) ? m + 1 : jz);
747 const FacReal lii = Lt[i * b + i];
748 FacReal* Wi = W + i * b;
750 for (int j = ja; j < jt; ++j)
751 Wi[j] = -acc[j] / lii;
752 if (i < jz)
753 Wi[i] = FacReal(1) / lii;
754 }
755 t.team_barrier();
756 FacReal* Qk = &D.Q(k, 0, 0);
757 int qa, qz;
758 hostShare(t, b, [&](int i) { return (double)(b - i) * (i + 1); }, qa, qz);
759 for (int i = qa; i < qz; ++i) { // Q(i, j <= i) = sum_{m >= i} W(m, i) W(m, j)
762 for (int j = 0; j <= i; ++j)
763 acc[j] = 0;
764 hostAccumulate(acc, W, W + i, b, i, b, 0, i + 1);
765 for (int j = 0; j <= i; ++j) {
766 Qk[i * b + j] = acc[j];
767 Qk[j * b + i] = acc[j];
768 }
769 }
770 t.team_barrier();
771 return true;
772 }
773 // border: Y(., c, .) depends on its own column c only, so a thread runs one column through the
774 // whole forward and backward sweep (a lane per row i) with no barrier between the planes.
776 const int P = D.pl.P, b = D.pl.b, rank = t.team_rank(), T = t.team_size();
777 for (int c = rank; c < b; c += T) {
779 for (int k = 0; k <= P - 2; ++k) { // forward
780 for (int j = 0; j < b; ++j) {
781 FacReal bj = 0;
782 if (j == c)
783 bj = (k == 0 ? D.e(0, j) : FacReal(0)) + (k == P - 2 ? D.e(P - 1, j) : FacReal(0));
784 br[j] = (k == 0) ? bj : bj - D.e(k, j) * D.Y(k - 1, c, j);
785 }
787 for (int i = 0; i < b; ++i)
788 acc[i] = 0;
789 for (int j = 0; j < b; ++j) {
790 const FacReal* Qj = &D.Q(k, j, 0);
791 const FacReal bj = br[j];
793 for (int i = 0; i < b; ++i)
794 acc[i] += Qj[i] * bj;
795 }
796 FacReal* Yk = &D.Y(k, c, 0);
798 for (int i = 0; i < b; ++i)
799 Yk[i] = acc[i];
800 }
801 for (int k = P - 3; k >= 0; --k) { // backward
803 for (int i = 0; i < b; ++i)
804 acc[i] = 0;
805 for (int j = 0; j < b; ++j) {
806 const FacReal* Qj = &D.Q(k, j, 0);
807 const FacReal y = D.e(k + 1, j) * D.Y(k + 1, c, j);
809 for (int i = 0; i < b; ++i)
810 acc[i] += Qj[i] * y;
811 }
812 FacReal* Yk = &D.Y(k, c, 0);
814 for (int i = 0; i < b; ++i)
815 Yk[i] = Yk[i] - acc[i];
816 }
817 }
818 t.team_barrier();
819 }
820 // operator()'s control flow (attempts, shifts, flags) on the host schedule.
822 const int P = D.pl.P;
823 const int Pt = D.pl.border ? P - 1 : P;
824 Kokkos::single(Kokkos::PerTeam(t), [&]() {
825 for (int a = 0; a < kBottomAttempts; ++a)
826 D.stat(2 + a) = 0;
827 });
828 scaling(t); // (its barrier also publishes the cleared flags)
829 couplings(t);
830 int attempt = 0;
831 bool ok = false;
832 for (; attempt < kBottomAttempts && !ok; ++attempt) {
833 const double delta = bottomShift(attempt);
834 const double tauPiv = (attempt == 0) ? tauPiv0 : kBottomPivotTol;
835 ok = true;
836 for (int k = 0; k < Pt && ok; ++k) {
837 hostAssemble(t, k, k == 0 ? 0 : 1, delta);
838 ok = hostInvert(t, k, tauPiv, 2 + attempt);
839 }
840 if (ok && D.pl.border) {
841 hostBorder(t);
842 hostAssemble(t, P - 1, 2, delta);
843 ok = hostInvert(t, P - 1, tauPiv, 2 + attempt);
844 }
845 }
846 const int restarts = attempt - 1;
847 Kokkos::single(Kokkos::PerTeam(t), [&]() {
848 D.stat(0) = ok ? 1 : 0;
849 D.stat(1) = restarts;
850 });
851 }
852
854 const int P = D.pl.P;
855 const int Pt = D.pl.border ? P - 1 : P;
856 if constexpr (kHostFactor) {
857 if (!teamAlgorithm) {
858 hostFactor(t);
859 return;
860 }
861 }
862 const SMat Ls(t.team_scratch(0), D.pl.b, kBottomTile + 1);
863 const SMat St(t.team_scratch(0), kBottomTile, kBottomTile + 1);
864 Kokkos::single(Kokkos::PerTeam(t), [&]() {
865 for (int a = 0; a < kBottomAttempts; ++a)
866 D.stat(2 + a) = 0;
867 });
868 scaling(t); // (its barrier also publishes the cleared flags)
869 couplings(t);
870 int attempt = 0;
871 bool ok = false;
872 for (; attempt < kBottomAttempts && !ok; ++attempt) {
873 const double delta = bottomShift(attempt);
874 const double tauPiv = (attempt == 0) ? tauPiv0 : kBottomPivotTol;
875 ok = true;
876 for (int k = 0; k < Pt && ok; ++k) {
877 assemble(t, k, k == 0 ? 0 : 1, delta, St);
878 ok = invert(t, k, tauPiv, 2 + attempt, Ls, St);
879 }
880 if (ok && D.pl.border) {
881 border(t);
882 assemble(t, P - 1, 2, delta, St);
883 ok = invert(t, P - 1, tauPiv, 2 + attempt, Ls, St);
884 }
885 }
886 const int restarts = attempt - 1;
887 Kokkos::single(Kokkos::PerTeam(t), [&]() {
888 D.stat(0) = ok ? 1 : 0;
889 D.stat(1) = restarts;
890 });
891 }
892};
893
894} // namespace peclet::flow
flow — portable (Kokkos) cut-cell pressure-operator face openness from an SDF.
#define PECLET_BOTTOM_HOST_SIMD
#define PECLET_BOTTOM_UNROLL
double bottomShift(int attempt)
constexpr int kBottomMaxPlane
BottomPlanes bottomChoosePlanes(C3 inner, const int bc[6])
constexpr int kBottomAttempts
constexpr int kBottomTile
constexpr double kBottomPivotTol
Kokkos::View< FacReal ***, Kokkos::LayoutRight, CCMem > Mat
int factorIndex(const int c[3]) const
Kokkos::View< FacReal *, CCMem > u
Kokkos::View< FacReal *, CCMem > g
Kokkos::View< FacReal **, Kokkos::LayoutRight, CCMem > Sq
static BottomDirect allocate(const BottomPlanes &p, C3 ext)
Kokkos::View< double *, CCMem > s
void coords(int f, int c[3]) const
bool face(const OpV &AFX, const OpV &AFY, const OpV &AFZ, const int c[3], int i, int f6, int nb[3], double &af) const
void apply(const Member &t, const RV &r, const ZV &z, const CV &comp, const ZV *keep) const
Kokkos::View< int *, CCMem > stat
int extIndex(const int c[3]) const
Kokkos::View< const double *, CCMem > aug
Kokkos::View< FacReal **, Kokkos::LayoutRight, typename CCExec::scratch_memory_space, Kokkos::MemoryTraits< Kokkos::Unmanaged > > SMat
Kokkos::View< const int *, CCMem > comp
void couplings(const Member &t) const
Kokkos::View< const int *, CCMem > kc
bool hostInvert(const Member &t, int k, double tauPiv, int flag) const
void assemble(const Member &t, int k, int mode, double delta, const SMat &St) const
Kokkos::TeamPolicy< CCExec >::member_type Member
void hostAssemble(const Member &t, int k, int mode, double delta) const
void scaling(const Member &t) const
static void hostAccumulate(FacReal *acc, const FacReal *x, const FacReal *y, int b, int m0, int m1, int lo, int hi)
void operator()(const Member &t) const
void border(const Member &t) const
bool invert(const Member &t, int k, double tauPiv, int flag, const SMat &Ls, const SMat &St) const
static void hostShare(const Member &t, int n, const Wf &w, int &lo, int &hi)
static std::size_t scratchBytes(int b)
double entry(int k, int i, int j, double delta) const
void hostBorder(const Member &t) const
void hostFactor(const Member &t) const
Kokkos::View< double *, peclet::flow::CCMem > OpV