peclet.voro 1.0.4
Device-native moving-particle Voronoi dynamics
Loading...
Searching...
No Matches
polymesh.hpp
Go to the documentation of this file.
1
18#ifndef PECLET_VORO_FV_POLYMESH_HPP
19#define PECLET_VORO_FV_POLYMESH_HPP
20
21#include <cmath>
22#include <cstdio>
23#include <functional>
24#include <Kokkos_Core.hpp>
25#include <string>
26#include <unordered_map>
27#include <vector>
28
29#include "peclet/core/common/view.hpp"
31#include "peclet/voro/sdf.hpp"
33
34namespace peclet::voro::fv {
35
36template <class Real>
37struct PolyMesh {
38 int nCells = 0, nFaces = 0, nInterior = 0;
39 std::vector<Real> points; // 3 * nPoints (world)
40 std::vector<int> faceOffset; // nFaces + 1
41 std::vector<int> faceVerts; // point ids, CCW about the owner's outward normal
42 std::vector<int> owner; // nFaces
43 std::vector<int> neighbour; // nFaces, -1 on boundary
44 std::vector<int> patch; // nFaces: 0 interior, 1 wall, 2 box
45 std::vector<int> cellFaceOffset; // nCells + 1
46 std::vector<int> cellFace; // faces of each cell (interior faces in both)
47 std::vector<Real> cellVolume; // nCells, from the assembled polygons
48 int nPoints() const { return (int)points.size() / 3; }
49};
50
51namespace detail {
52struct VertexKey {
53 long long a, b, c;
54 bool operator==(const VertexKey& o) const { return a == o.a && b == o.b && c == o.c; }
55};
57 std::size_t operator()(const VertexKey& k) const {
58 std::size_t h = (std::size_t)k.a * 73856093u;
59 h ^= (std::size_t)k.b * 19349663u;
60 h ^= (std::size_t)k.c * 83492791u;
61 return h;
62 }
63};
64} // namespace detail
65
76template <class Real>
77int mergeWallVertices(PolyMesh<Real>& m, const Real L[3], Real tol) {
78 const int nP = m.nPoints();
79 std::vector<char> onWall(nP, 0);
80 for (int f = 0; f < m.nFaces; ++f)
81 if (m.patch[f] == 1)
82 for (int q = m.faceOffset[f]; q < m.faceOffset[f + 1]; ++q)
83 onWall[m.faceVerts[q]] = 1;
84 std::vector<int> ids;
85 for (int p = 0; p < nP; ++p)
86 if (onWall[p])
87 ids.push_back(p);
88 // hash grid of cell size tol over the periodic box
89 std::unordered_map<detail::VertexKey, std::vector<int>, detail::VertexKeyHash> grid;
90 long long Mq[3];
91 for (int k = 0; k < 3; ++k)
92 Mq[k] = std::max<long long>(1, (long long)std::floor(L[k] / tol));
93 auto cellOf = [&](Real x, int k) {
94 long long c = (long long)std::floor(x / L[k] * (Real)Mq[k]);
95 c %= Mq[k];
96 return c < 0 ? c + Mq[k] : c;
97 };
98 for (int p : ids)
99 grid[detail::VertexKey{cellOf(m.points[3 * p], 0), cellOf(m.points[3 * p + 1], 1),
100 cellOf(m.points[3 * p + 2], 2)}]
101 .push_back(p);
102 std::vector<int> parent(nP);
103 for (int p = 0; p < nP; ++p)
104 parent[p] = p;
105 std::function<int(int)> find = [&](int a) {
106 while (parent[a] != a)
107 a = parent[a] = parent[parent[a]];
108 return a;
109 };
110 const Real tol2 = tol * tol;
111 for (int p : ids) {
112 const long long c0[3] = {cellOf(m.points[3 * p], 0), cellOf(m.points[3 * p + 1], 1),
113 cellOf(m.points[3 * p + 2], 2)};
114 for (int dz = -1; dz <= 1; ++dz)
115 for (int dy = -1; dy <= 1; ++dy)
116 for (int dx = -1; dx <= 1; ++dx) {
117 auto w = [&](long long c, long long M) { return ((c % M) + M) % M; };
118 auto it = grid.find(
119 detail::VertexKey{w(c0[0] + dx, Mq[0]), w(c0[1] + dy, Mq[1]), w(c0[2] + dz, Mq[2])});
120 if (it == grid.end())
121 continue;
122 for (int q : it->second) {
123 if (q <= p)
124 continue;
125 Real d2 = 0;
126 for (int k = 0; k < 3; ++k) {
127 Real d = m.points[3 * q + k] - m.points[3 * p + k];
128 d -= L[k] * std::round(d / L[k]);
129 d2 += d * d;
130 }
131 if (d2 <= tol2) {
132 const int ra = find(p), rb = find(q);
133 if (ra != rb)
134 parent[rb] = ra;
135 }
136 }
137 }
138 }
139 // merged position = the mean of the group (min-image about the root)
140 std::vector<Real> acc(3 * (size_t)nP, Real(0));
141 std::vector<int> cnt(nP, 0);
142 int merged = 0;
143 for (int p : ids) {
144 const int r = find(p);
145 for (int k = 0; k < 3; ++k) {
146 Real d = m.points[3 * p + k] - m.points[3 * r + k];
147 d -= L[k] * std::round(d / L[k]);
148 acc[3 * r + k] += d;
149 }
150 ++cnt[r];
151 if (r != p)
152 ++merged;
153 }
154 for (int p : ids)
155 if (find(p) == p && cnt[p] > 1)
156 for (int k = 0; k < 3; ++k)
157 m.points[3 * p + k] += acc[3 * p + k] / (Real)cnt[p];
158 // rewrite the faces
159 std::vector<int> newVerts, newOff(1, 0), newOwner, newNbr, newPatch;
160 std::vector<int> faceMap(m.nFaces, -1);
161 for (int f = 0; f < m.nFaces; ++f) {
162 std::vector<int> poly;
163 for (int q = m.faceOffset[f]; q < m.faceOffset[f + 1]; ++q) {
164 const int v = find(m.faceVerts[q]);
165 if (poly.empty() || poly.back() != v)
166 poly.push_back(v);
167 }
168 while (poly.size() > 1 && poly.front() == poly.back())
169 poly.pop_back();
170 if (poly.size() < 3)
171 continue; // a degenerate face (collapsed by the merge)
172 faceMap[f] = (int)newOff.size() - 1;
173 newVerts.insert(newVerts.end(), poly.begin(), poly.end());
174 newOff.push_back((int)newVerts.size());
175 newOwner.push_back(m.owner[f]);
176 newNbr.push_back(m.neighbour[f]);
177 newPatch.push_back(m.patch[f]);
178 }
179 std::vector<int> cf, cfo(1, 0);
180 for (int i = 0; i < m.nCells; ++i) {
181 for (int q = m.cellFaceOffset[i]; q < m.cellFaceOffset[i + 1]; ++q)
182 if (faceMap[m.cellFace[q]] >= 0)
183 cf.push_back(faceMap[m.cellFace[q]]);
184 cfo.push_back((int)cf.size());
185 }
186 int nInt = 0;
187 for (int f = 0; f < (int)newPatch.size(); ++f)
188 nInt += newPatch[f] == 0;
189 m.faceVerts = newVerts;
190 m.faceOffset = newOff;
191 m.owner = newOwner;
192 m.neighbour = newNbr;
193 m.patch = newPatch;
194 m.cellFace = cf;
195 m.cellFaceOffset = cfo;
196 m.nFaces = (int)newPatch.size();
197 m.nInterior = nInt;
198 return merged;
199}
200
202template <class Real, int MAXP, int MAXT>
204 const Kokkos::View<Real*, peclet::core::MemSpace>& pos, int N,
205 const Real L[3], WallStore<Real> wall = {},
206 Kokkos::View<Real*, peclet::core::MemSpace> xRef = {},
207 Real quantum = Real(1e-9)) {
208 using Cell = ConvexCell<Real, MAXP, MAXT, false>;
209 auto np = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, store.np);
210 auto nt = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, store.nt);
211 auto pnbr = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, store.pnbr);
212 auto tri = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, store.tri);
213 auto ph = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, pos);
214 // host decode of the store (the same steps as TopologyStore::load, on the mirrors)
215 auto loadHost = [&](int i, Cell& c) {
216 c.initBoxPlanes(L[0], L[1], L[2]);
217 const int npi = np(i), nti = nt(i);
218 c.np = npi;
219 c.nt = nti;
220 c.overflow = false;
221 for (int k = 6; k < npi; ++k)
222 c.pnbr[k] = pnbr((size_t)i * MAXP + k);
223 for (int t = 0; t < nti; ++t) {
224 const unsigned w = tri((size_t)i * MAXT + t);
225 c.t0[t] = (unsigned char)(w & 0xffu);
226 c.t1[t] = (unsigned char)((w >> 8) & 0xffu);
227 c.t2[t] = (unsigned char)((w >> 16) & 0xffu);
228 c.alive[t] = ((w >> 24) & 1u) != 0u;
229 }
230 };
231 const bool hasWall = wall.cnt.extent(0) > 0;
232 typename Kokkos::View<Real*, peclet::core::MemSpace>::host_mirror_type wrecM, xrM;
233 typename Kokkos::View<int*, peclet::core::MemSpace>::host_mirror_type wcntM;
234 if (hasWall) {
235 wrecM = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, wall.rec);
236 wcntM = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, wall.cnt);
237 xrM = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace{}, xRef);
238 }
239 PolyMesh<Real> m;
240 m.nCells = N;
241 m.cellVolume.assign(N, Real(0));
242 std::unordered_map<detail::VertexKey, int, detail::VertexKeyHash> vmap;
243 vmap.reserve((size_t)N * 8);
244 // keys are PERIODIC: the same vertex seen from a cell across the box boundary is offset by L
245 const long long M[3] = {(long long)std::llround(Real(1) / quantum),
246 (long long)std::llround(Real(1) / quantum),
247 (long long)std::llround(Real(1) / quantum)};
248 auto wrapq = [](long long v, long long m) {
249 v %= m;
250 return v < 0 ? v + m : v;
251 };
252 auto vertexId = [&](Real x, Real y, Real z) {
253 detail::VertexKey k{wrapq((long long)std::llround(x / (quantum * L[0])), M[0]),
254 wrapq((long long)std::llround(y / (quantum * L[1])), M[1]),
255 wrapq((long long)std::llround(z / (quantum * L[2])), M[2])};
256 auto it = vmap.find(k);
257 if (it != vmap.end())
258 return it->second;
259 const int id = m.nPoints();
260 m.points.push_back(x);
261 m.points.push_back(y);
262 m.points.push_back(z);
263 vmap.emplace(k, id);
264 return id;
265 };
266 // interior faces are keyed by (min cell, max cell) to be emitted once
267 std::vector<std::vector<int>> cellFaces(N);
268 std::vector<int> faceOwner, faceNbr, facePatch, faceOff{0}, faceV;
269 const Real Lh[3] = {L[0] / 2, L[1] / 2, L[2] / 2};
270 for (int i = 0; i < N; ++i) {
271 Cell c;
272 loadHost(i, c);
273 const Real sx = ph(3 * i), sy = ph(3 * i + 1), sz = ph(3 * i + 2);
274 if (hasWall) {
275 Real dx = sx - xrM(3 * i), dy = sy - xrM(3 * i + 1), dz = sz - xrM(3 * i + 2);
276 dx = dx > Lh[0] ? dx - L[0] : (dx < -Lh[0] ? dx + L[0] : dx);
277 dy = dy > Lh[1] ? dy - L[1] : (dy < -Lh[1] ? dy + L[1] : dy);
278 dz = dz > Lh[2] ? dz - L[2] : (dz < -Lh[2] ? dz + L[2] : dz);
279 // restore the wall planes from the host mirrors (same arithmetic as WallStore::load)
280 int r = 0;
281 const int nrec = wcntM(i);
282 for (int k = 6; k < c.np; ++k) {
283 if (c.pnbr[k] >= 0 || r >= nrec)
284 continue;
285 const size_t o = ((size_t)i * WallStore<Real>::kMax + r) * 4;
286 const Real ux = wrecM(o), uy = wrecM(o + 1), uz = wrecM(o + 2);
287 const Real h = wrecM(o + 3) - (ux * dx + uy * dy + uz * dz);
288 c.n[k][0] = h * ux;
289 c.n[k][1] = h * uy;
290 c.n[k][2] = h * uz;
291 c.nn[k] = h * h;
292 ++r;
293 }
294 }
295 c.reevalGeometry(sx, sy, sz, ph.data(), L[0]);
296 Real V = 0;
297 for (int k = 0; k < c.np; ++k) {
298 Real fx[Cell::MAXFV], fy[Cell::MAXFV], fz[Cell::MAXFV];
299 const int nv = c.faceOrdered(k, fx, fy, fz);
300 if (nv < 3)
301 continue;
302 const int j = c.pnbr[k];
303 // pyramid volume from the seed (exact for the convex cell)
304 for (int q = 1; q + 1 < nv; ++q) {
305 const Real ax = fx[0], ay = fy[0], az = fz[0], bx = fx[q], by = fy[q], bz = fz[q],
306 cx = fx[q + 1], cy = fy[q + 1], cz = fz[q + 1];
307 V += std::fabs(ax * (by * cz - bz * cy) - ay * (bx * cz - bz * cx) +
308 az * (bx * cy - by * cx)) /
309 Real(6);
310 }
311 if (j >= 0 && j < i)
312 continue; // emitted from the lower cell
313 const int fid = (int)faceOwner.size();
314 faceOwner.push_back(i);
315 faceNbr.push_back(j >= 0 ? j : -1);
316 facePatch.push_back(j >= 0 ? 0 : (j == kBoundaryFacet ? 1 : 2));
317 for (int q = 0; q < nv; ++q)
318 faceV.push_back(vertexId(sx + fx[q], sy + fy[q], sz + fz[q]));
319 faceOff.push_back((int)faceV.size());
320 cellFaces[i].push_back(fid);
321 if (j >= 0)
322 cellFaces[j].push_back(fid);
323 }
324 m.cellVolume[i] = V;
325 }
326 // order: interior faces first
327 const int nF = (int)faceOwner.size();
328 std::vector<int> perm(nF), inv(nF);
329 int q = 0;
330 for (int f = 0; f < nF; ++f)
331 if (facePatch[f] == 0)
332 perm[q++] = f;
333 m.nInterior = q;
334 for (int f = 0; f < nF; ++f)
335 if (facePatch[f] != 0)
336 perm[q++] = f;
337 for (int f = 0; f < nF; ++f)
338 inv[perm[f]] = f;
339 m.nFaces = nF;
340 m.faceOffset.assign(1, 0);
341 for (int f = 0; f < nF; ++f) {
342 const int o = perm[f];
343 m.owner.push_back(faceOwner[o]);
344 m.neighbour.push_back(faceNbr[o]);
345 m.patch.push_back(facePatch[o]);
346 for (int v = faceOff[o]; v < faceOff[o + 1]; ++v)
347 m.faceVerts.push_back(faceV[v]);
348 m.faceOffset.push_back((int)m.faceVerts.size());
349 }
350 m.cellFaceOffset.assign(1, 0);
351 for (int i = 0; i < N; ++i) {
352 for (int f : cellFaces[i])
353 m.cellFace.push_back(inv[f]);
354 m.cellFaceOffset.push_back((int)m.cellFace.size());
355 }
356 return m;
357}
358
361template <class Real>
362bool writeVtu(const std::string& path, const PolyMesh<Real>& m,
363 const std::vector<Real>* cellScalar = nullptr, const char* scalarName = "scalar") {
364 std::FILE* fp = std::fopen(path.c_str(), "w");
365 if (!fp)
366 return false;
367 const int nP = m.nPoints(), nC = m.nCells;
368 std::fprintf(fp,
369 "<?xml version=\"1.0\"?>\n<VTKFile type=\"UnstructuredGrid\" version=\"1.0\" "
370 "byte_order=\"LittleEndian\">\n<UnstructuredGrid>\n<Piece NumberOfPoints=\"%d\" "
371 "NumberOfCells=\"%d\">\n",
372 nP, nC);
373 std::fprintf(
374 fp, "<Points>\n<DataArray type=\"Float64\" NumberOfComponents=\"3\" format=\"ascii\">\n");
375 for (int p = 0; p < nP; ++p)
376 std::fprintf(fp, "%.17g %.17g %.17g\n", (double)m.points[3 * p], (double)m.points[3 * p + 1],
377 (double)m.points[3 * p + 2]);
378 std::fprintf(fp, "</DataArray>\n</Points>\n<Cells>\n");
379 // connectivity: the union of a cell's face vertices (unique), offsets, types = 42
380 std::vector<int> conn, connOff, faces, faceOff;
381 std::vector<char> seen(nP, 0);
382 for (int i = 0; i < nC; ++i) {
383 std::vector<int> touched;
384 for (int q = m.cellFaceOffset[i]; q < m.cellFaceOffset[i + 1]; ++q) {
385 const int f = m.cellFace[q];
386 for (int v = m.faceOffset[f]; v < m.faceOffset[f + 1]; ++v) {
387 const int p = m.faceVerts[v];
388 if (!seen[p]) {
389 seen[p] = 1;
390 touched.push_back(p);
391 conn.push_back(p);
392 }
393 }
394 }
395 for (int p : touched)
396 seen[p] = 0;
397 connOff.push_back((int)conn.size());
398 const int nf = m.cellFaceOffset[i + 1] - m.cellFaceOffset[i];
399 faces.push_back(nf);
400 for (int q = m.cellFaceOffset[i]; q < m.cellFaceOffset[i + 1]; ++q) {
401 const int f = m.cellFace[q];
402 const int nv = m.faceOffset[f + 1] - m.faceOffset[f];
403 faces.push_back(nv);
404 // orientation: outward for the owner; reversed for the neighbour
405 if (m.owner[f] == i)
406 for (int v = m.faceOffset[f]; v < m.faceOffset[f + 1]; ++v)
407 faces.push_back(m.faceVerts[v]);
408 else
409 for (int v = m.faceOffset[f + 1] - 1; v >= m.faceOffset[f]; --v)
410 faces.push_back(m.faceVerts[v]);
411 }
412 faceOff.push_back((int)faces.size());
413 }
414 std::fprintf(fp, "<DataArray type=\"Int64\" Name=\"connectivity\" format=\"ascii\">\n");
415 for (int v : conn)
416 std::fprintf(fp, "%d\n", v);
417 std::fprintf(fp, "</DataArray>\n<DataArray type=\"Int64\" Name=\"offsets\" format=\"ascii\">\n");
418 for (int o : connOff)
419 std::fprintf(fp, "%d\n", o);
420 std::fprintf(fp, "</DataArray>\n<DataArray type=\"UInt8\" Name=\"types\" format=\"ascii\">\n");
421 for (int i = 0; i < nC; ++i)
422 std::fprintf(fp, "42\n");
423 std::fprintf(fp, "</DataArray>\n<DataArray type=\"Int64\" Name=\"faces\" format=\"ascii\">\n");
424 for (int v : faces)
425 std::fprintf(fp, "%d\n", v);
426 std::fprintf(fp,
427 "</DataArray>\n<DataArray type=\"Int64\" Name=\"faceoffsets\" format=\"ascii\">\n");
428 for (int o : faceOff)
429 std::fprintf(fp, "%d\n", o);
430 std::fprintf(fp, "</DataArray>\n</Cells>\n<CellData>\n");
431 std::fprintf(fp, "<DataArray type=\"Float64\" Name=\"volume\" format=\"ascii\">\n");
432 for (int i = 0; i < nC; ++i)
433 std::fprintf(fp, "%.17g\n", (double)m.cellVolume[i]);
434 std::fprintf(fp, "</DataArray>\n");
435 if (cellScalar) {
436 std::fprintf(fp, "<DataArray type=\"Float64\" Name=\"%s\" format=\"ascii\">\n", scalarName);
437 for (int i = 0; i < nC; ++i)
438 std::fprintf(fp, "%.17g\n", (double)(*cellScalar)[i]);
439 std::fprintf(fp, "</DataArray>\n");
440 }
441 std::fprintf(fp, "</CellData>\n</Piece>\n</UnstructuredGrid>\n</VTKFile>\n");
442 std::fclose(fp);
443 return true;
444}
445
446} // namespace peclet::voro::fv
447
448#endif // PECLET_VORO_FV_POLYMESH_HPP
Definition collocated.hpp:38
PolyMesh< Real > buildPolyMesh(const TopologyStore< MAXP, MAXT > &store, const Kokkos::View< Real *, peclet::core::MemSpace > &pos, int N, const Real L[3], WallStore< Real > wall={}, Kokkos::View< Real *, peclet::core::MemSpace > xRef={}, Real quantum=Real(1e-9))
Assemble the polyhedral mesh of a store at positions posHost (3N, world, in [0,L)).
Definition polymesh.hpp:203
bool writeVtu(const std::string &path, const PolyMesh< Real > &m, const std::vector< Real > *cellScalar=nullptr, const char *scalarName="scalar")
Definition polymesh.hpp:362
int mergeWallVertices(PolyMesh< Real > &m, const Real L[3], Real tol)
Definition polymesh.hpp:77
constexpr int kBoundaryFacet
Definition sdf.hpp:36
Definition topology_store.hpp:39
Kokkos::View< int *, MemSpace > np
Definition topology_store.hpp:42
Kokkos::View< unsigned *, MemSpace > tri
Definition topology_store.hpp:45
Kokkos::View< int *, MemSpace > nt
Definition topology_store.hpp:43
Kokkos::View< int *, MemSpace > pnbr
Definition topology_store.hpp:44
Definition sdf.hpp:352
static constexpr int kMax
Definition sdf.hpp:354
Definition polymesh.hpp:37
std::vector< int > faceOffset
Definition polymesh.hpp:40
std::vector< Real > points
Definition polymesh.hpp:39
std::vector< int > patch
Definition polymesh.hpp:44
std::vector< int > neighbour
Definition polymesh.hpp:43
std::vector< int > cellFaceOffset
Definition polymesh.hpp:45
std::vector< int > faceVerts
Definition polymesh.hpp:41
int nInterior
Definition polymesh.hpp:38
int nCells
Definition polymesh.hpp:38
int nPoints() const
Definition polymesh.hpp:48
std::vector< Real > cellVolume
Definition polymesh.hpp:47
std::vector< int > cellFace
Definition polymesh.hpp:46
std::vector< int > owner
Definition polymesh.hpp:42
int nFaces
Definition polymesh.hpp:38
Definition polymesh.hpp:56
std::size_t operator()(const VertexKey &k) const
Definition polymesh.hpp:57
Definition polymesh.hpp:52
bool operator==(const VertexKey &o) const
Definition polymesh.hpp:54
long long b
Definition polymesh.hpp:53
long long a
Definition polymesh.hpp:53
long long c
Definition polymesh.hpp:53