TTK
Loading...
Searching...
No Matches
PersistentGenerators.h
Go to the documentation of this file.
1
44
45#pragma once
46
48
49#include <limits>
50
51namespace ttk {
53 public:
55
69 template <typename triangulationType>
71 std::vector<GeneratorType> &generators,
72 std::vector<std::vector<SimplexId>> &connComps,
73 const SimplexId *const offsets,
74 const triangulationType &triangulation);
75
76 private:
83 void getConnectedComponents(
84 const std::vector<std::array<SimplexId, 2>> &edgeNeighs,
85 std::vector<SimplexId> &connComp) const;
86
94 template <typename triangulationType>
95 void findGeneratorsConnectedComponents(
96 std::vector<std::vector<SimplexId>> &connComps,
97 const std::vector<GeneratorType> &generators,
98 const triangulationType &triangulation) const;
99
110 template <typename triangulationType>
111 void findHandlesGenerators(std::vector<GeneratorType> &generators,
112 const std::vector<SimplexId> &inf1Saddles,
113 const std::vector<PersistencePair> &minSadPairs,
114 const SimplexId *const offsets,
115 const triangulationType &triangulation) const;
116
131 template <typename triangulationType>
132 void pruneHandleGenerator(std::vector<SimplexId> &generator,
133 std::vector<bool> &genMask,
134 std::vector<bool> &pathVerts,
135 std::vector<SimplexId> &preds,
136 std::vector<size_t> &dists,
137 const SimplexId &s1,
138 const triangulationType &triangulation) const;
139
140 protected:
142 };
143} // namespace ttk
144
145template <typename triangulationType>
146void ttk::PersistentGenerators::findGeneratorsConnectedComponents(
147 std::vector<std::vector<SimplexId>> &connComps,
148 const std::vector<GeneratorType> &generators,
149 const triangulationType &triangulation) const {
150
151 connComps.resize(generators.size());
152
153 // global edge id -> local generator id mapping
154 std::vector<SimplexId> isVisited(triangulation.getNumberOfEdges(), -1);
155
156 for(size_t i = 0; i < generators.size(); ++i) {
157 const auto &genEdges{generators[i].boundary};
158 auto &connComp{connComps[i]};
159 if(genEdges.empty()) {
160 continue;
161 }
162 connComp.resize(genEdges.size(), -1);
163 // fill isVisited for the local generator
164 // (cleanup at iteration end)
165 for(size_t j = 0; j < genEdges.size(); ++j) {
166 isVisited[genEdges[j]] = j;
167 }
168
169 // for each generator edge, the local neighbor ids
170 // (assumption: exactly 2 neighboring edges per generator edge)
171 std::vector<std::array<SimplexId, 2>> edgeNeighs(genEdges.size());
172
173 for(size_t j = 0; j < genEdges.size(); ++j) {
174 const auto edge{genEdges[j]};
175 for(size_t k = 0; k < 2; ++k) {
176 SimplexId v{};
177 triangulation.getEdgeVertex(edge, k, v);
178 const auto nneighs{triangulation.getVertexEdgeNumber(v)};
179 for(SimplexId l = 0; l < nneighs; ++l) {
180 SimplexId neigh{};
181 triangulation.getVertexEdge(v, l, neigh);
182 const auto neighId{isVisited[neigh]};
183 if(neigh == edge || neighId == -1) {
184 continue;
185 }
186 edgeNeighs[j][k] = neighId;
187 break; // go to next vertex
188 }
189 }
190 }
191
192 this->getConnectedComponents(edgeNeighs, connComp);
193
194 // clean isVisited
195 for(const auto e : genEdges) {
196 isVisited[e] = -1;
197 }
198 }
199}
200
201template <typename triangulationType>
202void ttk::PersistentGenerators::findHandlesGenerators(
203 std::vector<GeneratorType> &generators,
204 const std::vector<SimplexId> &inf1Saddles,
205 const std::vector<PersistencePair> &minSadPairs,
206 const SimplexId *const offsets,
207 const triangulationType &triangulation) const {
208
209 Timer tm{};
210
211 // minimum vertex id -> paired 1-saddle edge id mapping
212 std::vector<SimplexId> min2sad(triangulation.getNumberOfVertices(), -1);
213 for(const auto &p : minSadPairs) {
214 min2sad[p.birth] = p.death;
215 }
216
217 // global maximum
218 const auto globMax{std::distance(
219 offsets,
220 std::max_element(offsets, offsets + triangulation.getNumberOfVertices()))};
221
222 // storage (allocated once, cleaned after each iteration)
223 std::vector<bool> genMask(triangulation.getNumberOfEdges(), false);
224 std::vector<size_t> dists(
225 triangulation.getNumberOfVertices(), std::numeric_limits<size_t>::max());
226 std::vector<SimplexId> preds(triangulation.getNumberOfVertices(), -1);
227 std::vector<bool> pathVerts(triangulation.getNumberOfVertices(), false);
228
229 // follow descending 1-separatrices to find representative generator
230 for(const auto s1 : inf1Saddles) {
231 std::vector<SimplexId> generator{};
232 std::set<SimplexId> visited1Sads{};
233
234 std::queue<SimplexId> toPropagate{};
235 toPropagate.push(s1);
236
237 while(!toPropagate.empty()) {
238 // current 1-saddle
239 const auto curr{toPropagate.front()};
240 toPropagate.pop();
241 visited1Sads.emplace(curr);
242
243 if(curr != s1) {
244 generator.emplace_back(curr);
245 }
246
247 for(SimplexId i = 0; i < 2; ++i) {
248 SimplexId currVert{};
249 triangulation.getEdgeVertex(curr, i, currVert);
250 std::vector<Cell> vpath{};
251 SimplexId min{-1};
252 this->dg_.getDescendingPath(Cell{0, currVert}, vpath, triangulation);
253 const auto &lastCell = vpath.back();
254 if(lastCell.dim_ == 0) {
255 min = lastCell.id_;
256 }
257
258 if(min == -1) {
259 // separatrix leads to nowhere
260 continue;
261 }
262
263 // store vpath
264 for(const auto &c : vpath) {
265 if(c.dim_ == 1) {
266 generator.emplace_back(c.id_);
267 }
268 }
269
270 if(min2sad[min] == -1) {
271 // global minimum ?
272 continue;
273 }
274
275 const auto next{min2sad[min]};
276 if(visited1Sads.find(next) == visited1Sads.end()) {
277 toPropagate.push(next);
278 }
279 }
280 }
281
282 // remove duplicates
283 TTK_PSORT(this->threadNumber_, generator.begin(), generator.end());
284 const auto last = std::unique(generator.begin(), generator.end());
285 generator.erase(last, generator.end());
286
287 if(this->PruneHandlesGenerators) {
288 // post-process: Dijkstra from one 1-saddle vertex to the other
289 this->pruneHandleGenerator(
290 generator, genMask, pathVerts, preds, dists, s1, triangulation);
291 }
292
293 // ensure unpaired 1-saddle is first
294 generator.emplace_back(s1);
295 std::swap(generator[0], generator.back());
296
297 generators.emplace_back(GeneratorType{
298 generator, -1,
299 std::array<SimplexId, 2>{
300 static_cast<SimplexId>(globMax),
301 this->dg_.getCellGreaterVertex(Cell{1, s1}, triangulation),
302 }});
303 }
304
305 this->printMsg("Computed " + std::to_string(inf1Saddles.size())
306 + " topological handle generators",
307 1.0, tm.getElapsedTime(), this->threadNumber_);
308}
309
310template <typename triangulationType>
311void ttk::PersistentGenerators::pruneHandleGenerator(
312 std::vector<SimplexId> &generator,
313 std::vector<bool> &genMask,
314 std::vector<bool> &pathVerts,
315 std::vector<SimplexId> &preds,
316 std::vector<size_t> &dists,
317 const SimplexId &s1,
318 const triangulationType &triangulation) const {
319
320 // store generator vertices for faster cleanup
321 std::vector<SimplexId> genVerts{};
322 genVerts.reserve(2 * generator.size());
323
324 for(const auto e : generator) {
325 genMask[e] = true;
326 }
327
328 using PQueueElem = std::pair<size_t, SimplexId>;
329 using PQueue = std::priority_queue<PQueueElem, std::vector<PQueueElem>,
330 std::greater<PQueueElem>>;
331 PQueue pq{};
332 // try to find the shortest path between seed0 & seed1 (the
333 // vertices of the critical edge marking the topological handle)
334 // on generator \ {s1}
335 SimplexId seed0{}, seed1{};
336 triangulation.getEdgeVertex(s1, 0, seed0);
337 triangulation.getEdgeVertex(s1, 1, seed1);
338 genVerts.emplace_back(seed0);
339 genVerts.emplace_back(seed1);
340 pq.emplace(0, seed0);
341 dists[seed0] = 0;
342
343 while(!pq.empty()) {
344 const auto curr{pq.top()};
345 genVerts.emplace_back(curr.second);
346 pq.pop();
347
348 const auto nneighs{triangulation.getVertexNeighborNumber(curr.second)};
349 dists[curr.second] = curr.first;
350
351 for(SimplexId i = 0; i < nneighs; ++i) {
352 SimplexId neigh{}, edge{};
353 // vertex neighbor & corresponding edge are at the same index
354 triangulation.getVertexNeighbor(curr.second, i, neigh);
355 triangulation.getVertexEdge(curr.second, i, edge);
356 // stay on generator edges
357 if(!genMask[edge]) {
358 continue;
359 }
360 // skip critical edge
361 if(curr.second == seed0 && neigh == seed1) {
362 continue;
363 }
364 if(dists[neigh] > curr.first + 1) {
365 dists[neigh] = curr.first + 1;
366 preds[neigh] = curr.second;
367 pq.emplace(dists[neigh], neigh);
368 }
369 }
370 }
371
372 // extract shortest path
373 SimplexId curr{seed1};
374 while(curr != seed0) {
375 pathVerts[curr] = true;
376 curr = preds[curr];
377 }
378 pathVerts[seed0] = true;
379 pathVerts[seed1] = true;
380
381 std::vector<SimplexId> prunedGenerator{};
382 prunedGenerator.reserve(generator.size());
383 for(const auto e : generator) {
384 SimplexId v0{}, v1{};
385 triangulation.getEdgeVertex(e, 0, v0);
386 triangulation.getEdgeVertex(e, 1, v1);
387 if(pathVerts[v0] && pathVerts[v1]) {
388 prunedGenerator.emplace_back(e);
389 }
390 }
391 std::swap(generator, prunedGenerator);
392
393 // cleanup: reset large vectors
394 for(const auto e : prunedGenerator) {
395 genMask[e] = false;
396 }
397 for(const auto v : genVerts) {
398 preds[v] = -1;
399 dists[v] = std::numeric_limits<size_t>::max();
400 pathVerts[v] = false;
401 }
402}
403
404template <typename triangulationType>
406 std::vector<GeneratorType> &generators,
407 std::vector<std::vector<SimplexId>> &connComps,
408 const SimplexId *const offsets,
409 const triangulationType &triangulation) {
410
411 // allocate memory
412 this->alloc(triangulation);
413
414 Timer tm{};
415 const auto dim = this->dg_.getDimensionality();
416 if(dim < 2) {
417 this->printWrn("Cannot compute cycles for 1D datasets");
418 return 0;
419 }
420 if(dim == 2) {
421 // fix container size for 2D datasets (use
422 // eliminateBoundariesSandwich for saddle-max instead of
423 // tripletsToPersistencePairs)
424 this->critEdges_.resize(triangulation.getNumberOfEdges());
425 this->edgeTrianglePartner_.resize(triangulation.getNumberOfEdges(), -1);
426 this->onBoundary_.resize(triangulation.getNumberOfEdges(), false);
427 this->s2Mapping_.resize(triangulation.getNumberOfTriangles(), -1);
428 this->s1Mapping_.resize(triangulation.getNumberOfEdges(), -1);
429 }
430
431 // get every critical cell sorted them by dimension
432 std::array<std::vector<SimplexId>, 4> criticalCellsByDim{};
433 // holds the critical cells order
434 auto &critCellsOrder{this->critCellsOrder_};
435
436 this->extractCriticalCells(
437 criticalCellsByDim, critCellsOrder, offsets, triangulation, true);
438
439 // if minima are paired
440 auto &pairedMinima{this->pairedCritCells_[0]};
441 // if 1-saddles are paired
442 auto &paired1Saddles{this->pairedCritCells_[1]};
443 // if 2-saddles are paired
444 auto &paired2Saddles{this->pairedCritCells_[dim - 1]};
445 // if maxima are paired
446 auto &pairedMaxima{this->pairedCritCells_[dim]};
447
448 // minima - saddle pairs
449 // we need the min-saddle pairs to get the infinite pairs of
450 // dimension 1 (topological handles)
451 std::vector<PersistencePair> minSadPairs{};
452 this->getMinSaddlePairs(minSadPairs, pairedMinima, paired1Saddles,
453 criticalCellsByDim[1], critCellsOrder[1], offsets,
454 triangulation);
455
456 if(dim == 3) {
457 // saddle - maxima pairs
458 // this should fasten the computation of saddle-saddle pairs
459 // (sandwich)
460 std::vector<PersistencePair> sadMaxPairs{};
461 this->getMaxSaddlePairs(
462 sadMaxPairs, pairedMaxima, paired2Saddles, criticalCellsByDim[dim - 1],
463 critCellsOrder[dim - 1], critCellsOrder[dim], triangulation);
464 }
465
466 if(!criticalCellsByDim[1].empty() && !criticalCellsByDim[2].empty()) {
467 // compute saddle-saddle pairs, extract generators/cycles
468 // (eliminateBoundariesSandwich boundaries right before
469 // simplification)
470 std::vector<PersistencePair> sadSadPairs{};
471 this->getSaddleSaddlePairs(
472 sadSadPairs, paired1Saddles, dim == 3 ? paired2Saddles : pairedMaxima,
473 true, generators, criticalCellsByDim[1], criticalCellsByDim[2],
474 critCellsOrder[1], triangulation);
475 }
476
477 // detect topological handles
478 std::vector<SimplexId> inf1Saddles{};
479 for(const auto s1 : criticalCellsByDim[1]) {
480 if(!paired1Saddles[s1]) {
481 inf1Saddles.emplace_back(s1);
482 }
483 }
484
485 if(!inf1Saddles.empty()) {
486 this->findHandlesGenerators(
487 generators, inf1Saddles, minSadPairs, offsets, triangulation);
488 }
489
490 if(!generators.empty()) {
491 // detect connected components in cycles
492 this->findGeneratorsConnectedComponents(
493 connComps, generators, triangulation);
494 }
495
496 this->printMsg(
497 "Computed " + std::to_string(generators.size()) + " persistent generators",
498 1.0, tm.getElapsedTime(), this->threadNumber_);
499
500 // free memory
501 this->clear();
502
503 return 0;
504}
#define TTK_PSORT(NTHREADS,...)
Parallel sort macro.
Definition OpenMP.h:46
TTK DiscreteMorseSandwich processing package.
TTK PersistentGenerators processing package.
int computePersistentGenerators(std::vector< GeneratorType > &generators, std::vector< std::vector< SimplexId > > &connComps, const SimplexId *const offsets, const triangulationType &triangulation)
Compute the persistence generators from the discrete gradient.
std::string to_string(__int128)
Definition ripserpy.cpp:99
The Topology ToolKit.
int SimplexId
Identifier type for simplices of any dimension.
Definition DataTypes.h:22
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/|__ _|"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)