peclet-amr 0.2.0
Block-local-Morton AMR octree + collocated cut-cell Navier–Stokes solver (header-only C++20, Kokkos + MPI)
Loading...
Searching...
No Matches
seam_recon.hpp
Go to the documentation of this file.
1// amr — the prebuilt tables the convective flux needs at a 2:1 coarse/fine seam (ROADMAP B5).
2//
3// docs/amr_cf_convective.md: the SOU/Koren reconstruction of the ADVECTED value is O(h) wrong at
4// every face whose upwind-side stencil crosses a level — the coarse upwind cell's column is
5// TANGENTIALLY offset from the sub-face centroid (case 1), and the second upwind probe of the two
6// same-level faces beside the seam sits at the wrong DISTANCE and at a tangential offset as well
7// (cases 2 and 3). The fix is a level-aware, upwind-side reconstruction; everything it needs that
8// depends only on the mesh is prebuilt here, once, in setSolid.
9//
10// THREE KINDS OF ENTRY, one CSR of records (§5.2):
11//
12// * a SAMPLE record, one per 2:1 sub-face pair (C, F): `φ_C` sampled at F's tangential column,
13// i.e. the entry (C, 1) followed VERBATIM by `cf_scheme.hpp::cfAppendStencil`'s
14// (coarse* − coarse) delta — so the record's weights sum to 1 and the gathered value is the
15// Martin–Cartwright tangential quadratic the C/F operator overlays already use. It is read
16// twice: as the sub-face's own `φ_U*` (case 1) and as F's upstream probe on the face behind it
17// (case 2), where its `recDist` = 1.5·width(F) is the probe's TRUE distance.
18// * a LAYER record, one per (coarse cell C, face) whose far side is refined: the mean of the
19// four fine cells in that face layer, `recDist` = 0.75·width(C) — the upstream probe of C's
20// opposite face (case 3), again at its true distance.
21// * a DESCRIPTOR, one per face slot that references at least one record: which record plays
22// `φ_U*` / `φ_D*` and which plays the upstream probe, for each of the two flow directions,
23// plus the half widths `d1I/d1J`. A slot with no live field gets `seam = −1` and the kernels
24// take TODAY'S line, bit for bit.
25//
26// GATES (§5.4). No sample record unless both incident cells are REGULAR (fluid and not cut — the
27// per-face C/F flux gate of docs/amr_cf_flux_gate.md, ghost-visible under MPI); no layer record
28// unless the coarse cell is regular and all four fine cells are fluid; never a partial record. A
29// withheld record leaves the slot on today's arithmetic, so cut seams are bit-identical.
30//
31// DETERMINISM. The walk is SEQUENTIAL over the local rows in `forEachFaceFull` order and record
32// ids are assigned on first reference, so the tables are a pure function of the mesh — which is
33// what makes the np = 1 build bitwise identical to the single-rank one (records are gathered in
34// record order by the kernel, never reordered).
35#ifndef PECLET_AMR_SEAM_RECON_HPP
36#define PECLET_AMR_SEAM_RECON_HPP
37
38#include <array>
39#include <map>
40#include <utility>
41#include <vector>
42
44#include "peclet/amr/common.hpp"
46#include "peclet/core/common/types.hpp"
47
48namespace peclet::amr {
49
59 std::vector<Index> seam;
60 std::vector<Index> sampI;
61 std::vector<Index> sampJ;
62 std::vector<Index> uuRecI;
63 std::vector<Index> uuRecJ;
64 std::vector<double> d1I;
65 std::vector<double> d1J;
66 std::vector<Index> recStart;
67 std::vector<double> recDist;
68 std::vector<Index> recCell;
69 std::vector<double> recW;
73 Index numDescriptors() const { return static_cast<Index>(sampI.size()); }
74};
75
76namespace detail {
77
81template <unsigned Bits>
82inline bool seamFaceLayer(const AmrPoisson<3, Bits>& ap, Index C, int axis, int dir,
83 std::array<Index, 4>& cells) {
84 const unsigned Lc = ap.levelOf(C);
85 if (Lc == 0)
86 return false; // C is at the finest level: nothing below it
87 const std::array<long, 3> lo = ap.loOf(C);
88 const long sc = 1L << Lc, sh = sc >> 1;
89 std::array<long, 3> p = lo;
90 p[axis] = (dir > 0) ? lo[axis] + sc : lo[axis] - 1;
91 for (int k = 0; k < 4; ++k) {
92 std::array<long, 3> q = p;
93 int bit = 0;
94 for (int t = 0; t < 3; ++t) {
95 if (t == axis)
96 continue;
97 q[t] = lo[t] + (((k >> bit) & 1) ? sh : 0L);
98 ++bit;
99 }
100 const auto [c, Lq] = ap.probeSlot(q);
101 if (c < 0 || Lq + 1 != Lc)
102 return false;
103 cells[static_cast<std::size_t>(k)] = c;
104 }
105 return true;
106}
107
108} // namespace detail
109
128template <unsigned Bits, class RegularFn, class FluidFn>
131 const Index n = ap.octree().numLeaves();
133 std::vector<std::vector<detail::ScalarEnt>> rec; // entries, one vector per record
134 std::vector<double> dist; // recDist, one per record
135
136 // (coarse, fine, axis) -> sample record id; (coarse, axis, dir) -> layer record id. A withheld
137 // record is cached as −1 so a gate is evaluated once per key, not once per referencing slot.
138 std::map<std::array<Index, 3>, Index> sampleOf, layerOf;
139
140 auto sampleRecord = [&](Index C, Index F, int axis) -> Index {
141 if (C < 0 || F < 0)
142 return -1;
143 const std::array<Index, 3> key{C, F, static_cast<Index>(axis)};
144 const auto it = sampleOf.find(key);
145 if (it != sampleOf.end())
146 return it->second;
147 Index id = -1;
148 if (regularOk(C) && regularOk(F)) {
149 std::vector<detail::ScalarEnt> e;
150 e.push_back(detail::ScalarEnt{C, 1.0}); // the raw coarse value; the stencil is its DELTA
151 detail::cfAppendStencil(ap, e, C, F, axis, 1.0, fluidOk, scheme);
152 id = static_cast<Index>(rec.size());
153 rec.push_back(std::move(e));
154 dist.push_back(1.5 * static_cast<double>(ap.cellWidth(F, axis)));
155 ++out.numSampleRecords;
156 }
157 sampleOf.emplace(key, id);
158 return id;
159 };
160
161 auto layerRecord = [&](Index C, int axis, int dir) -> Index {
162 if (C < 0)
163 return -1;
164 const std::array<Index, 3> key{C, static_cast<Index>(axis), static_cast<Index>(dir)};
165 const auto it = layerOf.find(key);
166 if (it != layerOf.end())
167 return it->second;
168 Index id = -1;
169 std::array<Index, 4> cells{};
170 if (regularOk(C) && detail::seamFaceLayer(ap, C, axis, dir, cells)) {
171 bool ok = true;
172 for (const Index c : cells)
173 ok = ok && fluidOk(c);
174 if (ok) {
175 std::vector<detail::ScalarEnt> e;
176 for (const Index c : cells)
177 e.push_back(detail::ScalarEnt{c, 0.25});
178 id = static_cast<Index>(rec.size());
179 rec.push_back(std::move(e));
180 dist.push_back(0.75 * static_cast<double>(ap.cellWidth(C, axis)));
181 ++out.numLayerRecords;
182 }
183 }
184 layerOf.emplace(key, id);
185 return id;
186 };
187
188 for (Index i = 0; i < n; ++i) {
189 ap.forEachFaceFull(i, [&](Index j, int a, int d, double, double, double) {
190 const unsigned Li = ap.levelOf(i), Lj = ap.levelOf(j);
191 Index rsI = -1, rsJ = -1, ruI = -1, ruJ = -1;
192 // Case 1 — this slot IS a 2:1 sub-face: the coarse side's value is sampled at the fine
193 // side's tangential column. (A level in this octree counts DOWN: 0 is the finest.)
194 if (Lj != Li) {
195 if (Li > Lj)
196 rsI = sampleRecord(i, j, a);
197 else
198 rsJ = sampleRecord(j, i, a);
199 }
200 // Cases 2/3 — the upstream probe of each side crosses a level: coarser ⇒ the same
201 // tangential sample at its true distance; finer ⇒ the mean of that face's fine layer.
202 const Index uI = ap.periodicNeighbor(i, a, -d);
203 if (uI >= 0) {
204 const unsigned Lu = ap.levelOf(uI);
205 if (Lu > Li)
206 ruI = sampleRecord(uI, i, a);
207 else if (Lu < Li)
208 ruI = layerRecord(i, a, -d);
209 }
210 const Index uJ = ap.periodicNeighbor(j, a, d);
211 if (uJ >= 0) {
212 const unsigned Lu = ap.levelOf(uJ);
213 if (Lu > Lj)
214 ruJ = sampleRecord(uJ, j, a);
215 else if (Lu < Lj)
216 ruJ = layerRecord(j, a, d);
217 }
218 if (rsI < 0 && rsJ < 0 && ruI < 0 && ruJ < 0) {
219 out.seam.push_back(-1); // plain slot: today's reconstruction, bit for bit
220 return;
221 }
222 out.seam.push_back(out.numDescriptors());
223 out.sampI.push_back(rsI);
224 out.sampJ.push_back(rsJ);
225 out.uuRecI.push_back(ruI);
226 out.uuRecJ.push_back(ruJ);
227 out.d1I.push_back(0.5 * static_cast<double>(ap.cellWidth(i, a)));
228 out.d1J.push_back(0.5 * static_cast<double>(ap.cellWidth(j, a)));
229 });
230 }
231
232 const Index nRec = static_cast<Index>(rec.size());
233 out.recStart.assign(static_cast<std::size_t>(nRec) + 1, 0);
234 for (Index r = 0; r < nRec; ++r)
235 out.recStart[static_cast<std::size_t>(r) + 1] =
236 out.recStart[static_cast<std::size_t>(r)] +
237 static_cast<Index>(rec[static_cast<std::size_t>(r)].size());
238 out.recDist = std::move(dist);
239 out.recCell.reserve(static_cast<std::size_t>(out.recStart[static_cast<std::size_t>(nRec)]));
240 out.recW.reserve(static_cast<std::size_t>(out.recStart[static_cast<std::size_t>(nRec)]));
241 for (Index r = 0; r < nRec; ++r)
242 for (const auto& e : rec[static_cast<std::size_t>(r)]) {
243 out.recCell.push_back(e.cell);
244 out.recW.push_back(e.w);
245 }
246 return out;
247}
248
249} // namespace peclet::amr
250
251#endif // PECLET_AMR_SEAM_RECON_HPP
Cell-centered FV Poisson operator on one (periodic) block octree.
Definition poisson.hpp:43
unsigned levelOf(Index slot) const
Octree level of an extended slot (local leaf or declared ghost).
Definition poisson.hpp:98
Real cellWidth(Index i, int axis) const
Cell width along axis (Phase 3: per axis).
Definition poisson.hpp:255
std::pair< Index, unsigned > probeSlot(const std::array< long, Dim > &p) const
Covering slot + level of a block-local probe (longs, possibly outside the block).
Definition poisson.hpp:120
const Octree & octree() const
Definition poisson.hpp:586
std::array< long, Dim > loOf(Index slot) const
Block-local lo corner of an extended slot as longs (ghosts may lie outside the block).
Definition poisson.hpp:103
void cfAppendStencil(const AmrPoisson< 3, Bits > &ap, std::vector< Ent > &out, Index coarse, Index fine, int axis, double scale, FluidFn &&fluidOk, CfScheme scheme, const Ent &proto=Ent{})
Append the scheme's (coarse* − coarse_raw) substitution stencil for ONE directed C/F sub-face to out,...
bool seamFaceLayer(const AmrPoisson< 3, Bits > &ap, Index C, int axis, int dir, std::array< Index, 4 > &cells)
The four sub-face neighbour leaves across C's face (axis, dir), in forEachFaceFull's own sub-face ord...
SeamReconHost buildSeamRecon(const AmrPoisson< 3, Bits > &ap, RegularFn &&regularOk, FluidFn &&fluidOk, CfScheme scheme)
Build the seam-reconstruction tables over the LOCAL rows of ap (docs/amr_cf_convective....
std::vector< std::array< double, Dim > > transferFieldGradients(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF)
Per-old-leaf minmod prolongation gradients (per fine-coordinate unit) — transferField's stencil,...
Definition adapt.hpp:51
CfScheme
Coarse/fine interface scheme for the collocated flow operators.
Definition cf_scheme.hpp:73
Host-side seam-reconstruction tables (docs/amr_cf_convective.md §5.2), built once per AmrFlow::setSol...
Index numDescriptors() const
Number of descriptors, i.e. of face slots that take the seam path (seam[slot] >= 0).
std::vector< Index > recStart
record CSR offsets, size nRec+1
std::vector< double > recW
record entries: weight
Index numSampleRecords
diagnostics: how many of the records are sample records
Index numLayerRecords
diagnostics: how many are layer records
std::vector< double > d1J
per descriptor: half width of j along the face axis (physical)
std::vector< Index > seam
per face slot: descriptor id, −1 = plain slot
std::vector< double > recDist
per record: dUU for UPSTREAM use (unused as a φ_U* sample)
std::vector< Index > sampI
per descriptor: sample record when i is the COARSE cell, else −1
std::vector< double > d1I
per descriptor: half width of i along the face axis (physical)
std::vector< Index > uuRecJ
per descriptor: upstream-probe record for up == j, else −1
std::vector< Index > sampJ
per descriptor: sample record when j is the COARSE cell, else −1
std::vector< Index > recCell
record entries: cell slot
std::vector< Index > uuRecI
per descriptor: upstream-probe record for up == i, else −1