core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
device_scene.hpp
Go to the documentation of this file.
1// core — the OWNING device scene + batched point evaluation (Layer 2-for-core).
2//
3// Two things the rest of the suite kept hand-rolling, now in one place:
4//
5// DeviceScene<Real> owns the node / instance / grid-descriptor / sample-pool Views of one
6// scene, built from a SceneBuilder or the flat encoding, and hands out the
7// non-owning SceneView (raw pointers) that kernels capture by value. This is
8// the type flow's setScene, dem's analytic walls and voro's SdfScene are
9// meant to sit on instead of each decoding + uploading privately.
10//
11// evalPoints BATCHED evaluation over arbitrary point arrays — the API decision the AMR
12// requirements single out (docs/AMR_GEOMETRY_SETUP_REQUIREMENTS.md item 1):
13// a device View of points in, a device View of distances out, one
14// parallel_for; plus the host-pointer form for oracles and today's
15// single-threaded builders. Points are ARBITRARY coordinates — leaf centers,
16// face centers, ±h_L probes, virtual uniform positions, coarse MG faces —
17// never a lattice assumption.
18//
19// The per-point functions come from scene.hpp / scene_query.hpp and are PECLET_HD, so host serial,
20// host parallel and device batched paths all evaluate the SAME expressions — bit-identical per
21// point across drivers on the same backend, which is what the parity gates assume.
22//
23// Requires Kokkos (this header owns Views); the per-point machinery it drives does not.
24#ifndef PECLET_CORE_GEOM_DEVICE_SCENE_HPP
25#define PECLET_CORE_GEOM_DEVICE_SCENE_HPP
26
27#include <Kokkos_Core.hpp>
28#include <stdexcept>
29#include <vector>
30
33
34namespace peclet::core::geom {
35
37template <class Real, class MemSpace = typename Kokkos::DefaultExecutionSpace::memory_space>
39 public:
40 DeviceScene() = default;
41
45 d.upload(b.nodes(), b.grids(), b.samples(), b.instances());
46 return d;
47 }
48
50 static DeviceScene fromFlat(const std::vector<int>& nodeInts, const std::vector<Real>& nodeReals,
51 const std::vector<int>& instInts,
52 const std::vector<Real>& instReals) {
54 /*grids=*/{}, /*pool=*/{});
55 return from(b);
56 }
57
61 s.nodes = nodes_.data();
62 s.nodeCount = static_cast<int>(nodes_.extent(0));
63 s.grids = grids_.data();
64 s.gridCount = static_cast<int>(grids_.extent(0));
65 s.samples = samples_.data();
66 s.sampleCount = static_cast<long>(samples_.extent(0));
67 s.instances = insts_.data();
68 s.instanceCount = static_cast<int>(insts_.extent(0));
69 return s;
70 }
71
72 private:
73 void upload(const std::vector<ShapeNode<Real>>& nodes, const std::vector<GridDesc<Real>>& grids,
74 const std::vector<float>& pool, const std::vector<Instance<Real>>& insts) {
75 if (nodes.empty())
76 throw std::invalid_argument("DeviceScene: empty node table");
77 nodes_ = Kokkos::View<ShapeNode<Real>*, MemSpace>("sceneNodes", nodes.size());
78 insts_ =
79 Kokkos::View<Instance<Real>*, MemSpace>("sceneInsts", insts.empty() ? 1 : insts.size());
80 grids_ =
81 Kokkos::View<GridDesc<Real>*, MemSpace>("sceneGrids", grids.empty() ? 1 : grids.size());
82 samples_ = Kokkos::View<float*, MemSpace>("scenePool", pool.empty() ? 1 : pool.size());
83 auto up = [](auto view, const auto& host) {
84 auto h = Kokkos::create_mirror_view(view);
85 for (std::size_t i = 0; i < host.size(); ++i)
86 h(i) = host[i];
87 Kokkos::deep_copy(view, h);
88 };
89 up(nodes_, nodes);
90 up(insts_, insts);
91 up(grids_, grids);
92 up(samples_, pool);
93 if (insts.empty())
94 instCount0_ = true;
95 }
96
97 Kokkos::View<ShapeNode<Real>*, MemSpace> nodes_;
98 Kokkos::View<Instance<Real>*, MemSpace> insts_;
99 Kokkos::View<GridDesc<Real>*, MemSpace> grids_;
100 Kokkos::View<float*, MemSpace> samples_;
101 bool instCount0_ = false;
102
103 public:
104 bool empty() const { return nodes_.extent(0) == 0; }
105 int instanceCount() const { return instCount0_ ? 0 : static_cast<int>(insts_.extent(0)); }
106};
107
108// ---------------------------------------------------------------------------------------------
109// Batched drivers
110// ---------------------------------------------------------------------------------------------
111
114template <class ExecSpace, class Real, class PtsView, class OutView>
116 const PeriodicBox<Real>& box, const CandidateGridView<Real>& grid,
117 const PtsView& pts, const OutView& out) {
118 const std::size_t n = pts.extent(0);
119 Kokkos::parallel_for(
120 "peclet::core::geom::evalSphereUnionPoints", Kokkos::RangePolicy<ExecSpace>(space, 0, n),
121 KOKKOS_LAMBDA(const std::size_t i) {
123 u, Vec3<Real>{Real(pts(i, 0)), Real(pts(i, 1)), Real(pts(i, 2))}, box, grid);
124 });
125}
126
129template <class Real>
131 const CandidateGridView<Real>& grid, const Real* pts, std::size_t n,
132 Real* out) {
133 for (std::size_t i = 0; i < n; ++i)
134 out[i] =
135 evalSphereUnionGrid(u, Vec3<Real>{pts[3 * i], pts[3 * i + 1], pts[3 * i + 2]}, box, grid);
136}
137
139template <class ExecSpace, class Real, class PtsView, class OutView>
141 const OutView& out) {
142 const std::size_t n = pts.extent(0);
143 Kokkos::parallel_for(
144 "peclet::core::geom::evalScenePoints", Kokkos::RangePolicy<ExecSpace>(space, 0, n),
145 KOKKOS_LAMBDA(const std::size_t i) {
146 out(i) = evalScene(sc, Vec3<Real>{Real(pts(i, 0)), Real(pts(i, 1)), Real(pts(i, 2))});
147 });
148}
149
150template <class Real>
151void evalScenePointsHost(const SceneView<Real>& sc, const Real* pts, std::size_t n, Real* out) {
152 for (std::size_t i = 0; i < n; ++i)
153 out[i] = evalScene(sc, Vec3<Real>{pts[3 * i], pts[3 * i + 1], pts[3 * i + 2]});
154}
155
156// ---------------------------------------------------------------------------------------------
157// SceneQueryDevice: the owning accelerated query every device consumer shares
158// ---------------------------------------------------------------------------------------------
159
164template <class Real, class MemSpace = typename Kokkos::DefaultExecutionSpace::memory_space>
166 public:
167 SceneQueryDevice() = default;
168
173 PeriodicBox<Real> box, bool accelerate = true, int nbHint = 0) {
175 d.box_ = box;
177 // host-side view for extraction + candidate build (pointers into the builder's host arrays)
178 const SceneView<Real> hostView = b.view();
179 std::vector<Real> cx, cy, cz, r;
180 if (extractSphereUnion(hostView, cx, cy, cz, r)) {
181 d.uploadUnion(cx, cy, cz, r);
182 if (accelerate) {
184 hu.cx = cx.data();
185 hu.cy = cy.data();
186 hu.cz = cz.data();
187 hu.r = r.data();
188 hu.n = static_cast<int>(cx.size());
189 hu.equalR = d.uMeta_.equalR;
190 hu.r0 = d.uMeta_.r0;
191 d.uploadGrid(buildSphereCandidateGrid(hu, origin, extent, box, nbHint));
192 }
193 } else if (accelerate && hostView.instanceCount > 8) {
194 // General-grid construction is nbins x nCertified tree walks; refuse silly costs rather
195 // than silently taking minutes — unaccelerated full scans stay exact.
196 const long nb = 32L * 32L * 32L; // upper estimate of the default bin count
197 if ((long)hostView.instanceCount * nb <= 50L * 1000L * 1000L)
198 d.uploadGrid(buildSceneCandidateGrid(hostView, origin, extent, box));
199 }
200 return d;
201 }
202
206 v.box = box_;
207 if (uMeta_.n > 0) {
208 v.u = uMeta_;
209 v.u.cx = cx_.data();
210 v.u.cy = cy_.data();
211 v.u.cz = cz_.data();
212 v.u.r = r_.data();
213 } else {
214 v.scene = scene_.view();
215 }
216 if (gMeta_.nx > 0) {
217 v.grid = gMeta_;
218 v.grid.offsets = gOff_.data();
219 v.grid.items = gItems_.data();
220 v.grid.always = gAlways_.data();
221 }
222 return v;
223 }
224
225 bool sphereFast() const { return uMeta_.n > 0; }
226 bool accelerated() const { return gMeta_.nx > 0; }
227 const DeviceScene<Real, MemSpace>& scene() const { return scene_; }
228
229 private:
230 void uploadUnion(const std::vector<Real>& cx, const std::vector<Real>& cy,
231 const std::vector<Real>& cz, const std::vector<Real>& r) {
232 auto up = [](const char* nm, const std::vector<Real>& h) {
233 Kokkos::View<Real*, MemSpace> v(nm, h.size());
234 auto m = Kokkos::create_mirror_view(v);
235 for (std::size_t i = 0; i < h.size(); ++i)
236 m(i) = h[i];
237 Kokkos::deep_copy(v, m);
238 return v;
239 };
240 cx_ = up("sq_cx", cx);
241 cy_ = up("sq_cy", cy);
242 cz_ = up("sq_cz", cz);
243 r_ = up("sq_r", r);
244 uMeta_.n = static_cast<int>(cx.size());
245 uMeta_.equalR = true;
246 for (std::size_t i = 1; i < r.size(); ++i)
247 if (r[i] != r[0])
248 uMeta_.equalR = false;
249 uMeta_.r0 = r.empty() ? Real(0) : r[0];
250 }
251 void uploadGrid(const CandidateGrid<Real>& g) {
252 gMeta_ = g.meta;
253 gMeta_.alwaysCount = static_cast<int>(g.always.size());
254 auto up = [](const char* nm, const std::vector<int>& h) {
255 Kokkos::View<int*, MemSpace> v(nm, h.empty() ? 1 : h.size());
256 auto m = Kokkos::create_mirror_view(v);
257 for (std::size_t i = 0; i < h.size(); ++i)
258 m(i) = h[i];
259 Kokkos::deep_copy(v, m);
260 return v;
261 };
262 gOff_ = up("sq_off", g.offsets);
263 gItems_ = up("sq_items", g.items);
264 gAlways_ = up("sq_always", g.always);
265 }
266
268 Kokkos::View<Real*, MemSpace> cx_, cy_, cz_, r_;
269 Kokkos::View<int*, MemSpace> gOff_, gItems_, gAlways_;
270 SphereUnionView<Real> uMeta_{};
272 PeriodicBox<Real> box_{};
273};
274
277template <class ExecSpace, class Real, class PtsView, class OutView>
279 const OutView& out) {
280 const std::size_t n = pts.extent(0);
281 Kokkos::parallel_for(
282 "peclet::core::geom::evalQueryPoints", Kokkos::RangePolicy<ExecSpace>(space, 0, n),
283 KOKKOS_LAMBDA(const std::size_t i) {
284 out(i) = q.eval(Vec3<Real>{Real(pts(i, 0)), Real(pts(i, 1)), Real(pts(i, 2))});
285 });
286}
287
288} // namespace peclet::core::geom
289
290#endif // PECLET_CORE_GEOM_DEVICE_SCENE_HPP
Owning device-resident scene. Copyable (Views are shared_ptr-like); view() is cheap.
static DeviceScene fromFlat(const std::vector< int > &nodeInts, const std::vector< Real > &nodeReals, const std::vector< int > &instInts, const std::vector< Real > &instReals)
Upload from the flat encoding (the binding path; see scene_builder.hpp for the layout).
SceneView< Real > view() const
Non-owning bundle over the device arrays; capture by value into kernels.
static DeviceScene from(const SceneBuilder< Real > &b)
Upload from a host SceneBuilder (the composition path).
const std::vector< ShapeNode< Real > > & nodes() const
const std::vector< Instance< Real > > & instances() const
SceneView< Real > view() const
Non-owning view over this builder's arrays. Invalidated by any further add*.
const std::vector< float > & samples() const
const std::vector< GridDesc< Real > > & grids() const
static SceneBuilder decode(const std::vector< int > &nodeInts, const std::vector< Real > &nodeReals, const std::vector< int > &instInts, const std::vector< Real > &instReals, const std::vector< GridDesc< Real > > &grids, const std::vector< float > &pool)
Rebuild from the flat encoding (grids and the sample pool are carried across verbatim).
Owns everything a SceneQueryView needs on ONE memory space: the DeviceScene, the extracted sphere-uni...
const DeviceScene< Real, MemSpace > & scene() const
static SceneQueryDevice build(const SceneBuilder< Real > &b, Vec3< Real > origin, Vec3< Real > extent, PeriodicBox< Real > box, bool accelerate=true, int nbHint=0)
Build from a host SceneBuilder.
SceneQueryView< Real > view() const
POD view for kernels; valid while this object lives.
PECLET_HD Real evalSphereUnionGrid(const SphereUnionView< Real > &u, Vec3< Real > p, const PeriodicBox< Real > &box, const CandidateGridView< Real > &g)
Union SDF through the candidate grid: bin lookup + short list, full-scan fallback when the bin is out...
CandidateGrid< Real > buildSceneCandidateGrid(const SceneView< Real > &sc, Vec3< Real > origin, Vec3< Real > extent, const PeriodicBox< Real > &box, Real binSizeHint=0)
Candidate grid for a GENERAL scene: certified instances splat Lipschitz bounds evaluated at bin cente...
void evalSphereUnionPoints(const ExecSpace &space, const SphereUnionView< Real > &u, const PeriodicBox< Real > &box, const CandidateGridView< Real > &grid, const PtsView &pts, const OutView &out)
Batched sphere-union evaluation, device: pts is (N,3), out is (N), any layouts/memory spaces reachabl...
void evalScenePointsHost(const SceneView< Real > &sc, const Real *pts, std::size_t n, Real *out)
bool extractSphereUnion(const SceneView< Real > &sc, std::vector< Real > &cx, std::vector< Real > &cy, std::vector< Real > &cz, std::vector< Real > &r)
Try to view a scene as a PLAIN sphere union: every instance a single kSphere leaf with a bitwise-iden...
PECLET_HD Real evalScene(const SceneView< Real > &sc, Vec3< Real > p)
Convenience: the union of EVERY instance.
Definition scene.hpp:499
void evalQueryPoints(const ExecSpace &space, const SceneQueryView< Real > &q, const PtsView &pts, const OutView &out)
Batched driver over a SceneQueryView (the form the restructured AMR builders and flow's sampling kern...
void evalScenePoints(const ExecSpace &space, const SceneView< Real > &sc, const PtsView &pts, const OutView &out)
Batched general-scene evaluation (union over instances), device.
CandidateGrid< Real > buildSphereCandidateGrid(const SphereUnionView< Real > &u, Vec3< Real > origin, Vec3< Real > extent, const PeriodicBox< Real > &box, int nbHint=0)
Geometry-driven candidate build for a sphere union over [origin, origin+extent).
void evalSphereUnionPointsHost(const SphereUnionView< Real > &u, const PeriodicBox< Real > &box, const CandidateGridView< Real > &grid, const Real *pts, std::size_t n, Real *out)
Host-pointer form (the oracle / today's single-threaded builders): plain loop, no Kokkos types in the...
ExecSpace::memory_space MemSpace
Definition view.hpp:21
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
Kokkos::DefaultExecutionSpace ExecSpace
Definition view.hpp:20
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
CandidateGridView< Real > grid
const Instance< Real > * instances
Definition scene.hpp:236
const GridDesc< Real > * grids
Definition scene.hpp:232
const ShapeNode< Real > * nodes
Definition scene.hpp:230