core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
facegeom_assembly.hpp
Go to the documentation of this file.
1// core — device assembly of the collocated-projection FACE-GEOMETRY tables (D4).
2//
3// buildFaceGeom (flow.hpp) walks AmrPoisson::forEachFaceFull on the host to produce the
4// per-(sub)face geometry CSR the collocated divergence / gradient / ABC face-field / FOU advection
5// kernels consume: neighbour, axis, dir, α·area, raw area, distance, openness α, and the two SOU
6// upstream-probe leaves. It is pure per-face geometry — embarrassingly parallel — so for a moving
7// boundary it should rebuild on device rather than re-walk the host and re-upload. This header does
8// that, reusing the D2 FvFaceEmit traversal helpers (decode / wrap / areaOf / openness / locate)
9// and the S1 offsets primitive.
10//
11// Bit-exactness: same forEachFaceFull enumeration (axis-major, dir −1/+1, 2:1 sub-faces), same
12// per-face formulas, each cell fills its own slice — so on OpenMP the device FaceGeom == host
13// buildFaceGeom bit-for-bit (test_amr_facegeom). GPU is tolerance-not-bit-exact (FMA), per the
14// convention.
15//
16// Requires a Kokkos build + the morton checkout (PECLET_CORE_HAVE_MORTON).
17#ifndef PECLET_CORE_AMR_FACEGEOM_ASSEMBLY_HPP
18#define PECLET_CORE_AMR_FACEGEOM_ASSEMBLY_HPP
19
20#ifdef PECLET_CORE_HAVE_MORTON
21
22#include <array>
23
24#include "peclet/core/amr/assembly.hpp" // FvFaceEmit (shared geometry traversal helpers)
26#include "peclet/core/amr/csr.hpp" // scanOffsets
27#include "peclet/core/amr/face_geom.hpp" // FaceGeom (the produced type)
30
31namespace peclet::core::amr {
32
36template <unsigned Bits = 21u>
41
44 std::array<Coord, 3> lo = M::from_code(g.ov.codes(i)).decode();
45 const Coord si = Coord(Coord(1) << g.ov.levels(i));
46 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
47 : static_cast<long>(lo[axis]) - 1;
48 std::array<Coord, 3> p = lo;
49 p[axis] = g.wrap(pc, axis);
50 return g.ov.locate(M::encode(p).code());
51 }
52
55 template <class CB>
57 std::array<Coord, 3> lo = M::from_code(g.ov.codes(i)).decode();
58 const unsigned Li = g.ov.levels(i);
59 const Coord si = Coord(Coord(1) << Li);
60 for (int axis = 0; axis < 3; ++axis)
61 for (int dir = -1; dir <= 1; dir += 2) {
62 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
63 : static_cast<long>(lo[axis]) - 1;
64 std::array<Coord, 3> p = lo;
65 p[axis] = g.wrap(pc, axis);
66 const Index j = g.ov.locate(M::encode(p).code());
67 const unsigned Lj = g.ov.levels(j);
68 if (Lj >= Li) {
69 const Coord sj = Coord(Coord(1) << Lj);
70 cb(j, axis, dir, g.areaOf(si),
71 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * g.h0,
72 g.openness(i, axis, dir));
73 } else {
74 const Coord sj = Coord(si >> 1);
75 const int nsub = 1 << 2; // 2^(Dim-1), Dim=3
76 for (int k = 0; k < nsub; ++k) {
77 std::array<Coord, 3> q = lo;
78 q[axis] = g.wrap(pc, axis);
79 int bit = 0;
80 for (int t = 0; t < 3; ++t) {
81 if (t == axis)
82 continue;
83 const Coord off = ((k >> bit) & 1) ? sj : Coord(0);
84 q[t] = g.wrap(static_cast<long>(lo[t]) + static_cast<long>(off), t);
85 ++bit;
86 }
87 const Index jj = g.ov.locate(M::encode(q).code());
88 cb(jj, axis, dir, g.areaOf(sj),
89 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * g.h0,
90 g.openness(jj, axis, -dir));
91 }
92 }
93 }
94 }
95
97 struct CountCB {
98 Index c = 0;
99 KOKKOS_INLINE_FUNCTION void operator()(Index, int, int, double, double, double) { ++c; }
100 } cb;
102 return cb.c;
103 }
104};
105
108template <unsigned Bits>
110 const BlockOctreeView<3, Bits>& ov) {
112 emit.g.ov = ov;
113 emit.g.h0 = ap.h0();
114 emit.g.periodic = ap.periodic();
115 emit.g.hasOpen = ap.hasOpenness();
116 for (int d = 0; d < 3; ++d)
117 emit.g.fineExt[d] = ap.fineExt()[d];
118 if (ap.hasOpenness())
119 emit.g.alpha = toDevice(ap.opennessRaw(), "fg::alpha");
120
121 const Index n = ov.numLeaves();
122 Index nf = 0;
123 View<Index> start = scanOffsets(n, KOKKOS_LAMBDA(const Index i) { return emit.count(i); }, nf);
124
125 FaceGeom fg;
126 fg.n = n;
127 fg.start = start;
128 // NB: pass the label as std::string — a const char* *variable* is read by view_alloc as a
129 // pointer-to-memory (only string literals are special-cased as labels).
130 auto mk = [&](const char* lbl, std::size_t m) {
131 return View<double>(Kokkos::view_alloc(std::string(lbl), Kokkos::WithoutInitializing), m);
132 };
133 auto mkI = [&](const char* lbl, std::size_t m) {
134 return View<Index>(Kokkos::view_alloc(std::string(lbl), Kokkos::WithoutInitializing), m);
135 };
136 const std::size_t F = static_cast<std::size_t>(nf);
137 fg.nbr = mkI("fg::nbr", F);
138 fg.axis = View<int>(Kokkos::view_alloc("fg::axis", Kokkos::WithoutInitializing), F);
139 fg.dir = View<int>(Kokkos::view_alloc("fg::dir", Kokkos::WithoutInitializing), F);
140 fg.alphaArea = mk("fg::aArea", F);
141 fg.rawArea = mk("fg::rArea", F);
142 fg.dist = mk("fg::dist", F);
143 fg.alpha = mk("fg::alpha", F);
144 fg.upupI = mkI("fg::upupI", F);
145 fg.upupJ = mkI("fg::upupJ", F);
146 fg.invVol = mk("fg::invVol", static_cast<std::size_t>(n));
147 fg.fluid = View<char>(Kokkos::view_alloc("fg::fluid", Kokkos::WithoutInitializing),
148 static_cast<std::size_t>(n));
149
150 View<char> fluidDev = toDevice(fluidHost, "fg::fluidIn");
151 FaceGeom out = fg; // copy of the View handles for capture
152 const FaceGeomEmit<Bits> e = emit;
153 Kokkos::parallel_for(
154 "amr::facegeom_fill", n, KOKKOS_LAMBDA(const Index i) {
155 out.invVol(i) = 1.0 / e.g.cellVolume(i);
156 out.fluid(i) = fluidDev(i);
157 // own-slice fill cursor; cb writes one (sub)face per call (forEachFaceFull order).
158 struct FillCB {
159 const FaceGeomEmit<Bits>* e;
160 const FaceGeom* o; // captured-by-value FaceGeom is const; View element writes still work
161 Index i;
162 Index k;
163 KOKKOS_INLINE_FUNCTION void operator()(Index j, int axis, int dir, double area,
164 double dist, double alpha) {
165 o->nbr(k) = j;
166 o->axis(k) = axis;
167 o->dir(k) = dir;
168 o->alphaArea(k) = alpha * area;
169 o->rawArea(k) = area;
170 o->dist(k) = dist;
171 o->alpha(k) = alpha;
172 o->upupI(k) = e->periodicNeighbor(i, axis, -dir);
173 o->upupJ(k) = e->periodicNeighbor(j, axis, dir);
174 ++k;
175 }
176 } cb{&e, &out, i, out.start(i)};
177 e.forEachFaceFull(i, cb);
178 });
179 return fg;
180}
181
182} // namespace peclet::core::amr
183
184#endif // PECLET_CORE_HAVE_MORTON
185#endif // PECLET_CORE_AMR_FACEGEOM_ASSEMBLY_HPP
FaceGeom assembleFaceGeom(const AmrPoisson< 3, Bits > &ap, const std::vector< char > &fluidHost, const BlockOctreeView< 3, Bits > &ov)
Assemble the collocated FaceGeom entirely on device from a built AmrPoisson (openness set) + a per-ce...
View< Index > scanOffsets(Index n, const CountFn &countFn, Index &nTotal)
CSR row offsets (size n+1) from a per-cell count functor, via one exclusive prefix scan,...
Definition csr.hpp:55
View< T > toDevice(const std::vector< T > &h, const std::string &label)
Upload a host std::vector into a freshly-sized device View (empty vector => empty view).
Definition view.hpp:44
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
The device face-geometry walker: replicates AmrPoisson::forEachFaceFull + periodicNeighbor over the d...
typename BlockOctreeView< 3, Bits >::Coord Coord
Index periodicNeighbor(Index i, int axis, int dir) const
Single-leaf periodic face neighbour (AmrPoisson::periodicNeighbor) — the SOU upstream probe.
void forEachFaceFull(Index i, CB &cb) const
Visit each (sub)face of leaf i: cb(neighbour, axis, dir, area, dist, alpha) in forEachFaceFull order.
typename BlockOctreeView< 3, Bits >::M M
View< Index > nbr
neighbour leaf per face, size nFaces
Definition face_geom.hpp:22