peclet.voro 1.0.4
Device-native moving-particle Voronoi dynamics
Loading...
Searching...
No Matches
voro_bindings_tess.hpp
Go to the documentation of this file.
1
10#ifndef PECLET_VORO_BINDINGS_TESS_HPP
11#define PECLET_VORO_BINDINGS_TESS_HPP
12
24
25namespace peclet::voro::pybind {
26
27// --------------------------------------------------------------------------------------------------
28// Tessellation: the bare moving-point (power-)Voronoi tessellator, optionally SDF-clipped.
29// The engine is chosen at build() from what was set: {Voronoi, Power} x {no geometry, SdfScene}.
30// --------------------------------------------------------------------------------------------------
31template <bool W, class S>
32using MT =
34using MtVariant =
35 std::variant<MT<false, NoSdfT>, MT<true, NoSdfT>, MT<false, SceneT>, MT<true, SceneT>>;
36
37inline nb::dict repairStatsDict(const peclet::voro::RepairStats& st) {
38 nb::dict d;
39 d["flagged"] = st.pass1Raw; // cells the certificate flagged
40 d["pass1"] = st.pass1; // cells gathered in Pass 1
41 d["pass2"] = st.pass2; // cells gathered in Pass 2
42 d["rebuilt"] =
43 (st.route == peclet::voro::RepairStats::kRebuildGate); // gate routed to a full rebuild
44 d["fell_back"] = st.fellBack; // verify failed -> cold rebuild
45 d["extra"] = st.extra; // cells gathered across the verify extra-passes
46 d["surgical"] = st.surgical; // Pass-1 cells repaired surgically (no grid gather)
47 d["verify_passes"] = st.verifyPasses; // number of verify iterations run
48 d["wall_flagged"] = st.wallFlagged; // cells flagged by the SDF boundary watch
49 return d;
50}
51
52class Tess : public peclet::core::python::Releasable {
53 public:
54 Tess() = default;
55
56 void set_domain(std::array<real_t, 3> extent, std::array<real_t, 3> origin,
57 std::array<bool, 3> periodic) {
58 checkDomain("Tessellation", extent, origin, periodic);
59 L_ = extent;
60 }
61 std::array<real_t, 3> extent() const { return L_; }
62 void set_tolerance(real_t frac) {
63 if (!(frac > real_t(0)))
64 throw std::invalid_argument("voro: set_tolerance(frac) needs frac > 0.");
65 tolFrac_ = frac;
66 }
67 void set_geometry(nb::ndarray<int, nb::c_contig> node_ints,
68 nb::ndarray<real_t, nb::c_contig> node_reals, int root, real_t grad_h) {
69 scene_ = makeSceneHolder(node_ints, node_reals, root, grad_h);
70 }
71 void clear_geometry() { scene_.clear(); }
72 void set_wall_mode(const std::string& mode, real_t skin_frac) {
73 wallExact_ = parseWallMode(mode);
74 if (skin_frac < real_t(0))
75 throw std::invalid_argument("voro: set_wall_mode(skin_frac=...) needs skin_frac >= 0.");
76 wallSkinFrac_ = skin_frac;
77 }
78 void set_weights(nb::ndarray<real_t, nb::c_contig> w) {
79 if (w.ndim() != 1)
80 throw std::runtime_error("set_weights(): expected an (N,) array");
81 wHost_ = flatten1(w);
82 weighted_ = true;
83 wDirty_ = true;
84 }
86 wHost_.clear();
87 weighted_ = false;
88 wDirty_ = false;
89 }
90
91 // diagnostics: ablation switches of the repair (both certificates are complete; the gate is the
92 // "never slower than a cold build" guard) and the validity counts of the last cold build.
93 void set_local_certificate(bool on) { localCert_ = on; }
94 void set_gate(bool on) { useGate_ = on; }
95 void set_profile(bool on) {
96 profile_ = on;
97 std::visit([&](auto& mt) { mt.profile = on; }, mt_);
98 }
99 nb::dict build_report() {
100 auto r = std::visit([](auto& mt) { return mt.report(); }, mt_);
101 nb::dict d;
102 d["buried"] = r.buried;
103 d["reach_exceeded"] = r.reachExceeded;
104 d["empty"] = r.empty;
105 d["overflow"] = r.overflow;
106 d["incomplete"] = r.incomplete;
107 d["over_buffer_rebuilds"] = r.overBufferRebuilds;
108 return d;
109 }
110
111 // Cold build: (re)allocate the resident tessellation for N points and build it from scratch.
112 void build(nb::ndarray<real_t, nb::c_contig> a, bool strict) {
113 std::vector<real_t> p = flatten3(a);
114 N_ = static_cast<int>(p.size() / 3);
115 if (N_ <= 0)
116 throw std::runtime_error("voro: build() needs at least one point");
117 const double boxVol = static_cast<double>(L_[0]) * L_[1] * L_[2];
118 const real_t spacing = static_cast<real_t>(std::cbrt(boxVol / N_));
119 if (weighted_ && static_cast<int>(wHost_.size()) != N_)
120 throw std::runtime_error("build(): weights (N,) must match the particle count");
121 pos_ = peclet::core::toDevice<real_t>(p, "pos");
122 if (weighted_) {
123 weight_ = peclet::core::toDevice<real_t>(wHost_, "w");
124 wDirty_ = false;
125 }
126 if (!weighted_ && !scene_.set)
127 mt_.template emplace<MT<false, NoSdfT>>();
128 else if (weighted_ && !scene_.set)
129 mt_.template emplace<MT<true, NoSdfT>>();
130 else if (!weighted_)
131 mt_.template emplace<MT<false, SceneT>>();
132 else
133 mt_.template emplace<MT<true, SceneT>>();
134 std::visit(
135 [&](auto& mt) {
136 using T = std::decay_t<decltype(mt)>;
137 mt.localCert = localCert_;
138 mt.useGate = useGate_;
139 mt.profile = profile_;
140 if constexpr (T::kHasSdf) {
141 mt.sdf = scene_.scene;
142 mt.wallExact = wallExact_;
143 mt.wallSkin = wallSkinFrac_ * spacing;
144 }
145 if constexpr (std::is_same_v<typename T::PlanePolicy, peclet::voro::Power>)
146 mt.setWeights(weight_);
147 mt.alloc(N_, L_.data(), tolFrac_ * spacing, real_t(defaults::kSkin) * spacing,
148 defaults::kSearchWindow, N_);
149 mt.rebuild(pos_);
150 },
151 mt_);
152 auto r = std::visit([](auto& mt) { return mt.report(); }, mt_);
153 if (r.buried > 0 || r.reachExceeded > 0 || r.overflow > 0) {
154 const std::string msg =
155 "peclet.voro: the tessellation is not a guaranteed-exact partition: " +
156 std::to_string(r.buried) +
157 " buried power cell(s) (seed outside its own cell, emptied), " +
158 std::to_string(r.reachExceeded) + " cell(s) with a search reach beyond half the box " +
159 "(min-image invalid), " + std::to_string(r.overflow) +
160 " overflowed cell(s). See diagnostics.build_report().";
161 if (strict)
162 throw std::runtime_error(msg);
163 nb::module_::import_("warnings").attr("warn")(msg);
164 }
165 }
166
167 // Incremental repair: update the resident tessellation to new positions (same N) without a full
168 // rebuild. Returns the per-step work stats.
169 nb::dict step(nb::ndarray<real_t, nb::c_contig> a) {
170 if (N_ == 0)
171 throw std::runtime_error(
172 "voro: Tessellation.step() before build() — call build(positions) first.");
173 std::vector<real_t> p = flatten3(a);
174 if (static_cast<int>(p.size() / 3) != N_)
175 throw std::runtime_error(
176 "voro: step(positions) got " + std::to_string(p.size() / 3) + " points but build() had " +
177 std::to_string(N_) +
178 " — the count is fixed by build(); call build() again to change it.");
179 pos_ = peclet::core::toDevice<real_t>(p, "pos");
180 if (weighted_ && wDirty_) { // weights changed since the last build/step: refresh in place
181 if (static_cast<int>(wHost_.size()) != N_)
182 throw std::runtime_error("step(): weights (N,) must match the particle count");
183 Kokkos::deep_copy(
184 weight_, Kokkos::View<const real_t*, Kokkos::HostSpace>(wHost_.data(), wHost_.size()));
185 wDirty_ = false;
186 }
187 auto st = std::visit([&](auto& mt) { return mt.step(pos_); }, mt_);
188 return repairStatsDict(st);
189 }
190
191 nb::ndarray<nb::numpy, real_t> get_volumes() {
192 const std::size_t N = static_cast<std::size_t>(N_);
193 DView vol = std::visit([](auto& mt) { return mt.vol; }, mt_);
194 auto v = Kokkos::subview(vol, Kokkos::make_pair(std::size_t(0), N));
195 return peclet::core::python::vector_to_ndarray(peclet::core::toVector(v), {N}, {1});
196 }
197
198 // Per-cell Voronoi neighbour (= face) count, recomputed from the resident topology store.
199 nb::ndarray<nb::numpy, int> get_neighbor_counts() {
200 using Cell =
202 const int N = N_;
203 Kokkos::View<int*, peclet::core::MemSpace> cnt("nbr", N);
204 auto st = std::visit([](auto& mt) { return mt.store; }, mt_);
205 auto C = cnt;
206 const real_t Lx = L_[0], Ly = L_[1], Lz = L_[2];
207 Kokkos::parallel_for(
208 "peclet.voro.nbrcount", Kokkos::RangePolicy<peclet::core::ExecSpace>(0, N),
209 KOKKOS_LAMBDA(int i) {
210 Cell c;
211 st.load(i, c, Lx, Ly, Lz);
212 C(i) = c.countFaces();
213 });
214 return peclet::core::python::vector_to_ndarray(peclet::core::toVector(cnt),
215 {static_cast<std::size_t>(N)}, {1});
216 }
217
218 // Rung A3: energies + forces of the RESIDENT tessellation (after build/step) on the published
219 // view — interfacial Σσ(t_i,t_j)A, wetting Σσ_s(t_i)A_wall, volume Σe_i(V_i) via a
220 // caller-supplied e'(V_i) — routed to the seed positions (and power weights). No rebuild:
221 // reevalPublish over the store with the facet-edge area-Jacobian CSR.
222 nb::dict energy_forces(nb::ndarray<int, nb::c_contig> types,
223 nb::ndarray<real_t, nb::c_contig> tension,
224 std::optional<nb::ndarray<real_t, nb::c_contig>> sigma_wall,
225 std::optional<nb::ndarray<real_t, nb::c_contig>> dEdV, real_t lloyd,
226 real_t facet_tension) {
227 if (N_ == 0)
228 throw std::runtime_error(
229 "voro: Tessellation.energy_forces() before build() — call build(positions) first.");
230 const int N = N_;
231 if ((int)types.shape(0) != N)
232 throw std::runtime_error("energy_forces(): types must be (N,)");
233 int nT = 0;
234 for (int i = 0; i < N; ++i)
235 nT = std::max(nT, types.data()[i] + 1);
236 if ((int)tension.size() != nT * nT)
237 throw std::runtime_error(
238 "energy_forces(): tension must be (nTypes, nTypes) with nTypes = max(types)+1");
239 Kokkos::View<int*, Mem> dtype("en.type", N);
240 Kokkos::deep_copy(dtype, Kokkos::View<const int*, Kokkos::HostSpace>(types.data(), N));
241 DView dten("en.tension", (size_t)nT * nT);
242 Kokkos::deep_copy(
243 dten, Kokkos::View<const real_t*, Kokkos::HostSpace>(tension.data(), (size_t)nT * nT));
244 DView dsw, dde;
245 if (sigma_wall) {
246 if ((int)sigma_wall->size() != nT)
247 throw std::runtime_error("energy_forces(): sigma_wall must be (nTypes,)");
248 dsw = DView("en.sw", nT);
249 Kokkos::deep_copy(dsw,
250 Kokkos::View<const real_t*, Kokkos::HostSpace>(sigma_wall->data(), nT));
251 }
252 if (dEdV) {
253 if ((int)dEdV->size() != N)
254 throw std::runtime_error("energy_forces(): dEdV must be (N,)");
255 dde = DView("en.dEdV", N);
256 Kokkos::deep_copy(dde, Kokkos::View<const real_t*, Kokkos::HostSpace>(dEdV->data(), N));
257 }
258 DView force("en.force", 3 * (size_t)N), forceW("en.forceW", weighted_ ? N : 0);
259 Kokkos::deep_copy(force, real_t(0));
260 if (weighted_)
261 Kokkos::deep_copy(forceW, real_t(0));
262 real_t eIf = 0, eWall = 0, eLloyd = 0, eTen = 0;
263 const real_t Larr[3] = {L_[0], L_[1], L_[2]};
264 std::visit(
265 [&](auto& mt) {
266 using T = std::decay_t<decltype(mt)>;
267 using Policy = typename T::PlanePolicy;
268 auto view =
269 peclet::voro::reevalPublish<real_t, defaults::kMaxPlanes, defaults::kMaxTriangles>(
270 mt.store, pos_, mt.vol, N, Larr, mt.wall, mt.xRef, /*withAreaGrad=*/true,
271 /*withMoments=*/lloyd != 0);
272 if (lloyd != 0)
273 eLloyd = peclet::voro::energy::lloydEnergyForce<real_t>(view, pos_, lloyd, force);
274 if (facet_tension != 0)
275 eTen = peclet::voro::energy::facetTensionEnergyForce<real_t, Policy>(
276 view, facet_tension, pos_, weight_, L_[0], force, forceW, mt.sdf);
277 eIf = peclet::voro::energy::interfaceEnergyForce<real_t, Policy>(
278 view, dtype, dten, nT, pos_, weight_, L_[0], force, forceW, mt.sdf);
279 if (dsw.extent(0) > 0)
280 eWall = peclet::voro::energy::wallEnergyForce<real_t, Policy>(
281 view, dtype, dsw, mt.sdf, pos_, weight_, L_[0], force, forceW);
282 if (dde.extent(0) > 0)
283 peclet::voro::energy::volumeGradientForce<real_t, Policy>(view, dde, pos_, weight_,
284 L_[0], force, forceW, mt.sdf);
285 },
286 mt_);
287 nb::dict d;
288 d["interface_energy"] = eIf;
289 d["wall_energy"] = eWall;
290 d["lloyd_energy"] = eLloyd;
291 d["tension_energy"] = eTen;
292 d["force"] = peclet::core::python::vector_to_ndarray(
293 peclet::core::toVector(force), {static_cast<std::size_t>(N), std::size_t(3)}, {3, 1});
294 if (weighted_)
295 d["force_w"] = peclet::core::python::vector_to_ndarray(peclet::core::toVector(forceW),
296 {static_cast<std::size_t>(N)}, {1});
297 return d;
298 }
299
300 // The face mesh of the resident tessellation (track C): reevalPublish over the store, then the
301 // reciprocal map and the owner/neighbour face records (fv/mesh.hpp). Used by FlowSolver.
303
304 // Per-cell number of resident SDF wall planes (0 everywhere without geometry).
305 nb::ndarray<nb::numpy, int> get_wall_counts() {
306 const std::size_t N = static_cast<std::size_t>(N_);
307 std::vector<int> v = std::visit(
308 [&](auto& mt) {
309 using T = std::decay_t<decltype(mt)>;
310 if constexpr (T::kHasSdf)
311 return peclet::core::toVector(mt.wall.cnt);
312 else
313 return std::vector<int>(N, 0);
314 },
315 mt_);
316 return peclet::core::python::vector_to_ndarray(std::move(v), {N}, {1});
317 }
318
319 int num_particles() const { return N_; }
320
321 // Drop every Kokkos View (teardown registry: runs before Kokkos::finalize at shutdown).
322 void release() noexcept override {
323 mt_.template emplace<MT<false, NoSdfT>>();
324 pos_ = DView{};
325 weight_ = DView{};
326 scene_.clear();
327 N_ = 0;
328 }
329
330 private:
331 std::array<real_t, 3> L_{1, 1, 1};
332 real_t tolFrac_ = defaults::kCertificateTolerance;
333 bool localCert_ = true, useGate_ = true, profile_ = false;
334 bool wallExact_ = true;
335 real_t wallSkinFrac_ = defaults::kWallSkin;
336 bool weighted_ = false, wDirty_ = false;
337 std::vector<real_t> wHost_;
338 int N_ = 0;
339 DView pos_, weight_;
340 SceneHolder scene_;
341 MtVariant mt_;
342};
343
344// The diagnostics tier of Tessellation: a view onto the owner (no state of its own).
347};
348} // namespace peclet::voro::pybind
349
350#endif // PECLET_VORO_BINDINGS_TESS_HPP
Definition voro_bindings_tess.hpp:52
std::array< real_t, 3 > extent() const
Definition voro_bindings_tess.hpp:61
nb::dict build_report()
Definition voro_bindings_tess.hpp:99
nb::ndarray< nb::numpy, real_t > get_volumes()
Definition voro_bindings_tess.hpp:191
void release() noexcept override
Definition voro_bindings_tess.hpp:322
void set_geometry(nb::ndarray< int, nb::c_contig > node_ints, nb::ndarray< real_t, nb::c_contig > node_reals, int root, real_t grad_h)
Definition voro_bindings_tess.hpp:67
nb::ndarray< nb::numpy, int > get_neighbor_counts()
Definition voro_bindings_tess.hpp:199
void set_tolerance(real_t frac)
Definition voro_bindings_tess.hpp:62
void set_gate(bool on)
Definition voro_bindings_tess.hpp:94
nb::dict energy_forces(nb::ndarray< int, nb::c_contig > types, nb::ndarray< real_t, nb::c_contig > tension, std::optional< nb::ndarray< real_t, nb::c_contig > > sigma_wall, std::optional< nb::ndarray< real_t, nb::c_contig > > dEdV, real_t lloyd, real_t facet_tension)
Definition voro_bindings_tess.hpp:222
nb::dict step(nb::ndarray< real_t, nb::c_contig > a)
Definition voro_bindings_tess.hpp:169
void set_local_certificate(bool on)
Definition voro_bindings_tess.hpp:93
void set_weights(nb::ndarray< real_t, nb::c_contig > w)
Definition voro_bindings_tess.hpp:78
void clear_weights()
Definition voro_bindings_tess.hpp:85
void clear_geometry()
Definition voro_bindings_tess.hpp:71
nb::ndarray< nb::numpy, int > get_wall_counts()
Definition voro_bindings_tess.hpp:305
void set_wall_mode(const std::string &mode, real_t skin_frac)
Definition voro_bindings_tess.hpp:72
void set_domain(std::array< real_t, 3 > extent, std::array< real_t, 3 > origin, std::array< bool, 3 > periodic)
Definition voro_bindings_tess.hpp:56
peclet::voro::fv::FaceMesh< real_t > face_mesh()
Definition voro_tessellation.cpp:8
void set_profile(bool on)
Definition voro_bindings_tess.hpp:95
void build(nb::ndarray< real_t, nb::c_contig > a, bool strict)
Definition voro_bindings_tess.hpp:112
int num_particles() const
Definition voro_bindings_tess.hpp:319
Rung A3: interfacial (surface-tension) energy between cells of different species on the published vie...
Track B, rung B1: the CENTROIDAL (Lloyd / CVT) energy on the published view.
Track C, rung C1 (Voronoi methods plan): the FACE mesh of a published tessellation — one record per g...
constexpr double kWallSkin
Definition voro_bindings_common.hpp:70
Definition voro_bindings_common.hpp:45
void checkDomain(const char *cls, std::array< real_t, 3 > extent, std::array< real_t, 3 > origin, std::array< bool, 3 > periodic)
Definition voro_bindings_common.hpp:195
nb::dict repairStatsDict(const peclet::voro::RepairStats &st)
Definition voro_bindings_tess.hpp:37
std::vector< real_t > flatten1(nb::ndarray< real_t, nb::c_contig > a)
Definition voro_bindings_common.hpp:114
std::vector< real_t > flatten3(nb::ndarray< real_t, nb::c_contig > a)
Definition voro_bindings_common.hpp:106
bool parseWallMode(const std::string &mode)
Definition voro_bindings_common.hpp:216
std::variant< MT< false, NoSdfT >, MT< true, NoSdfT >, MT< false, SceneT >, MT< true, SceneT > > MtVariant
Definition voro_bindings_tess.hpp:35
SceneHolder makeSceneHolder(nb::ndarray< int, nb::c_contig > node_ints, nb::ndarray< real_t, nb::c_contig > node_reals, int root, real_t grad_h)
Definition voro_bindings_common.hpp:152
Definition convex_cell.hpp:140
Definition repair.hpp:90
Per-step repair telemetry.
Definition repair.hpp:61
int wallFlagged
cells flagged by the SDF boundary watch (0 without an SDF)
Definition repair.hpp:70
int extra
cells gathered across the verify extra-passes
Definition repair.hpp:67
int pass1
cells gathered in Pass 1 (flagged ∪ partners ∪ skin-movers, after dilation)
Definition repair.hpp:64
int surgical
Pass-1 cells repaired surgically (Phase 4, no grid gather)
Definition repair.hpp:68
int pass2
cells gathered in Pass 2 (new face-neighbours of the movers)
Definition repair.hpp:66
bool fellBack
true if the cold-build fallback was triggered (repair did not close)
Definition repair.hpp:73
Route route
Definition repair.hpp:72
int pass1Raw
flagged count BEFORE dilation (the gate signal: pass1Raw/nProc = churn)
Definition repair.hpp:65
@ kRebuildGate
Definition repair.hpp:63
int verifyPasses
number of verify iterations run
Definition repair.hpp:69
Definition mesh.hpp:33
SceneT scene
Definition voro_bindings_common.hpp:141
void clear()
Definition voro_bindings_common.hpp:143
bool set
Definition voro_bindings_common.hpp:142
Definition voro_bindings_tess.hpp:345
Tess * t
Definition voro_bindings_tess.hpp:346
Track B, rung B1: uniform facet tension E = σ Σ_faces A_f (every interior face once) — the "roundness...
Rung A3: per-cell volume energies e_i(V_i) on the published view — target volume, log-barrier and the...
Internal helpers shared by the translation units of the peclet.voro nanobind module.
Kokkos::View< real_t *, peclet::core::MemSpace > DView
Definition voro_bindings_common.hpp:39
double real_t
Definition voro_bindings_common.hpp:38
The ONE seed-foot SDF wall chain (Voronoi methods plan, rung A3) + the wall interfacial energy on the...