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
scalar_transport.hpp
Go to the documentation of this file.
1// core — explicit finite-volume scalar transport on a BlockOctree.
2//
3// Solves dc/dt + div(u c) = D lap(c) on the adaptive octree (periodic), the
4// canonical "AMR for scalar transport" target. Reusable shared infra: flow (or
5// any consumer) can advect a species/level-set on an AMR grid through this, and
6// later couple it to a structured hydro solver via leaf point-location.
7//
8// Discretisation (cell-centered FV, explicit Euler):
9// * advection — first-order upwind face flux. The outward normal velocity at a
10// face selects the upwind cell, so the same physical face uses the same upwind
11// value from both sides => conservative and monotone (no new extrema).
12// * diffusion — the conservative two-point flux of AmrPoisson.
13// * 2:1 interfaces — the coarse side sums over all fine sub-faces (each at its
14// own face centre / velocity), so mass is conserved across refinement jumps.
15// The face-normal velocity is sampled from a user callable vel(faceCentreWorld,
16// axis) -> Real; pass a divergence-free field for a conservative, monotone update.
17//
18#ifndef PECLET_AMR_SCALAR_TRANSPORT_HPP
19#define PECLET_AMR_SCALAR_TRANSPORT_HPP
20
21#include <array>
22#include <cmath>
23#include <vector>
24
26#include "peclet/amr/common.hpp"
28#include "peclet/core/common/types.hpp"
29
30namespace peclet::amr {
31
32template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
34 public:
36 using M = typename Octree::M;
37 using Code = typename Octree::Code;
38 using Coord = typename Octree::Coord;
39
40 ScalarTransport() = default;
42
43 void init(const Octree& t, const AmrGeometry<Dim>& geo) {
44 t_ = &t;
45 geo_ = geo;
46 for (int d = 0; d < Dim; ++d)
47 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
48 }
49
50 Index numLeaves() const { return t_->numLeaves(); }
52 Real cellWidth(Index i, int axis) const {
53 return geo_.h0[axis] * static_cast<Real>(Index(1) << t_->level(i));
54 }
55 Real cellWidth(Index i) const { return cellWidth(i, 0); }
57 const Real f = static_cast<Real>(Index(1) << t_->level(i));
58 Real v = 1;
59 for (int d = 0; d < Dim; ++d)
60 v *= geo_.h0[d] * f;
61 return v;
62 }
63
65 double totalMass(const std::vector<double>& c) const {
66 double s = 0.0;
67 for (Index i = 0; i < numLeaves(); ++i)
68 s += cellVolume(i) * c[static_cast<std::size_t>(i)];
69 return s;
70 }
71
74 template <class VelFn>
75 void step(const std::vector<double>& c, std::vector<double>& cOut, double dt, double D,
76 VelFn&& vel) const {
77 const Index n = numLeaves();
78 cOut.assign(static_cast<std::size_t>(n), 0.0);
79 for (Index i = 0; i < n; ++i) {
80 const double ci = c[static_cast<std::size_t>(i)];
81 double flux = 0.0; // sum of outward (advective + diffusive) fluxes
82 forEachFace(i, [&](Index j, int axis, int oSign, Real area, const Vec<Dim>& fc, Real dist) {
83 const double cj = c[static_cast<std::size_t>(j)];
84 const double unOut = oSign * static_cast<double>(vel(fc, axis));
85 const double cUp = (unOut > 0.0) ? ci : cj; // upwind
86 flux += area * unOut * cUp; // advective outflow
87 flux += -D * area * (cj - ci) / dist; // diffusive outflow
88 });
89 cOut[static_cast<std::size_t>(i)] = ci - dt * flux / cellVolume(i);
90 }
91 }
92
95 template <class Fn>
96 void forEachFace(Index i, Fn&& fn) const {
97 auto b = t_->bounds(i);
98 const auto& lo = b[0];
99 const unsigned Li = t_->level(i);
100 const Coord si = Coord(Coord(1) << Li);
101 for (int axis = 0; axis < Dim; ++axis)
102 for (int dir = -1; dir <= 1; dir += 2) {
103 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
104 : static_cast<long>(lo[axis]) - 1;
105 const long facePlane = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
106 : static_cast<long>(lo[axis]);
107 std::array<Coord, Dim> p = lo;
108 p[axis] = wrap(pc, axis);
109 Index j0 = t_->find(M::encode(p).code());
110 const unsigned Lj = t_->level(j0);
111 if (Lj >= Li) {
112 fn(j0, axis, dir, faceArea(si, axis),
113 faceCentre(lo, axis, facePlane, std::array<Coord, Dim>{}, si),
114 0.5 * (static_cast<Real>(si) + static_cast<Real>(Coord(Coord(1) << Lj))) *
115 geo_.h0[axis]);
116 } else {
117 const Coord sj = Coord(si >> 1);
118 const int nsub = 1 << (Dim - 1);
119 for (int k = 0; k < nsub; ++k) {
120 std::array<Coord, Dim> q = lo;
121 q[axis] = wrap(pc, axis);
122 std::array<Coord, Dim> off{};
123 int bit = 0;
124 for (int t = 0; t < Dim; ++t) {
125 if (t == axis)
126 continue;
127 off[t] = ((k >> bit) & 1) ? sj : Coord(0);
128 q[t] = wrap(static_cast<long>(lo[t]) + static_cast<long>(off[t]), t);
129 ++bit;
130 }
131 Index jj = t_->find(M::encode(q).code());
132 fn(jj, axis, dir, faceArea(sj, axis), faceCentre(lo, axis, facePlane, off, sj),
133 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * geo_.h0[axis]);
134 }
135 }
136 }
137 }
138
139 private:
140 Coord wrap(long c, int axis) const {
141 long e = static_cast<long>(fineExt_[axis]);
142 return static_cast<Coord>(((c % e) + e) % e);
143 }
145 Real faceArea(Coord s, int axis) const {
146 Real a = 1;
147 for (int d = 0; d < Dim; ++d)
148 if (d != axis)
149 a *= static_cast<Real>(s) * geo_.h0[d];
150 return a;
151 }
152 // World centre of a (sub)face: `facePlane` is the shared-plane coord (fine) on
153 // `axis`; the sub-face spans width `tw` from lo+off in each tangential axis.
154 Vec<Dim> faceCentre(const std::array<Coord, Dim>& lo, int axis, long facePlane,
155 const std::array<Coord, Dim>& off, Coord tw) const {
156 Vec<Dim> fc{};
157 for (int d = 0; d < Dim; ++d) {
158 if (d == axis)
159 fc[d] = geo_.origin[d] + static_cast<Real>(facePlane) * geo_.h0[d];
160 else
161 fc[d] = geo_.origin[d] +
162 (static_cast<Real>(lo[d] + off[d]) + 0.5 * static_cast<Real>(tw)) * geo_.h0[d];
163 }
164 return fc;
165 }
166
167 const Octree* t_ = nullptr;
168 AmrGeometry<Dim> geo_{};
169 std::array<Coord, Dim> fineExt_{};
170};
171
172} // namespace peclet::amr
173
174#endif // PECLET_AMR_SCALAR_TRANSPORT_HPP
morton::Morton< Dim, Bits > M
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
std::array< std::array< Coord, Dim >, 2 > bounds(Index i) const
Inclusive integer bounds [lo, hi] of leaf i in fine units.
void step(const std::vector< double > &c, std::vector< double > &cOut, double dt, double D, VelFn &&vel) const
One explicit Euler step: cOut = c + dt*(-div(u c) + D lap(c)).
BlockOctree< Dim, Bits > Octree
Real cellWidth(Index i, int axis) const
Phase 3: per-axis cell width; the scalar form is the cubic one (docs/amr_anisotropic....
void init(const Octree &t, const AmrGeometry< Dim > &geo)
typename Octree::Coord Coord
ScalarTransport(const Octree &t, const AmrGeometry< Dim > &geo)
void forEachFace(Index i, Fn &&fn) const
Visit each (sub)face of leaf i.
double totalMass(const std::vector< double > &c) const
Total scalar content sum_i V_i c_i (conserved by a divergence-free update).
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
World-space placement of a block-local octree: fine coordinate (0,..,0) sits at origin,...