249 ShapeSpaceExtremaPolarity polarity) {
250 constexpr const char*
context =
"ShapeSpaceSaliency::computeExtinctionValues";
251 validatePolarity(polarity,
context);
252 validateRootedTree(tree,
context);
253 validateNodeBuffer(tree, attribute,
false,
"attribute",
context);
255 std::vector<NodeId> nodes;
261 throw std::invalid_argument(std::string(
context) +
" requires at least one live node.");
268 std::vector<NodeId>
stack;
270 depth[
static_cast<std::size_t
>(tree.
root())] = 0;
271 while (!
stack.empty()) {
275 depth[
static_cast<std::size_t
>(
childId)] = depth[
static_cast<std::size_t
>(
nodeId)] + 1;
281 const std::span<const std::int32_t>
supportCardinalityByNode = detail::CommittedTreeAccess::nodeSupportCardinalities(tree);
286 const std::size_t
lhsIndex =
static_cast<std::size_t
>(
lhs);
287 const std::size_t
rhsIndex =
static_cast<std::size_t
>(
rhs);
297 throw std::logic_error(std::string(
context) +
298 " cannot distinguish two live nodes by spatial support and hierarchy depth.");
302 const Real lhsLevel = attribute[static_cast<std::size_t>(lhs)];
303 const Real rhsLevel = attribute[static_cast<std::size_t>(rhs)];
304 if (lhsLevel == rhsLevel) {
305 return canonicalShapeSpaceNodePrecedes(lhs, rhs);
307 if (polarity == ShapeSpaceExtremaPolarity::Minima) {
313 Real
globalMinimum = attribute[
static_cast<std::size_t
>(nodes.front())];
316 const Real
level = attribute[
static_cast<std::size_t
>(
nodeId)];
321 std::vector<NodeId> componentParent(slotCount, InvalidNode);
322 std::vector<int> componentSize(slotCount, 0);
323 std::vector<int> survivor(slotCount, -1);
324 std::vector<std::uint8_t> active(slotCount, 0);
325 std::vector<NodeId> plateauRepresentative(slotCount, InvalidNode);
326 std::vector<std::vector<NodeId>> priorComponents(slotCount);
328 auto findComponent = [&](NodeId nodeId) {
329 NodeId root = nodeId;
330 while (componentParent[
static_cast<std::size_t
>(root)] != root) {
331 root = componentParent[
static_cast<std::size_t
>(root)];
333 while (componentParent[
static_cast<std::size_t
>(nodeId)] != nodeId) {
334 const NodeId next = componentParent[
static_cast<std::size_t
>(nodeId)];
335 componentParent[
static_cast<std::size_t
>(nodeId)] = root;
342 lhs = findComponent(lhs);
343 rhs = findComponent(rhs);
347 const int lhsSize = componentSize[
static_cast<std::size_t
>(lhs)];
348 const int rhsSize = componentSize[
static_cast<std::size_t
>(rhs)];
349 if (lhsSize < rhsSize || (lhsSize == rhsSize && canonicalShapeSpaceNodePrecedes(rhs, lhs))) {
352 componentParent[
static_cast<std::size_t
>(rhs)] = lhs;
353 componentSize[
static_cast<std::size_t
>(lhs)] += componentSize[
static_cast<std::size_t
>(rhs)];
357 auto forEachShapeNeighbor = [&](
NodeId nodeId,
auto&& visitor) {
358 if (!tree.
isRoot(nodeId)) {
359 visitor(tree.
parent(nodeId));
361 for (NodeId childId : tree.children(nodeId)) {
366 std::vector<ShapeSpaceExtremum<Real>> extrema;
367 std::vector<std::uint8_t> finalized;
369 auto finishExtremum = [&](
int extremumIndex, Real deathLevel) {
370 if (extremumIndex < 0 ||
static_cast<std::size_t
>(extremumIndex) >= extrema.size() || finalized[
static_cast<std::size_t
>(extremumIndex)] != 0) {
371 throw std::runtime_error(std::string(context) +
" encountered inconsistent component-extremum state.");
373 ShapeSpaceExtremum<Real>& extremum = extrema[
static_cast<std::size_t
>(extremumIndex)];
374 extremum.deathLevel = deathLevel;
375 extremum.extinction = checkedExtinction(extremum.birthLevel, deathLevel, polarity, extremum.representative, context);
376 finalized[
static_cast<std::size_t
>(extremumIndex)] = 1;
379 auto isStronger = [&](
int lhsIndex,
int rhsIndex) {
380 const auto& lhs = extrema[
static_cast<std::size_t
>(lhsIndex)];
381 const auto& rhs = extrema[
static_cast<std::size_t
>(rhsIndex)];
382 if (lhs.birthLevel != rhs.birthLevel) {
383 if (polarity == ShapeSpaceExtremaPolarity::Minima) {
384 return lhs.birthLevel < rhs.birthLevel;
386 return rhs.birthLevel < lhs.birthLevel;
388 return canonicalShapeSpaceNodePrecedes(lhs.representative, rhs.representative);
391 std::size_t batchBegin = 0;
392 while (batchBegin < nodes.size()) {
393 const Real level = attribute[
static_cast<std::size_t
>(nodes[batchBegin])];
394 std::size_t batchEnd = batchBegin + 1;
395 while (batchEnd < nodes.size() && attribute[
static_cast<std::size_t
>(nodes[batchEnd])] == level) {
399 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
400 const NodeId nodeId = nodes[i];
401 const std::size_t index =
static_cast<std::size_t
>(nodeId);
403 componentParent[index] = nodeId;
404 componentSize[index] = 1;
405 survivor[index] = -1;
408 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
409 const NodeId nodeId = nodes[i];
410 forEachShapeNeighbor(nodeId, [&](NodeId neighborId) {
411 if (active[
static_cast<std::size_t
>(neighborId)] != 0 && attribute[
static_cast<std::size_t
>(neighborId)] == level) {
412 static_cast<void>(joinComponents(nodeId, neighborId));
417 std::vector<NodeId> plateauRoots;
418 plateauRoots.reserve(batchEnd - batchBegin);
419 for (std::size_t i = batchBegin; i < batchEnd; ++i) {
420 const NodeId nodeId = nodes[i];
421 const NodeId plateauRoot = findComponent(nodeId);
422 plateauRoots.push_back(plateauRoot);
424 NodeId& representative = plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)];
425 if (representative == InvalidNode || depth[
static_cast<std::size_t
>(nodeId)] < depth[
static_cast<std::size_t
>(representative)] ||
426 (depth[
static_cast<std::size_t
>(nodeId)] == depth[
static_cast<std::size_t
>(representative)] &&
427 canonicalShapeSpaceNodePrecedes(nodeId, representative))) {
428 representative = nodeId;
431 forEachShapeNeighbor(nodeId, [&](NodeId neighborId) {
432 if (active[
static_cast<std::size_t
>(neighborId)] != 0 && attribute[
static_cast<std::size_t
>(neighborId)] != level) {
433 priorComponents[
static_cast<std::size_t
>(plateauRoot)].push_back(findComponent(neighborId));
438 std::sort(plateauRoots.begin(), plateauRoots.end(), canonicalShapeSpaceNodePrecedes);
439 plateauRoots.erase(std::unique(plateauRoots.begin(), plateauRoots.end()), plateauRoots.end());
441 for (NodeId plateauRoot : plateauRoots) {
442 auto& adjacentComponents = priorComponents[
static_cast<std::size_t
>(plateauRoot)];
443 for (NodeId& component : adjacentComponents) {
444 component = findComponent(component);
446 std::sort(adjacentComponents.begin(), adjacentComponents.end(), canonicalShapeSpaceNodePrecedes);
447 adjacentComponents.erase(std::unique(adjacentComponents.begin(), adjacentComponents.end()), adjacentComponents.end());
449 int winningExtremum = -1;
450 if (adjacentComponents.empty()) {
451 const NodeId representative = plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)];
452 winningExtremum =
static_cast<int>(extrema.size());
453 extrema.push_back(ShapeSpaceExtremum<Real>{representative, level, level, Real{0}});
454 finalized.push_back(0);
456 for (NodeId component : adjacentComponents) {
457 const int candidate = survivor[
static_cast<std::size_t
>(component)];
459 throw std::runtime_error(std::string(context) +
" found an active level component without a surviving extremum.");
461 if (winningExtremum < 0 || isStronger(candidate, winningExtremum)) {
462 winningExtremum = candidate;
466 for (NodeId component : adjacentComponents) {
467 const int candidate = survivor[
static_cast<std::size_t
>(component)];
468 if (candidate != winningExtremum) {
469 finishExtremum(candidate, level);
474 NodeId combinedRoot = plateauRoot;
475 for (NodeId component : adjacentComponents) {
476 combinedRoot = joinComponents(combinedRoot, component);
478 survivor[
static_cast<std::size_t
>(combinedRoot)] = winningExtremum;
480 plateauRepresentative[
static_cast<std::size_t
>(plateauRoot)] = InvalidNode;
481 adjacentComponents.clear();
484 batchBegin = batchEnd;
487 const NodeId finalComponent = findComponent(nodes.front());
488 for (NodeId nodeId : nodes) {
489 if (findComponent(nodeId) != finalComponent) {
490 throw std::runtime_error(std::string(context) +
" did not produce one connected final level component.");
494 const int dominantExtremum = survivor[
static_cast<std::size_t
>(finalComponent)];
495 if (dominantExtremum < 0) {
496 throw std::runtime_error(std::string(context) +
" did not retain a dominant extremum.");
498 finishExtremum(dominantExtremum, polarity == ShapeSpaceExtremaPolarity::Minima ? globalMaximum : globalMinimum);
500 for (std::uint8_t isFinalized : finalized) {
501 if (isFinalized == 0) {
502 throw std::runtime_error(std::string(context) +
" left an extremum without a death level.");
506 std::sort(extrema.begin(), extrema.end(),
507 [&](
const auto& lhs,
const auto& rhs) { return canonicalShapeSpaceNodePrecedes(lhs.representative, rhs.representative); });
509 ShapeSpaceExtinctionResult<Real> result;
510 result.extrema = std::move(extrema);
511 result.nodeScores.assign(slotCount, Real{0});
512 for (
const ShapeSpaceExtremum<Real>& extremum : result.extrema) {
513 result.nodeScores[
static_cast<std::size_t
>(extremum.representative)] = extremum.extinction;