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
adapt.hpp
Go to the documentation of this file.
1// core — dynamic (solution-adaptive) AMR for a BlockOctree: flag from an
2// error indicator, refine/coarsen + 2:1 balance, and remap the field onto the new
3// mesh.
4//
5// The core reusable piece is `transferField` — a conservative field remap between
6// any two octrees on the same domain:
7// * a new leaf at the same level as the covering old leaf → copy;
8// * a new leaf finer than the old (refinement / balance) → prolong (piecewise-
9// constant, or minmod-limited linear — both conservative because the children of
10// a cell are symmetric about its centroid, so the linear term integrates to 0);
11// * a new leaf coarser than the old (coarsening) → volume-weighted average
12// of the old leaves it covers.
13// This one operation powers refinement transfer, coarsening transfer, and the extra
14// cells inserted by 2:1 balancing, uniformly.
15//
16// `adaptField` applies a per-leaf flag set (refine / keep / coarsen) to produce the
17// new octree and the remapped field; `adapt` is the all-in-one driver using the
18// Löhner indicator (indicators.hpp). The level convention is the octree's: refining
19// *decreases* level toward 0 (finest), coarsening increases it toward `lmax` (root).
20//
21#ifndef PECLET_AMR_ADAPT_HPP
22#define PECLET_AMR_ADAPT_HPP
23
24#include <array>
25#include <cmath>
26#include <type_traits>
27#include <utility>
28#include <vector>
29
31#include "peclet/amr/common.hpp"
33#include "peclet/core/common/types.hpp"
34
35namespace peclet::amr {
36
37namespace detail {
38inline double minmod(double a, double b) {
39 if (a * b <= 0.0)
40 return 0.0;
41 return (std::fabs(a) < std::fabs(b)) ? a : b;
42}
43} // namespace detail
44
50template <int Dim, unsigned Bits>
51std::vector<std::array<double, Dim>> transferFieldGradients(const BlockOctree<Dim, Bits>& oldT,
52 const std::vector<double>& oldF) {
53 using BO = BlockOctree<Dim, Bits>;
54 using Coord = typename BO::Coord;
55 using M = typename BO::M;
56 auto centroid = [](const BO& t, Index i) {
57 auto o = M::from_code(t.code(i)).decode();
58 const double s = static_cast<double>(Coord(1) << t.level(i));
59 std::array<double, Dim> c{};
60 for (int d = 0; d < Dim; ++d)
61 c[d] = static_cast<double>(o[d]) + 0.5 * s;
62 return c;
63 };
64 const Index no = oldT.numLeaves();
65 std::vector<std::array<double, Dim>> grad(static_cast<std::size_t>(no),
66 std::array<double, Dim>{});
67 for (Index i = 0; i < no; ++i) {
68 auto ci = centroid(oldT, i);
69 const double ui = oldF[static_cast<std::size_t>(i)];
70 for (int axis = 0; axis < Dim; ++axis) {
71 const Index jp = oldT.faceNeighbor(i, axis, +1);
72 const Index jm = oldT.faceNeighbor(i, axis, -1);
73 if (jp < 0 || jm < 0)
74 continue;
75 auto cp = centroid(oldT, jp);
76 auto cm = centroid(oldT, jm);
77 const double sp = (oldF[static_cast<std::size_t>(jp)] - ui) / (cp[axis] - ci[axis]);
78 const double sm = (ui - oldF[static_cast<std::size_t>(jm)]) / (ci[axis] - cm[axis]);
79 grad[static_cast<std::size_t>(i)][axis] = detail::minmod(sp, sm);
80 }
81 }
82 return grad;
83}
84
90template <int Dim, unsigned Bits>
91std::vector<double> transferField(
92 const BlockOctree<Dim, Bits>& oldT, const std::vector<double>& oldF,
93 const BlockOctree<Dim, Bits>& newT, bool linear = true,
94 // type_identity: keep gradIn out of deduction (array<double, N> carries size_t, Dim is int)
95 const std::type_identity_t<std::vector<std::array<double, Dim>>>* gradIn = nullptr) {
96 using BO = BlockOctree<Dim, Bits>;
97 using Code = typename BO::Code;
98 using Coord = typename BO::Coord;
99 using M = typename BO::M;
100
101 auto centroid = [](const BO& t, Index i) {
102 auto o = M::from_code(t.code(i)).decode();
103 const double s = static_cast<double>(Coord(1) << t.level(i));
104 std::array<double, Dim> c{};
105 for (int d = 0; d < Dim; ++d)
106 c[d] = static_cast<double>(o[d]) + 0.5 * s;
107 return c;
108 };
109
110 const Index no = oldT.numLeaves();
111 // Per-old-leaf minmod gradient (per fine-coordinate unit) for linear prolongation.
112 std::vector<std::array<double, Dim>> grad;
113 if (linear && gradIn) {
114 grad = *gradIn;
115 } else if (linear) {
117 }
118
119 const Index nn = newT.numLeaves();
120 std::vector<double> nf(static_cast<std::size_t>(nn), 0.0);
121 std::vector<Index> n2o(static_cast<std::size_t>(nn), -1); // prolong: new cell → source old leaf
122 for (Index j = 0; j < nn; ++j) {
123 const Code cj = newT.code(j);
124 const unsigned Lj = newT.level(j);
125 const Index o = oldT.find(cj);
126 if (o < 0)
127 continue;
128 const unsigned Lo = oldT.level(o);
129 if (Lo == Lj) {
130 nf[static_cast<std::size_t>(j)] = oldF[static_cast<std::size_t>(o)]; // copy
131 } else if (Lo > Lj) {
132 // new cell is finer → prolong from old leaf o
133 n2o[static_cast<std::size_t>(j)] = o;
134 if (!linear) {
135 nf[static_cast<std::size_t>(j)] = oldF[static_cast<std::size_t>(o)];
136 } else {
137 auto co = centroid(oldT, o);
138 auto cn = centroid(newT, j);
139 double v = oldF[static_cast<std::size_t>(o)];
140 for (int d = 0; d < Dim; ++d)
141 v += grad[static_cast<std::size_t>(o)][d] * (cn[d] - co[d]);
142 nf[static_cast<std::size_t>(j)] = v;
143 }
144 } else {
145 // new cell is coarser → volume-weighted average of the old leaves it covers
146 // (descendants of cj are a contiguous Morton run starting at o).
147 auto oj = M::from_code(cj).decode();
148 const Coord sj = Coord(Coord(1) << Lj);
149 double vol = 0.0, acc = 0.0;
150 for (Index k = o; k < no; ++k) {
151 auto ok = M::from_code(oldT.code(k)).decode();
152 bool inside = true;
153 for (int d = 0; d < Dim; ++d)
154 if (ok[d] < oj[d] || ok[d] >= oj[d] + sj) {
155 inside = false;
156 break;
157 }
158 if (!inside)
159 break;
160 const double w = std::pow(2.0, static_cast<double>(Dim * static_cast<int>(oldT.level(k))));
161 acc += w * oldF[static_cast<std::size_t>(k)];
162 vol += w;
163 }
164 nf[static_cast<std::size_t>(j)] = (vol > 0.0) ? acc / vol : 0.0;
165 }
166 }
167
168 // Conservation fix for prolongation: a cell's new children must volume-average back
169 // to the parent value. Exact for PC (already true) and for linear under uniform
170 // refinement; this correction also restores it when 2:1 balance refines a cell
171 // non-uniformly (so the symmetric-offset cancellation no longer holds). One scalar
172 // shift per source old leaf — preserves the reconstructed shape, fixes the mean.
173 if (linear) {
174 std::vector<double> sv(static_cast<std::size_t>(no), 0.0),
175 vv(static_cast<std::size_t>(no), 0.0);
176 for (Index j = 0; j < nn; ++j) {
177 const Index o = n2o[static_cast<std::size_t>(j)];
178 if (o < 0)
179 continue;
180 const double w = std::pow(2.0, static_cast<double>(Dim * static_cast<int>(newT.level(j))));
181 sv[static_cast<std::size_t>(o)] += w * nf[static_cast<std::size_t>(j)];
182 vv[static_cast<std::size_t>(o)] += w;
183 }
184 for (Index j = 0; j < nn; ++j) {
185 const Index o = n2o[static_cast<std::size_t>(j)];
186 if (o < 0 || vv[static_cast<std::size_t>(o)] <= 0.0)
187 continue;
188 nf[static_cast<std::size_t>(j)] +=
189 oldF[static_cast<std::size_t>(o)] -
190 sv[static_cast<std::size_t>(o)] / vv[static_cast<std::size_t>(o)];
191 }
192 }
193 return nf;
194}
195
199enum AdaptFlag : int { kCoarsen = -1, kKeep = 0, kRefine = 1 };
200
201template <int Dim, unsigned Bits>
202std::vector<int> flagByIndicator(const BlockOctree<Dim, Bits>& t, const std::vector<double>& ind,
203 double refineThresh, double coarsenThresh,
204 unsigned finestLevel = 0) {
205 const Index n = t.numLeaves();
206 std::vector<int> f(static_cast<std::size_t>(n), kKeep);
207 for (Index i = 0; i < n; ++i) {
208 const unsigned L = t.level(i);
209 const double e = ind[static_cast<std::size_t>(i)];
210 if (e > refineThresh && L > finestLevel)
211 f[static_cast<std::size_t>(i)] = kRefine;
212 else if (e < coarsenThresh && L < t.lmax())
213 f[static_cast<std::size_t>(i)] = kCoarsen;
214 }
215 return f;
216}
217
218template <int Dim, unsigned Bits>
221 std::vector<double> field;
222};
223
228template <int Dim, unsigned Bits>
229AdaptResult<Dim, Bits> adaptField(const BlockOctree<Dim, Bits>& t, const std::vector<double>& f,
230 const std::vector<int>& flags, bool linear = true) {
231 using BO = BlockOctree<Dim, Bits>;
232 using Code = typename BO::Code;
233 using M = typename BO::M;
234 BO nt = t;
235 // coarsen sibling groups whose every child is flagged kCoarsen
236 nt.coarsenIf([&](Code parent, unsigned pl) {
237 for (unsigned oct = 0; oct < (1u << Dim); ++oct) {
238 Code cc = M::from_code(parent).child(pl, oct).code();
239 Index ci = t.find(cc);
240 if (ci < 0 || flags[static_cast<std::size_t>(ci)] != kCoarsen)
241 return false;
242 }
243 return true;
244 });
245 // refine leaves flagged kRefine
246 nt.refineIf([&](Code c, unsigned) {
247 Index ci = t.find(c);
248 return ci >= 0 && flags[static_cast<std::size_t>(ci)] == kRefine;
249 });
250 nt.balance2to1();
251 std::vector<double> nf = transferField(t, f, nt, linear);
252 return AdaptResult<Dim, Bits>{std::move(nt), std::move(nf)};
253}
254
256template <int Dim, unsigned Bits>
257AdaptResult<Dim, Bits> adapt(const BlockOctree<Dim, Bits>& t, const std::vector<double>& f,
258 double refineThresh, double coarsenThresh, unsigned finestLevel = 0,
259 double eps = 0.01, bool linear = true) {
260 auto ind = lohnerIndicator(t, f, eps);
262 return adaptField(t, f, flags, linear);
263}
264
265} // namespace peclet::amr
266
267#endif // PECLET_AMR_ADAPT_HPP
Per-block adaptive octree over block-local Morton codes.
Index coarsenIf(Pred &&pred)
Merge complete sibling groups (all 2^Dim children present, all at the same level) whose parent satisf...
Index faceNeighbor(Index i, int axis, int dir) const
The leaf across leaf i's face on axis in direction dir (±1), or -1 if it lies outside the block.
double minmod(double a, double b)
Definition adapt.hpp:38
std::vector< double > lohnerIndicator(const BlockOctree< Dim, Bits > &t, const std::vector< double > &u, double eps=0.01)
Löhner normalized second-difference indicator E_i ∈ [0,1], per leaf, for scalar u (indexed by leaf sl...
AdaptResult< Dim, Bits > adaptField(const BlockOctree< Dim, Bits > &t, const std::vector< double > &f, const std::vector< int > &flags, bool linear=true)
Apply adaptation flags (one level of refine/coarsen) to t carrying field f, then 2:1-balance and rema...
Definition adapt.hpp:229
std::vector< int > flagByIndicator(const BlockOctree< Dim, Bits > &t, const std::vector< double > &ind, double refineThresh, double coarsenThresh, unsigned finestLevel=0)
Definition adapt.hpp:202
AdaptResult< Dim, Bits > adapt(const BlockOctree< Dim, Bits > &t, const std::vector< double > &f, double refineThresh, double coarsenThresh, unsigned finestLevel=0, double eps=0.01, bool linear=true)
All-in-one solution-adaptive step: Löhner indicator → flags → adaptField.
Definition adapt.hpp:257
std::vector< double > transferField(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF, const BlockOctree< Dim, Bits > &newT, bool linear=true, const std::type_identity_t< std::vector< std::array< double, Dim > > > *gradIn=nullptr)
Conservative remap of a leaf field from oldT to newT (same domain).
Definition adapt.hpp:91
AdaptFlag
Per-leaf adaptation flags from an indicator: refine where ind > refineThresh (and the leaf can go fin...
Definition adapt.hpp:199
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
BlockOctree< Dim, Bits > octree
Definition adapt.hpp:220
std::vector< double > field
Definition adapt.hpp:221