core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
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// Header-only, guarded by PECLET_CORE_HAVE_MORTON. Serial/host first.
19#ifndef PECLET_CORE_AMR_SCALAR_TRANSPORT_HPP
20#define PECLET_CORE_AMR_SCALAR_TRANSPORT_HPP
21
22#ifdef PECLET_CORE_HAVE_MORTON
23
24#include <array>
25#include <cmath>
26#include <vector>
27
31
32namespace peclet::core::amr {
33
34template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
36 public:
38 using M = typename Octree::M;
39 using Code = typename Octree::Code;
40 using Coord = typename Octree::Coord;
41
42 ScalarTransport() = default;
44
45 void init(const Octree& t, const AmrGeometry<Dim>& geo) {
46 t_ = &t;
47 geo_ = geo;
48 for (int d = 0; d < Dim; ++d)
49 fineExt_[d] = static_cast<Coord>(t.brick()[d] * (Index(1) << t.lmax()));
50 }
51
52 Index numLeaves() const { return t_->numLeaves(); }
53 Real cellWidth(Index i) const { return geo_.h0 * static_cast<Real>(Index(1) << t_->level(i)); }
55 Real w = cellWidth(i), v = 1;
56 for (int d = 0; d < Dim; ++d)
57 v *= w;
58 return v;
59 }
60
62 double totalMass(const std::vector<double>& c) const {
63 double s = 0.0;
64 for (Index i = 0; i < numLeaves(); ++i)
65 s += cellVolume(i) * c[static_cast<std::size_t>(i)];
66 return s;
67 }
68
71 template <class VelFn>
72 void step(const std::vector<double>& c, std::vector<double>& cOut, double dt, double D,
73 VelFn&& vel) const {
74 const Index n = numLeaves();
75 cOut.assign(static_cast<std::size_t>(n), 0.0);
76 for (Index i = 0; i < n; ++i) {
77 const double ci = c[static_cast<std::size_t>(i)];
78 double flux = 0.0; // sum of outward (advective + diffusive) fluxes
79 forEachFace(i, [&](Index j, int axis, int oSign, Real area, const Vec<Dim>& fc, Real dist) {
80 const double cj = c[static_cast<std::size_t>(j)];
81 const double unOut = oSign * static_cast<double>(vel(fc, axis));
82 const double cUp = (unOut > 0.0) ? ci : cj; // upwind
83 flux += area * unOut * cUp; // advective outflow
84 flux += -D * area * (cj - ci) / dist; // diffusive outflow
85 });
86 cOut[static_cast<std::size_t>(i)] = ci - dt * flux / cellVolume(i);
87 }
88 }
89
92 template <class Fn>
93 void forEachFace(Index i, Fn&& fn) const {
94 auto b = t_->bounds(i);
95 const auto& lo = b[0];
96 const unsigned Li = t_->level(i);
97 const Coord si = Coord(Coord(1) << Li);
98 for (int axis = 0; axis < Dim; ++axis)
99 for (int dir = -1; dir <= 1; dir += 2) {
100 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
101 : static_cast<long>(lo[axis]) - 1;
102 const long facePlane = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(si)
103 : static_cast<long>(lo[axis]);
104 std::array<Coord, Dim> p = lo;
105 p[axis] = wrap(pc, axis);
106 Index j0 = t_->find(M::encode(p).code());
107 const unsigned Lj = t_->level(j0);
108 if (Lj >= Li) {
109 fn(j0, axis, dir, faceArea(si),
110 faceCentre(lo, axis, facePlane, std::array<Coord, Dim>{}, si),
111 0.5 * (static_cast<Real>(si) + static_cast<Real>(Coord(Coord(1) << Lj))) * geo_.h0);
112 } else {
113 const Coord sj = Coord(si >> 1);
114 const int nsub = 1 << (Dim - 1);
115 for (int k = 0; k < nsub; ++k) {
116 std::array<Coord, Dim> q = lo;
117 q[axis] = wrap(pc, axis);
118 std::array<Coord, Dim> off{};
119 int bit = 0;
120 for (int t = 0; t < Dim; ++t) {
121 if (t == axis)
122 continue;
123 off[t] = ((k >> bit) & 1) ? sj : Coord(0);
124 q[t] = wrap(static_cast<long>(lo[t]) + static_cast<long>(off[t]), t);
125 ++bit;
126 }
127 Index jj = t_->find(M::encode(q).code());
128 fn(jj, axis, dir, faceArea(sj), faceCentre(lo, axis, facePlane, off, sj),
129 0.5 * (static_cast<Real>(si) + static_cast<Real>(sj)) * geo_.h0);
130 }
131 }
132 }
133 }
134
135 private:
136 Coord wrap(long c, int axis) const {
137 long e = static_cast<long>(fineExt_[axis]);
138 return static_cast<Coord>(((c % e) + e) % e);
139 }
140 Real faceArea(Coord s) const {
141 Real a = 1;
142 for (int d = 0; d < Dim - 1; ++d)
143 a *= static_cast<Real>(s) * geo_.h0;
144 return a;
145 }
146 // World centre of a (sub)face: `facePlane` is the shared-plane coord (fine) on
147 // `axis`; the sub-face spans width `tw` from lo+off in each tangential axis.
148 Vec<Dim> faceCentre(const std::array<Coord, Dim>& lo, int axis, long facePlane,
149 const std::array<Coord, Dim>& off, Coord tw) const {
150 Vec<Dim> fc{};
151 for (int d = 0; d < Dim; ++d) {
152 if (d == axis)
153 fc[d] = geo_.origin[d] + static_cast<Real>(facePlane) * geo_.h0;
154 else
155 fc[d] = geo_.origin[d] +
156 (static_cast<Real>(lo[d] + off[d]) + 0.5 * static_cast<Real>(tw)) * geo_.h0;
157 }
158 return fc;
159 }
160
161 const Octree* t_ = nullptr;
162 AmrGeometry<Dim> geo_{};
163 std::array<Coord, Dim> fineExt_{};
164};
165
166} // namespace peclet::core::amr
167
168#endif // PECLET_CORE_HAVE_MORTON
169#endif // PECLET_CORE_AMR_SCALAR_TRANSPORT_HPP
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.
ScalarTransport(const Octree &t, const AmrGeometry< Dim > &geo)
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)).
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).
void init(const Octree &t, const AmrGeometry< Dim > &geo)
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