240 ShapeSpaceExtremaPolarity polarity) {
241 constexpr const char*
context =
"ShapeSpaceSaliency::computeExtinctionValues";
242 validatePolarity(polarity,
context);
243 validateRootedTree(tree,
context);
244 validateNodeBuffer(tree, attribute,
false,
"attribute",
context);
246 std::vector<NodeId> nodes;
249 nodes.push_back(nodeId);
256 std::vector<NodeId>
stack;
258 depth[
static_cast<std::size_t
>(tree.
root())] = 0;
259 while (!
stack.empty()) {
262 for (
NodeId childId : detail::TreeKernelAccess::children(tree, nodeId)) {
263 depth[
static_cast<std::size_t
>(
childId)] = depth[
static_cast<std::size_t
>(nodeId)] + 1;
269 const std::span<const std::int32_t>
supportCardinalityByNode = detail::TreeKernelAccess::nodeSupportCardinalities(tree);
274 const std::size_t
lhsIndex =
static_cast<std::size_t
>(
lhs);
275 const std::size_t
rhsIndex =
static_cast<std::size_t
>(
rhs);
285 throw std::logic_error(std::string(
context) +
286 " cannot distinguish two live nodes by spatial support and hierarchy depth.");
290 const Real lhsLevel = attribute[static_cast<std::size_t>(lhs)];
291 const Real rhsLevel = attribute[static_cast<std::size_t>(rhs)];
292 if (lhsLevel == rhsLevel) {
293 return canonicalShapeSpaceNodePrecedes(lhs, rhs);
295 if (polarity == ShapeSpaceExtremaPolarity::Minima) {
301 Real
globalMinimum = attribute[
static_cast<std::size_t
>(nodes.front())];
303 for (
NodeId nodeId : nodes) {
304 const Real
level = attribute[
static_cast<std::size_t
>(nodeId)];
309 std::vector<NodeId> componentParent(slotCount, InvalidNode);
310 std::vector<int> componentSize(slotCount, 0);
311 std::vector<int> survivor(slotCount, -1);
312 std::vector<std::uint8_t> active(slotCount, 0);
313 std::vector<NodeId> plateauRepresentative(slotCount, InvalidNode);
314 std::vector<std::vector<NodeId>> priorComponents(slotCount);
316 auto findComponent = [&](NodeId nodeId) {
317 NodeId root = nodeId;
318 while (componentParent[
static_cast<std::size_t
>(root)] != root) {
319 root = componentParent[
static_cast<std::size_t
>(root)];
321 while (componentParent[
static_cast<std::size_t
>(nodeId)] != nodeId) {
322 const NodeId next = componentParent[
static_cast<std::size_t
>(nodeId)];
323 componentParent[
static_cast<std::size_t
>(nodeId)] = root;
330 lhs = findComponent(lhs);
331 rhs = findComponent(rhs);
335 const int lhsSize = componentSize[
static_cast<std::size_t
>(lhs)];
336 const int rhsSize = componentSize[
static_cast<std::size_t
>(rhs)];
337 if (lhsSize < rhsSize || (lhsSize == rhsSize && canonicalShapeSpaceNodePrecedes(rhs, lhs))) {
340 componentParent[
static_cast<std::size_t
>(rhs)] = lhs;
341 componentSize[
static_cast<std::size_t
>(lhs)] += componentSize[
static_cast<std::size_t
>(rhs)];
345 auto forEachShapeNeighbor = [&](
NodeId nodeId,
auto&& visitor) {
346 if (!tree.
isRoot(nodeId)) {
347 visitor(detail::TreeKernelAccess::nodeParent(tree, nodeId));
349 for (NodeId childId : detail::TreeKernelAccess::children(tree, nodeId)) {
354 std::vector<ShapeSpaceExtremum<Real>> extrema;
355 std::vector<std::uint8_t> finalized;
357 auto finishExtremum = [&](
int extremumIndex, Real deathLevel) {
358 if (extremumIndex < 0 ||
static_cast<std::size_t
>(extremumIndex) >= extrema.size() || finalized[
static_cast<std::size_t
>(extremumIndex)] != 0) {
359 throw std::runtime_error(std::string(context) +
" encountered inconsistent component-extremum state.");
361 ShapeSpaceExtremum<Real>& extremum = extrema[
static_cast<std::size_t
>(extremumIndex)];
362 extremum.deathLevel = deathLevel;
363 extremum.extinction = checkedExtinction(extremum.birthLevel, deathLevel, polarity, extremum.representative, context);
364 finalized[
static_cast<std::size_t
>(extremumIndex)] = 1;
367 auto isStronger = [&](
int lhsIndex,
int rhsIndex) {
368 const auto& lhs = extrema[
static_cast<std::size_t
>(lhsIndex)];
369 const auto& rhs = extrema[
static_cast<std::size_t
>(rhsIndex)];
370 if (lhs.birthLevel != rhs.birthLevel) {
371 if (polarity == ShapeSpaceExtremaPolarity::Minima) {
372 return lhs.birthLevel < rhs.birthLevel;
374 return rhs.birthLevel < lhs.birthLevel;
376 return canonicalShapeSpaceNodePrecedes(lhs.representative, rhs.representative);
379 std::size_t batchBegin = 0;
380 while (batchBegin < nodes.size()) {
381 const Real level = attribute[
static_cast<std::size_t
>(nodes[batchBegin])];
382 std::size_t batchEnd = batchBegin + 1;
383 while (batchEnd < nodes.size() && attribute[
static_cast<std::size_t
>(nodes[batchEnd])] == level) {
387 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
388 const NodeId nodeId = nodes[i];
389 const std::size_t index =
static_cast<std::size_t
>(nodeId);
391 componentParent[index] = nodeId;
392 componentSize[index] = 1;
393 survivor[index] = -1;
396 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
397 const NodeId nodeId = nodes[i];
398 forEachShapeNeighbor(nodeId, [&](NodeId neighborId) {
399 if (active[
static_cast<std::size_t
>(neighborId)] != 0 && attribute[
static_cast<std::size_t
>(neighborId)] == level) {
400 static_cast<void>(joinComponents(nodeId, neighborId));
405 std::vector<NodeId> plateauRoots;
406 plateauRoots.reserve(batchEnd - batchBegin);
407 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
408 const NodeId nodeId = nodes[i];
409 const NodeId plateauRoot = findComponent(nodeId);
410 plateauRoots.push_back(plateauRoot);
412 NodeId& representative = plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)];
413 if (representative == InvalidNode || depth[
static_cast<std::size_t
>(nodeId)] < depth[
static_cast<std::size_t
>(representative)] ||
414 (depth[
static_cast<std::size_t
>(nodeId)] == depth[
static_cast<std::size_t
>(representative)] &&
415 canonicalShapeSpaceNodePrecedes(nodeId, representative))) {
416 representative = nodeId;
419 forEachShapeNeighbor(nodeId, [&](NodeId neighborId) {
420 if (active[
static_cast<std::size_t
>(neighborId)] != 0 && attribute[
static_cast<std::size_t
>(neighborId)] != level) {
421 priorComponents[
static_cast<std::size_t
>(plateauRoot)].push_back(findComponent(neighborId));
426 std::sort(plateauRoots.begin(), plateauRoots.end(), canonicalShapeSpaceNodePrecedes);
427 plateauRoots.erase(std::unique(plateauRoots.begin(), plateauRoots.end()), plateauRoots.end());
429 for (NodeId plateauRoot : plateauRoots) {
430 auto& adjacentComponents = priorComponents[
static_cast<std::size_t
>(plateauRoot)];
431 for (NodeId& component : adjacentComponents) {
432 component = findComponent(component);
434 std::sort(adjacentComponents.begin(), adjacentComponents.end(), canonicalShapeSpaceNodePrecedes);
435 adjacentComponents.erase(std::unique(adjacentComponents.begin(), adjacentComponents.end()), adjacentComponents.end());
437 int winningExtremum = -1;
438 if (adjacentComponents.empty()) {
439 const NodeId representative = plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)];
440 winningExtremum =
static_cast<int>(extrema.size());
441 extrema.push_back(ShapeSpaceExtremum<Real>{representative, level, level, Real{0}});
442 finalized.push_back(0);
444 for (NodeId component : adjacentComponents) {
445 const int candidate = survivor[
static_cast<std::size_t
>(component)];
447 throw std::runtime_error(std::string(context) +
" found an active level component without a surviving extremum.");
449 if (winningExtremum < 0 || isStronger(candidate, winningExtremum)) {
450 winningExtremum = candidate;
454 for (NodeId component : adjacentComponents) {
455 const int candidate = survivor[
static_cast<std::size_t
>(component)];
456 if (candidate != winningExtremum) {
457 finishExtremum(candidate, level);
462 NodeId combinedRoot = plateauRoot;
463 for (NodeId component : adjacentComponents) {
464 combinedRoot = joinComponents(combinedRoot, component);
466 survivor[
static_cast<std::size_t
>(combinedRoot)] = winningExtremum;
468 plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)] = InvalidNode;
469 adjacentComponents.clear();
472 batchBegin = batchEnd;
475 const NodeId finalComponent = findComponent(nodes.front());
476 for (NodeId nodeId : nodes) {
477 if (findComponent(nodeId) != finalComponent) {
478 throw std::runtime_error(std::string(context) +
" did not produce one connected final level component.");
482 const int dominantExtremum = survivor[
static_cast<std::size_t
>(finalComponent)];
483 if (dominantExtremum < 0) {
484 throw std::runtime_error(std::string(context) +
" did not retain a dominant extremum.");
486 finishExtremum(dominantExtremum, polarity == ShapeSpaceExtremaPolarity::Minima ? globalMaximum : globalMinimum);
488 for (std::uint8_t isFinalized : finalized) {
489 if (isFinalized == 0) {
490 throw std::runtime_error(std::string(context) +
" left an extremum without a death level.");
494 std::sort(extrema.begin(), extrema.end(),
495 [&](
const auto& lhs,
const auto& rhs) { return canonicalShapeSpaceNodePrecedes(lhs.representative, rhs.representative); });
497 ShapeSpaceExtinctionResult<Real> result;
498 result.extrema = std::move(extrema);
499 result.nodeScores.assign(slotCount, Real{0});
500 for (
const ShapeSpaceExtremum<Real>& extremum : result.extrema) {
501 result.nodeScores[
static_cast<std::size_t
>(extremum.representative)] = extremum.extinction;