core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
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// Header-only, guarded by PECLET_CORE_HAVE_MORTON.
22#ifndef PECLET_CORE_AMR_ADAPT_HPP
23#define PECLET_CORE_AMR_ADAPT_HPP
24
25#ifdef PECLET_CORE_HAVE_MORTON
26
27#include <array>
28#include <cmath>
29#include <utility>
30#include <vector>
31
35
37
38namespace detail {
39inline double minmod(double a, double b) {
40 if (a * b <= 0.0)
41 return 0.0;
42 return (std::fabs(a) < std::fabs(b)) ? a : b;
43}
44} // namespace detail
45
49template <int Dim, unsigned Bits>
50std::vector<double> transferField(const BlockOctree<Dim, Bits>& oldT,
51 const std::vector<double>& oldF,
52 const BlockOctree<Dim, Bits>& newT, bool linear = true) {
53 using BO = BlockOctree<Dim, Bits>;
54 using Code = typename BO::Code;
55 using Coord = typename BO::Coord;
56 using M = typename BO::M;
57
58 auto centroid = [](const BO& t, Index i) {
59 auto o = M::from_code(t.code(i)).decode();
60 const double s = static_cast<double>(Coord(1) << t.level(i));
61 std::array<double, Dim> c{};
62 for (int d = 0; d < Dim; ++d)
63 c[d] = static_cast<double>(o[d]) + 0.5 * s;
64 return c;
65 };
66
67 const Index no = oldT.numLeaves();
68 // Per-old-leaf minmod gradient (per fine-coordinate unit) for linear prolongation.
69 std::vector<std::array<double, Dim>> grad;
70 if (linear) {
71 grad.assign(static_cast<std::size_t>(no), std::array<double, Dim>{});
72 for (Index i = 0; i < no; ++i) {
73 auto ci = centroid(oldT, i);
74 const double ui = oldF[static_cast<std::size_t>(i)];
75 for (int axis = 0; axis < Dim; ++axis) {
76 const Index jp = oldT.faceNeighbor(i, axis, +1);
77 const Index jm = oldT.faceNeighbor(i, axis, -1);
78 if (jp < 0 || jm < 0)
79 continue;
80 auto cp = centroid(oldT, jp);
81 auto cm = centroid(oldT, jm);
82 const double sp = (oldF[static_cast<std::size_t>(jp)] - ui) / (cp[axis] - ci[axis]);
83 const double sm = (ui - oldF[static_cast<std::size_t>(jm)]) / (ci[axis] - cm[axis]);
84 grad[static_cast<std::size_t>(i)][axis] = detail::minmod(sp, sm);
85 }
86 }
87 }
88
89 const Index nn = newT.numLeaves();
90 std::vector<double> nf(static_cast<std::size_t>(nn), 0.0);
91 std::vector<Index> n2o(static_cast<std::size_t>(nn), -1); // prolong: new cell → source old leaf
92 for (Index j = 0; j < nn; ++j) {
93 const Code cj = newT.code(j);
94 const unsigned Lj = newT.level(j);
95 const Index o = oldT.find(cj);
96 if (o < 0)
97 continue;
98 const unsigned Lo = oldT.level(o);
99 if (Lo == Lj) {
100 nf[static_cast<std::size_t>(j)] = oldF[static_cast<std::size_t>(o)]; // copy
101 } else if (Lo > Lj) {
102 // new cell is finer → prolong from old leaf o
103 n2o[static_cast<std::size_t>(j)] = o;
104 if (!linear) {
105 nf[static_cast<std::size_t>(j)] = oldF[static_cast<std::size_t>(o)];
106 } else {
107 auto co = centroid(oldT, o);
108 auto cn = centroid(newT, j);
109 double v = oldF[static_cast<std::size_t>(o)];
110 for (int d = 0; d < Dim; ++d)
111 v += grad[static_cast<std::size_t>(o)][d] * (cn[d] - co[d]);
112 nf[static_cast<std::size_t>(j)] = v;
113 }
114 } else {
115 // new cell is coarser → volume-weighted average of the old leaves it covers
116 // (descendants of cj are a contiguous Morton run starting at o).
117 auto oj = M::from_code(cj).decode();
118 const Coord sj = Coord(Coord(1) << Lj);
119 double vol = 0.0, acc = 0.0;
120 for (Index k = o; k < no; ++k) {
121 auto ok = M::from_code(oldT.code(k)).decode();
122 bool inside = true;
123 for (int d = 0; d < Dim; ++d)
124 if (ok[d] < oj[d] || ok[d] >= oj[d] + sj) {
125 inside = false;
126 break;
127 }
128 if (!inside)
129 break;
130 const double w = std::pow(2.0, static_cast<double>(Dim * static_cast<int>(oldT.level(k))));
131 acc += w * oldF[static_cast<std::size_t>(k)];
132 vol += w;
133 }
134 nf[static_cast<std::size_t>(j)] = (vol > 0.0) ? acc / vol : 0.0;
135 }
136 }
137
138 // Conservation fix for prolongation: a cell's new children must volume-average back
139 // to the parent value. Exact for PC (already true) and for linear under uniform
140 // refinement; this correction also restores it when 2:1 balance refines a cell
141 // non-uniformly (so the symmetric-offset cancellation no longer holds). One scalar
142 // shift per source old leaf — preserves the reconstructed shape, fixes the mean.
143 if (linear) {
144 std::vector<double> sv(static_cast<std::size_t>(no), 0.0),
145 vv(static_cast<std::size_t>(no), 0.0);
146 for (Index j = 0; j < nn; ++j) {
147 const Index o = n2o[static_cast<std::size_t>(j)];
148 if (o < 0)
149 continue;
150 const double w = std::pow(2.0, static_cast<double>(Dim * static_cast<int>(newT.level(j))));
151 sv[static_cast<std::size_t>(o)] += w * nf[static_cast<std::size_t>(j)];
152 vv[static_cast<std::size_t>(o)] += w;
153 }
154 for (Index j = 0; j < nn; ++j) {
155 const Index o = n2o[static_cast<std::size_t>(j)];
156 if (o < 0 || vv[static_cast<std::size_t>(o)] <= 0.0)
157 continue;
158 nf[static_cast<std::size_t>(j)] +=
159 oldF[static_cast<std::size_t>(o)] -
160 sv[static_cast<std::size_t>(o)] / vv[static_cast<std::size_t>(o)];
161 }
162 }
163 return nf;
164}
165
169enum AdaptFlag : int { kCoarsen = -1, kKeep = 0, kRefine = 1 };
170
171template <int Dim, unsigned Bits>
172std::vector<int> flagByIndicator(const BlockOctree<Dim, Bits>& t, const std::vector<double>& ind,
173 double refineThresh, double coarsenThresh,
174 unsigned finestLevel = 0) {
175 const Index n = t.numLeaves();
176 std::vector<int> f(static_cast<std::size_t>(n), kKeep);
177 for (Index i = 0; i < n; ++i) {
178 const unsigned L = t.level(i);
179 const double e = ind[static_cast<std::size_t>(i)];
180 if (e > refineThresh && L > finestLevel)
181 f[static_cast<std::size_t>(i)] = kRefine;
182 else if (e < coarsenThresh && L < t.lmax())
183 f[static_cast<std::size_t>(i)] = kCoarsen;
184 }
185 return f;
186}
187
188template <int Dim, unsigned Bits>
191 std::vector<double> field;
192};
193
198template <int Dim, unsigned Bits>
199AdaptResult<Dim, Bits> adaptField(const BlockOctree<Dim, Bits>& t, const std::vector<double>& f,
200 const std::vector<int>& flags, bool linear = true) {
201 using BO = BlockOctree<Dim, Bits>;
202 using Code = typename BO::Code;
203 using M = typename BO::M;
204 BO nt = t;
205 // coarsen sibling groups whose every child is flagged kCoarsen
206 nt.coarsenIf([&](Code parent, unsigned pl) {
207 for (unsigned oct = 0; oct < (1u << Dim); ++oct) {
208 Code cc = M::from_code(parent).child(pl, oct).code();
209 Index ci = t.find(cc);
210 if (ci < 0 || flags[static_cast<std::size_t>(ci)] != kCoarsen)
211 return false;
212 }
213 return true;
214 });
215 // refine leaves flagged kRefine
216 nt.refineIf([&](Code c, unsigned) {
217 Index ci = t.find(c);
218 return ci >= 0 && flags[static_cast<std::size_t>(ci)] == kRefine;
219 });
220 nt.balance2to1();
221 std::vector<double> nf = transferField(t, f, nt, linear);
222 return AdaptResult<Dim, Bits>{std::move(nt), std::move(nf)};
223}
224
226template <int Dim, unsigned Bits>
227AdaptResult<Dim, Bits> adapt(const BlockOctree<Dim, Bits>& t, const std::vector<double>& f,
228 double refineThresh, double coarsenThresh, unsigned finestLevel = 0,
229 double eps = 0.01, bool linear = true) {
230 auto ind = lohnerIndicator(t, f, eps);
231 auto flags = flagByIndicator(t, ind, refineThresh, coarsenThresh, finestLevel);
232 return adaptField(t, f, flags, linear);
233}
234
235} // namespace peclet::core::amr
236
237#endif // PECLET_CORE_HAVE_MORTON
238#endif // PECLET_CORE_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...
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
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:39
std::vector< int > flagByIndicator(const BlockOctree< Dim, Bits > &t, const std::vector< double > &ind, double refineThresh, double coarsenThresh, unsigned finestLevel=0)
Definition adapt.hpp:172
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:199
AdaptFlag
Per-leaf adaptation flags from an indicator: refine where ind > refineThresh (and the leaf can go fin...
Definition adapt.hpp:169
std::vector< double > transferField(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF, const BlockOctree< Dim, Bits > &newT, bool linear=true)
Conservative remap of a leaf field from oldT to newT (same domain).
Definition adapt.hpp:50
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:227
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...
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
std::vector< double > field
Definition adapt.hpp:191
BlockOctree< Dim, Bits > octree
Definition adapt.hpp:190