121 friend class detail::TreeKernelAccess;
130 template <AltitudeValue T>
132 std::optional<NodeId> preservedExternalNodeIdOffset,
143 std::optional<GridDomain2D> gridDomain2D_;
147 std::optional<NodeId> preservedExternalNodeIdOffset_;
149 bool editSessionOpen_ =
false;
155 std::vector<NodeId> smallestNodeMap_;
159 std::vector<NodeId> nodeParent_;
163 std::vector<NodeId> firstChild_;
165 std::vector<NodeId> nextSibling_;
167 std::vector<NodeId> prevSibling_;
169 std::vector<NodeId> lastChild_;
171 std::vector<int> numChildrenByNode_;
175 std::vector<uint8_t> alive_;
177 std::vector<NodeId> freeNodeIds_;
181 std::vector<PixelId> properHead_;
183 std::vector<PixelId> properTail_;
185 std::vector<int> properPartCardinalityByNode_;
187 std::vector<PixelId> nextProperPart_;
189 std::vector<PixelId> prevProperPart_;
193 struct DfsIntervalCache {
195 std::vector<int> entryIndex;
197 std::vector<int> exitIndex;
201 void invalidate()
noexcept { valid =
false; }
204 mutable DfsIntervalCache dfsIntervalCache_;
206 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
209 struct NodeSupportMetadataCache {
211 std::vector<std::int32_t> cardinalityByNode;
213 std::vector<PixelId> smallestPixelByNode;
215 std::size_t nodeStructureVersion = std::numeric_limits<std::size_t>::max();
217 std::size_t topologyVersion = std::numeric_limits<std::size_t>::max();
219 std::size_t properPartVersion = std::numeric_limits<std::size_t>::max();
222 void invalidate() noexcept {
223 nodeStructureVersion = std::numeric_limits<std::size_t>::max();
224 topologyVersion = std::numeric_limits<std::size_t>::max();
225 properPartVersion = std::numeric_limits<std::size_t>::max();
229 mutable NodeSupportMetadataCache nodeSupportMetadataCache_;
233 std::size_t nodeStructureVersion_ = 0;
235 std::size_t topologyVersion_ = 0;
237 std::size_t properPartVersion_ = 0;
239 std::size_t mutationVersion_ = 0;
242 enum class ChildSplicePolicy { AppendToTargetTail, ReplaceSourceSlotWhenDirectChild };
250 inline NodeId allocateSlot() {
251 if (!freeNodeIds_.empty()) {
252 const NodeId slotId = freeNodeIds_.back();
253 freeNodeIds_.pop_back();
260 numChildrenByNode_[slotId] = 0;
264 properPartCardinalityByNode_[slotId] = 0;
268 const NodeId slotId =
static_cast<NodeId>(nodeParent_.size());
269 nodeParent_.push_back(InvalidNode);
270 firstChild_.push_back(InvalidNode);
271 nextSibling_.push_back(InvalidNode);
272 prevSibling_.push_back(InvalidNode);
273 lastChild_.push_back(InvalidNode);
274 numChildrenByNode_.push_back(0);
276 properHead_.push_back(InvalidPixel);
277 properTail_.push_back(InvalidPixel);
278 properPartCardinalityByNode_.push_back(0);
287 inline void initializeProperPartStorage(
size_t numPixels) {
288 nextProperPart_.assign(
numPixels, InvalidPixel);
289 prevProperPart_.assign(
numPixels, InvalidPixel);
295 inline void invalidateHigraNodeIdSpace() noexcept { preservedExternalNodeIdOffset_.reset(); }
306 inline void preserveExternalNodeIdOffset(NodeId internalNodeOffset) {
307 if (internalNodeOffset < 0) {
308 throw std::invalid_argument(
"An external internal-node offset must be non-negative.");
310 preservedExternalNodeIdOffset_ = internalNodeOffset;
316 inline void beginEditSession() {
317 if (editSessionOpen_) {
318 throw std::logic_error(
"A MorphologicalTree edit session is already open.");
320 editSessionOpen_ =
true;
326 inline void endEditSession() noexcept { editSessionOpen_ =
false; }
333 inline void recordEditCommit(TreeEditValidationMode mode)
noexcept {
334 if (mode == TreeEditValidationMode::Incremental) {
346 inline void initializeEmptyStorage(
size_t numPixels) {
349 smallestNodeMap_.assign(
numPixels, InvalidNode);
353 nextSibling_.clear();
354 prevSibling_.clear();
356 numChildrenByNode_.clear();
358 freeNodeIds_.clear();
361 properPartCardinalityByNode_.clear();
363 invalidateHigraNodeIdSpace();
365 dfsIntervalCache_.entryIndex.clear();
366 dfsIntervalCache_.exitIndex.clear();
367 invalidateDfsIntervalCache();
368 nodeSupportMetadataCache_.cardinalityByNode.clear();
369 nodeSupportMetadataCache_.smallestPixelByNode.clear();
370 nodeSupportMetadataCache_.invalidate();
371 invalidateAllIterators();
379 inline void releaseSlotStorage(NodeId slotId) {
383 freeNodeIds_.push_back(slotId);
389 numChildrenByNode_[slotId] = 0;
393 properPartCardinalityByNode_[slotId] = 0;
400 void reserveFreeNodeSlots(std::size_t additionalSlots) {
401 const auto maximum = freeNodeIds_.max_size();
402 if (additionalSlots > maximum - freeNodeIds_.size()) {
403 throw std::length_error(
"MorphologicalTree free-node storage exceeded its size bound.");
405 const auto required = freeNodeIds_.size() + additionalSlots;
406 const auto capacity = freeNodeIds_.capacity();
407 if (required > capacity) {
408 const auto grown = capacity > maximum / 2 ? maximum : capacity * 2;
409 freeNodeIds_.reserve(std::max(required, grown));
419 inline bool isFreeSlot(NodeId slotId)
const noexcept {
return slotId >= 0 && slotId < static_cast<NodeId>(alive_.size()) && alive_[slotId] == 0; }
427 inline void requireAliveNode(NodeId nodeId,
const char* context)
const {
437 inline void requireAliveNonRootNode(NodeId nodeId,
const char* context)
const {
438 requireAliveNode(nodeId, context);
445 inline void rebuildProperPartLinksFromSmallestNodeMap() {
446 properHead_.assign(nodeParent_.size(), InvalidPixel);
447 properTail_.assign(nodeParent_.size(), InvalidPixel);
448 properPartCardinalityByNode_.assign(nodeParent_.size(), 0);
449 initializeProperPartStorage(smallestNodeMap_.size());
451 for (PixelId pixel = 0; pixel < static_cast<PixelId>(smallestNodeMap_.size()); ++pixel) {
452 const NodeId smallestNodeSlotId = smallestNodeMap_[
static_cast<std::size_t
>(pixel)];
453 if (smallestNodeSlotId == InvalidNode || smallestNodeSlotId >=
static_cast<NodeId>(nodeParent_.size()) || isFreeSlot(smallestNodeSlotId)) {
457 const PixelId tailProperPartId = properTail_[smallestNodeSlotId];
458 if (tailProperPartId == InvalidPixel) {
459 properHead_[smallestNodeSlotId] = pixel;
460 properTail_[smallestNodeSlotId] = pixel;
462 nextProperPart_[tailProperPartId] = pixel;
463 prevProperPart_[pixel] = tailProperPartId;
464 properTail_[smallestNodeSlotId] = pixel;
466 properPartCardinalityByNode_[smallestNodeSlotId]++;
476 inline void unlinkPixelFromProperPart(NodeId smallestNodeSlotId, PixelId pixel)
noexcept {
477 const PixelId prev = prevProperPart_[
static_cast<size_t>(pixel)];
478 const PixelId next = nextProperPart_[
static_cast<size_t>(pixel)];
480 if (prev == InvalidPixel) {
481 properHead_[
static_cast<size_t>(smallestNodeSlotId)] = next;
483 nextProperPart_[
static_cast<size_t>(prev)] = next;
486 if (next == InvalidPixel) {
487 properTail_[
static_cast<size_t>(smallestNodeSlotId)] = prev;
489 prevProperPart_[
static_cast<size_t>(next)] = prev;
492 nextProperPart_[
static_cast<size_t>(pixel)] = InvalidPixel;
493 prevProperPart_[
static_cast<size_t>(pixel)] = InvalidPixel;
494 --properPartCardinalityByNode_[
static_cast<size_t>(smallestNodeSlotId)];
503 inline void appendDetachedProperPart(NodeId targetSlotId, PixelId pixel)
noexcept {
504 smallestNodeMap_[
static_cast<size_t>(pixel)] = targetSlotId;
505 const PixelId tail = properTail_[
static_cast<size_t>(targetSlotId)];
506 if (tail == InvalidPixel) {
507 properHead_[
static_cast<size_t>(targetSlotId)] = pixel;
508 properTail_[
static_cast<size_t>(targetSlotId)] = pixel;
510 nextProperPart_[
static_cast<size_t>(tail)] = pixel;
511 prevProperPart_[
static_cast<size_t>(pixel)] = tail;
512 properTail_[
static_cast<size_t>(targetSlotId)] = pixel;
514 ++properPartCardinalityByNode_[
static_cast<size_t>(targetSlotId)];
523 inline void spliceProperPartsSlots(NodeId targetSlotId, NodeId sourceSlotId)
noexcept {
524 const PixelId sourceHead = properHead_[
static_cast<size_t>(sourceSlotId)];
525 if (sourceHead == InvalidPixel) {
529 for (PixelId pixel = sourceHead; pixel !=
InvalidPixel; pixel = nextProperPart_[
static_cast<size_t>(pixel)]) {
530 smallestNodeMap_[
static_cast<size_t>(pixel)] = targetSlotId;
533 const PixelId sourceTail = properTail_[
static_cast<size_t>(sourceSlotId)];
534 const int sourceCount = properPartCardinalityByNode_[
static_cast<size_t>(sourceSlotId)];
535 const PixelId targetTail = properTail_[
static_cast<size_t>(targetSlotId)];
537 if (targetTail == InvalidPixel) {
538 properHead_[
static_cast<size_t>(targetSlotId)] = sourceHead;
539 properTail_[
static_cast<size_t>(targetSlotId)] = sourceTail;
541 nextProperPart_[
static_cast<size_t>(targetTail)] = sourceHead;
542 prevProperPart_[
static_cast<size_t>(sourceHead)] = targetTail;
543 properTail_[
static_cast<size_t>(targetSlotId)] = sourceTail;
546 properPartCardinalityByNode_[
static_cast<size_t>(targetSlotId)] += sourceCount;
547 properHead_[
static_cast<size_t>(sourceSlotId)] = InvalidPixel;
548 properTail_[
static_cast<size_t>(sourceSlotId)] = InvalidPixel;
549 properPartCardinalityByNode_[
static_cast<size_t>(sourceSlotId)] = 0;
557 inline void releaseSlotNode(NodeId slotNodeId) {
558 releaseSlotStorage(slotNodeId);
560 invalidateDfsIntervalCache();
561 bumpNodeStructureVersion();
567 inline void invalidateAllIterators() noexcept {
568 nodeStructureVersion_ = 0;
569 topologyVersion_ = 0;
570 properPartVersion_ = 0;
573 nodeSupportMetadataCache_.invalidate();
579 inline void invalidateLcaCache() const noexcept { lcaCache_.reset(); }
584 inline void bumpNodeStructureVersion() noexcept {
585 ++nodeStructureVersion_;
587 invalidateLcaCache();
588 invalidateHigraNodeIdSpace();
594 inline void bumpTopologyVersion() noexcept {
597 invalidateLcaCache();
598 invalidateDfsIntervalCache();
599 invalidateHigraNodeIdSpace();
605 inline void bumpProperPartVersion() noexcept {
606 ++properPartVersion_;
608 invalidateHigraNodeIdSpace();
616 inline void checkNodeIteratorVersion([[maybe_unused]] std::size_t expectedVersion)
const {
617 assert(expectedVersion == nodeStructureVersion_ &&
"Alive-node iterator invalidated by node-structure mutation.");
625 inline void checkTopologyIteratorVersion([[maybe_unused]] std::size_t expectedVersion)
const {
626 assert(expectedVersion == topologyVersion_ &&
"Topology iterator invalidated by tree-structure mutation.");
634 inline void checkProperPartIteratorVersion([[maybe_unused]] std::size_t expectedVersion)
const {
635 assert(expectedVersion == properPartVersion_ &&
"Proper-parts iterator invalidated by proper-part mutation.");
641 inline void invalidateDfsIntervalCache() const noexcept { dfsIntervalCache_.invalidate(); }
646 inline void recomputeDfsIntervalCache()
const {
647 dfsIntervalCache_.entryIndex.assign(nodeParent_.size(), -1);
648 dfsIntervalCache_.exitIndex.assign(nodeParent_.size(), -1);
650 if (rootNodeId_ == InvalidNode) {
651 dfsIntervalCache_.valid =
true;
656 int nextDfsEventIndex = 0;
657 std::vector<std::pair<NodeId, NodeId>> stack;
658 stack.emplace_back(rootNodeId_, firstChild_[rootNodeId_]);
659 dfsIntervalCache_.entryIndex[rootNodeId_] = nextDfsEventIndex++;
660 while (!stack.empty()) {
661 auto& [node, nextChild] = stack.back();
662 if (nextChild == InvalidNode) {
663 dfsIntervalCache_.exitIndex[node] = nextDfsEventIndex++;
666 const NodeId child = nextChild;
667 nextChild = nextSibling_[child];
668 dfsIntervalCache_.entryIndex[child] = nextDfsEventIndex++;
669 stack.emplace_back(child, firstChild_[child]);
673 dfsIntervalCache_.valid =
true;
679 inline void ensureDfsIntervalCache()
const {
680 if (!dfsIntervalCache_.valid) {
681 recomputeDfsIntervalCache();
690 inline const LCAEulerRMQ& ensureLcaCache()
const {
692 lcaCache_ = std::make_unique<LCAEulerRMQ>(
this);
704 [[nodiscard]]
inline std::optional<NodeId> lowestCommonAncestorFromDfsIntervalsEstablished(NodeId u, NodeId v)
const {
708 ensureDfsIntervalCache();
709 if (dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(u)] < 0 ||
710 dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(v)] < 0) {
713 const auto isAncestorCached = [&](
NodeId ancestor,
NodeId node) {
714 return dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(ancestor)] <=
715 dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(node)] &&
716 dfsIntervalCache_.exitIndex[
static_cast<std::size_t
>(ancestor)] >=
717 dfsIntervalCache_.exitIndex[
static_cast<std::size_t
>(node)];
719 if (isAncestorCached(u, v)) {
722 if (isAncestorCached(v, u)) {
734 [[nodiscard]]
inline NodeId lowestCommonAncestorEstablished(NodeId u, NodeId v)
const {
735 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(u, v);
736 return intervalResult.has_value() ? *intervalResult : ensureLcaCache().findLowestCommonAncestor(u, v);
743 [[nodiscard]]
inline long double estimatedLcaRmqStorageBytes() const noexcept {
744 std::size_t rmqLevels = 1;
745 const std::size_t eulerLength = numNodes_ > 0 ?
static_cast<std::size_t
>(numNodes_) * 2 - 1 : 0;
746 for (std::size_t blockSize = 2; blockSize <= eulerLength && blockSize <= std::numeric_limits<std::size_t>::max() / 2;
750 return static_cast<long double>(nodeParent_.size()) *
static_cast<long double>(36 + 8 * rmqLevels);
765 template <
typename QueryAccessor,
typename ResultConsumer>
766 inline void forEachLowestCommonAncestorEstablished(std::size_t numQueries, QueryAccessor&& queryForIndex, ResultConsumer&& consumeLca,
767 bool forceTarjan =
false)
const {
768 if (numQueries == 0) {
772 if (lcaCache_ && !forceTarjan) {
773 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
774 const auto [first, second] = queryForIndex(queryIndex);
775 consumeLca(queryIndex, lowestCommonAncestorEstablished(first, second));
780 const std::size_t numSlots = nodeParent_.size();
781 std::size_t numUnresolvedQueries = numQueries;
783 numUnresolvedQueries = 0;
784 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
785 const auto [first, second] = queryForIndex(queryIndex);
786 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
787 if (intervalResult.has_value()) {
788 consumeLca(queryIndex, *intervalResult);
791 ++numUnresolvedQueries;
794 if (numUnresolvedQueries == 0 || rootNodeId_ == InvalidNode) {
798 const long double estimatedRmqBytes = estimatedLcaRmqStorageBytes();
799 const long double estimatedTarjanBytes =
800 static_cast<long double>(numSlots) * 30.0L +
static_cast<long double>(numUnresolvedQueries) * 16.0L;
801 const bool batchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max() / 2);
802 if (!forceTarjan && (!batchFitsCompactIndices || estimatedRmqBytes <= estimatedTarjanBytes)) {
803 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
804 const auto [first, second] = queryForIndex(queryIndex);
805 if (lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
808 consumeLca(queryIndex, ensureLcaCache().findLowestCommonAncestor(first, second));
813 using QueryOffset = std::uint32_t;
814 std::vector<QueryOffset> queryOffsets(numSlots + 1, QueryOffset{0});
815 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
816 const auto [first, second] = queryForIndex(queryIndex);
817 if (!forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
820 ++queryOffsets[
static_cast<std::size_t
>(first) + 1];
821 ++queryOffsets[
static_cast<std::size_t
>(second) + 1];
823 for (std::size_t index = 1; index < queryOffsets.size(); ++index) {
824 queryOffsets[index] += queryOffsets[index - 1];
827 struct QueryReference {
829 std::uint32_t queryIndex = 0;
831 std::vector<QueryReference> queryReferences(queryOffsets.back());
832 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
833 const auto [first, second] = queryForIndex(queryIndex);
834 if (!forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
837 const QueryOffset firstPosition = queryOffsets[
static_cast<std::size_t
>(first)]++;
838 queryReferences[firstPosition] = {second,
static_cast<std::uint32_t
>(queryIndex)};
839 const QueryOffset secondPosition = queryOffsets[
static_cast<std::size_t
>(second)]++;
840 queryReferences[secondPosition] = {first,
static_cast<std::uint32_t
>(queryIndex)};
842 for (std::size_t index = numSlots; index > 0; --index) {
843 queryOffsets[index] = queryOffsets[index - 1];
845 queryOffsets[0] = QueryOffset{0};
847 const auto findSet = [](std::vector<NodeId>& setParents,
NodeId node) {
848 NodeId representative = node;
849 while (setParents[
static_cast<std::size_t
>(representative)] != representative) {
850 representative = setParents[
static_cast<std::size_t
>(representative)];
852 while (setParents[
static_cast<std::size_t
>(node)] != node) {
853 const NodeId next = setParents[
static_cast<std::size_t
>(node)];
854 setParents[
static_cast<std::size_t
>(node)] = representative;
857 return representative;
859 const auto uniteSets = [&](std::vector<NodeId>& setParents, std::vector<std::uint8_t>& setRanks,
NodeId first,
NodeId second) {
860 NodeId firstRoot = findSet(setParents, first);
861 NodeId secondRoot = findSet(setParents, second);
862 if (firstRoot == secondRoot) {
865 if (setRanks[
static_cast<std::size_t
>(firstRoot)] < setRanks[
static_cast<std::size_t
>(secondRoot)]) {
866 std::swap(firstRoot, secondRoot);
868 setParents[
static_cast<std::size_t
>(secondRoot)] = firstRoot;
869 if (setRanks[
static_cast<std::size_t
>(firstRoot)] == setRanks[
static_cast<std::size_t
>(secondRoot)]) {
870 ++setRanks[
static_cast<std::size_t
>(firstRoot)];
875 struct TraversalFrame {
880 std::vector<NodeId> setParents(numSlots, InvalidNode);
881 std::vector<std::uint8_t> setRanks(numSlots, std::uint8_t{0});
882 std::vector<NodeId>
ancestors(numSlots, InvalidNode);
883 std::vector<std::uint8_t> finishedNodes(numSlots, std::uint8_t{0});
884 std::vector<TraversalFrame> traversalStack;
885 traversalStack.reserve(
static_cast<std::size_t
>(numNodes_));
886 setParents[
static_cast<std::size_t
>(rootNodeId_)] = rootNodeId_;
887 ancestors[
static_cast<std::size_t
>(rootNodeId_)] = rootNodeId_;
888 traversalStack.push_back({rootNodeId_, firstChild_[
static_cast<std::size_t
>(rootNodeId_)]});
890 while (!traversalStack.empty()) {
891 TraversalFrame& frame = traversalStack.back();
892 if (frame.nextChild != InvalidNode) {
893 const NodeId child = frame.nextChild;
894 frame.nextChild = nextSibling_[
static_cast<std::size_t
>(child)];
895 setParents[
static_cast<std::size_t
>(child)] = child;
896 ancestors[
static_cast<std::size_t
>(child)] = child;
897 traversalStack.push_back({child, firstChild_[
static_cast<std::size_t
>(child)]});
901 const NodeId node = frame.node;
902 const std::size_t nodeIndex =
static_cast<std::size_t
>(node);
903 finishedNodes[nodeIndex] = std::uint8_t{1};
904 for (QueryOffset referenceIndex = queryOffsets[nodeIndex]; referenceIndex < queryOffsets[nodeIndex + 1]; ++referenceIndex) {
905 const QueryReference query = queryReferences[referenceIndex];
906 if (finishedNodes[
static_cast<std::size_t
>(query.otherEndpoint)] != 0) {
907 const NodeId representative = findSet(setParents, query.otherEndpoint);
908 consumeLca(query.queryIndex,
ancestors[
static_cast<std::size_t
>(representative)]);
912 traversalStack.pop_back();
913 if (!traversalStack.empty()) {
915 const NodeId representative = uniteSets(setParents, setRanks,
parent, node);
926 [[nodiscard]]
inline std::vector<NodeId>
927 lowestCommonAncestorsEstablished(std::span<
const std::pair<NodeId, NodeId>> queries)
const {
928 std::vector<NodeId> lcas(queries.size(), InvalidNode);
929 forEachLowestCommonAncestorEstablished(
930 queries.size(), [&](std::size_t queryIndex) { return queries[queryIndex]; },
931 [&](std::size_t queryIndex, NodeId lca) { lcas[queryIndex] = lca; });
946 template <
typename QueryAccessor,
typename ResultConsumer>
947 inline void forEachGeneratedLowestCommonAncestorEstablished(std::size_t numQueries, QueryAccessor&& queryForIndex,
948 ResultConsumer&& consumeLca)
const {
949 if (numQueries == 0) {
953 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
954 const auto [first, second] = queryForIndex(queryIndex);
955 consumeLca(queryIndex, lowestCommonAncestorEstablished(first, second));
960 const bool fullBatchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max() / 2);
961 const long double estimatedFullBatchTarjanBytes =
962 static_cast<long double>(nodeParent_.size()) * 30.0L +
static_cast<long double>(numQueries) * 24.0L;
963 if (fullBatchFitsCompactIndices && estimatedLcaRmqStorageBytes() > estimatedFullBatchTarjanBytes) {
964 constexpr std::size_t MaxStrategyProbes = 1024;
965 const std::size_t numProbes = std::min(numQueries, MaxStrategyProbes);
966 bool foundIncomparablePair =
false;
967 for (std::size_t probe = 0; probe < numProbes; ++probe) {
968 const std::size_t queryIndex = probe * numQueries / numProbes;
969 const auto [first, second] = queryForIndex(queryIndex);
970 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
971 foundIncomparablePair =
true;
975 if (foundIncomparablePair) {
976 std::vector<std::pair<NodeId, NodeId>> allPairs(numQueries);
977 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
978 allPairs[queryIndex] = queryForIndex(queryIndex);
980 forEachLowestCommonAncestorEstablished(
981 numQueries, [&](std::size_t queryIndex) {
return allPairs[queryIndex]; }, std::forward<ResultConsumer>(consumeLca),
true);
986 std::size_t numUnresolvedQueries = 0;
987 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
988 const auto [first, second] = queryForIndex(queryIndex);
989 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
990 if (intervalResult.has_value()) {
991 consumeLca(queryIndex, *intervalResult);
993 ++numUnresolvedQueries;
996 if (numUnresolvedQueries == 0) {
1000 const bool filteredBatchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()) &&
1001 numUnresolvedQueries <=
1002 static_cast<std::size_t
>(std::numeric_limits<std::uint32_t>::max() / 2);
1003 const long double estimatedFilteredTarjanBytes =
1004 static_cast<long double>(nodeParent_.size()) * 30.0L +
static_cast<long double>(numUnresolvedQueries) * 28.0L;
1005 if (!filteredBatchFitsCompactIndices || estimatedLcaRmqStorageBytes() <= estimatedFilteredTarjanBytes) {
1006 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
1007 const auto [first, second] = queryForIndex(queryIndex);
1008 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
1009 consumeLca(queryIndex, ensureLcaCache().findLowestCommonAncestor(first, second));
1015 std::vector<std::pair<NodeId, NodeId>> unresolvedPairs;
1016 std::vector<std::uint32_t> unresolvedQueryIndices;
1017 unresolvedPairs.reserve(numUnresolvedQueries);
1018 unresolvedQueryIndices.reserve(numUnresolvedQueries);
1019 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
1020 const auto query = queryForIndex(queryIndex);
1021 if (!lowestCommonAncestorFromDfsIntervalsEstablished(query.first, query.second).has_value()) {
1022 unresolvedPairs.push_back(query);
1023 unresolvedQueryIndices.push_back(
static_cast<std::uint32_t
>(queryIndex));
1026 forEachLowestCommonAncestorEstablished(
1027 unresolvedPairs.size(), [&](std::size_t queryIndex) { return unresolvedPairs[queryIndex]; },
1028 [&](std::size_t queryIndex, NodeId lca) { consumeLca(unresolvedQueryIndices[queryIndex], lca); },
true);
1039 void linkChildSlot(NodeId parentSlotId, NodeId childId) {
1040 if (parentSlotId < 0 || childId < 0)
1044 if (nodeParent_[childId] != InvalidNode) {
1045 unlinkChildSlot(nodeParent_[childId], childId);
1048 nodeParent_[childId] = parentSlotId;
1049 prevSibling_[childId] = lastChild_[parentSlotId];
1052 if (firstChild_[parentSlotId] == InvalidNode) {
1053 firstChild_[parentSlotId] = lastChild_[parentSlotId] = childId;
1055 nextSibling_[lastChild_[parentSlotId]] = childId;
1056 lastChild_[parentSlotId] = childId;
1058 ++numChildrenByNode_[parentSlotId];
1059 bumpTopologyVersion();
1068 inline void unlinkChildSlot(NodeId parentSlotId, NodeId childId) {
1069 if (parentSlotId < 0 || childId < 0)
1071 if (nodeParent_[childId] != parentSlotId)
1074 const NodeId prev = prevSibling_[childId];
1075 const NodeId next = nextSibling_[childId];
1077 if (prev == InvalidNode)
1078 firstChild_[parentSlotId] = next;
1080 nextSibling_[prev] = next;
1082 if (next == InvalidNode)
1083 lastChild_[parentSlotId] = prev;
1085 prevSibling_[next] = prev;
1087 if (numChildrenByNode_[parentSlotId] > 0)
1088 --numChildrenByNode_[parentSlotId];
1093 bumpTopologyVersion();
1103 inline void spliceChildrenSlots(NodeId toId, NodeId fromId, ChildSplicePolicy policy = ChildSplicePolicy::AppendToTargetTail) {
1104 if (toId < 0 || fromId < 0 || toId == fromId)
1107 const NodeId firstFrom = firstChild_[fromId];
1108 const NodeId lastFrom = lastChild_[fromId];
1109 const int movedCount = numChildrenByNode_[fromId];
1110 const bool replaceSourceSlot = policy == ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild && nodeParent_[fromId] == toId;
1112 for (NodeId childId = firstFrom; childId !=
InvalidNode; childId = nextSibling_[childId]) {
1113 nodeParent_[childId] = toId;
1116 if (replaceSourceSlot) {
1117 const NodeId prev = prevSibling_[fromId];
1118 const NodeId next = nextSibling_[fromId];
1120 if (firstFrom == InvalidNode) {
1121 if (prev == InvalidNode) {
1122 firstChild_[toId] = next;
1124 nextSibling_[prev] = next;
1127 if (next == InvalidNode) {
1128 lastChild_[toId] = prev;
1130 prevSibling_[next] = prev;
1133 if (prev == InvalidNode) {
1134 firstChild_[toId] = firstFrom;
1137 nextSibling_[prev] = firstFrom;
1138 prevSibling_[firstFrom] = prev;
1141 if (next == InvalidNode) {
1142 lastChild_[toId] = lastFrom;
1145 prevSibling_[next] = lastFrom;
1146 nextSibling_[lastFrom] = next;
1153 numChildrenByNode_[toId] += movedCount - 1;
1155 if (firstFrom == InvalidNode)
1158 if (firstChild_[toId] == InvalidNode) {
1159 firstChild_[toId] = firstFrom;
1160 lastChild_[toId] = lastFrom;
1162 nextSibling_[lastChild_[toId]] = firstFrom;
1163 prevSibling_[firstFrom] = lastChild_[toId];
1164 lastChild_[toId] = lastFrom;
1167 numChildrenByNode_[toId] += movedCount;
1172 numChildrenByNode_[fromId] = 0;
1173 bumpTopologyVersion();
1181 inline void releaseNode(NodeId nodeId) {
1185 const NodeId nodeSlot = nodeId;
1186 if (nodeParent_[nodeSlot] != nodeSlot) {
1189 if (numChildrenByNode_[nodeSlot] != 0 || properPartCardinalityByNode_[nodeSlot] != 0) {
1192 releaseSlotNode(nodeSlot);
1200 inline NodeId allocateNode() {
1201 if (freeNodeIds_.empty()) {
1204 const NodeId nodeSlot = allocateSlot();
1205 nodeParent_[nodeSlot] = nodeSlot;
1207 invalidateDfsIntervalCache();
1208 bumpNodeStructureVersion();
1221 inline NodeId createDetachedNode() {
1222 const NodeId nodeSlot = allocateSlot();
1223 nodeParent_[
static_cast<size_t>(nodeSlot)] = nodeSlot;
1225 invalidateDfsIntervalCache();
1226 bumpNodeStructureVersion();
1237 inline void removeChild(NodeId parentNodeId, NodeId childId,
bool releaseNodeFlag) {
1238 if (!
isAlive(parentNodeId) || !
isAlive(childId) || nodeParent_[
static_cast<std::size_t
>(childId)] != parentNodeId) {
1241 const NodeId parentSlotId = parentNodeId;
1242 const NodeId childSlotId = childId;
1243 if (releaseNodeFlag && numChildrenByNode_[
static_cast<std::size_t
>(childSlotId)] == 0 &&
1244 properPartCardinalityByNode_[
static_cast<std::size_t
>(childSlotId)] == 0) {
1245 reserveFreeNodeSlots(1);
1247 unlinkChildSlot(parentSlotId, childSlotId);
1248 nodeParent_[childSlotId] = childSlotId;
1249 invalidateDfsIntervalCache();
1250 if (releaseNodeFlag) {
1251 releaseNode(childId);
1261 inline void attachNode(NodeId parentNodeId, NodeId nodeId) {
1265 const NodeId parentSlotId = parentNodeId;
1266 const NodeId nodeSlotId = nodeId;
1267 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
1268 if (oldParentSlotId != InvalidNode && oldParentSlotId != nodeSlotId) {
1269 unlinkChildSlot(oldParentSlotId, nodeSlotId);
1272 linkChildSlot(parentSlotId, nodeSlotId);
1280 inline void detachNode(NodeId nodeId) {
1284 const NodeId nodeSlotId = nodeId;
1285 const NodeId parentSlotId = nodeParent_[nodeSlotId];
1286 if (parentSlotId == InvalidNode || parentSlotId == nodeSlotId) {
1289 unlinkChildSlot(parentSlotId, nodeSlotId);
1290 nodeParent_[nodeSlotId] = nodeSlotId;
1299 inline void moveNode(NodeId nodeId, NodeId newParentId) {
1303 const NodeId nodeSlotId = nodeId;
1304 const NodeId newParentSlotId = newParentId;
1305 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
1306 if (oldParentSlotId == newParentSlotId) {
1309 if (oldParentSlotId != InvalidNode && oldParentSlotId != nodeSlotId) {
1310 unlinkChildSlot(oldParentSlotId, nodeSlotId);
1313 linkChildSlot(newParentSlotId, nodeSlotId);
1322 inline void moveChildren(NodeId parentNodeId, NodeId sourceId) {
1323 if (!
isAlive(parentNodeId) || !
isAlive(sourceId) || parentNodeId == sourceId) {
1326 spliceChildrenSlots(parentNodeId, sourceId);
1336 inline void movePixelToProperPart(NodeId targetNodeId, NodeId sourceNodeId, PixelId pixel) {
1337 if (!
isAlive(targetNodeId) || !
isAlive(sourceNodeId) || !
isPixel(pixel) || targetNodeId == sourceNodeId) {
1340 const NodeId sourceSlotId = sourceNodeId;
1341 if (smallestNodeMap_[pixel] != sourceSlotId) {
1344 unlinkPixelFromProperPart(sourceSlotId, pixel);
1345 appendDetachedProperPart(targetNodeId, pixel);
1346 bumpProperPartVersion();
1355 inline void mergeProperParts(NodeId targetNodeId, NodeId sourceNodeId) {
1356 if (!
isAlive(targetNodeId) || !
isAlive(sourceNodeId) || targetNodeId == sourceNodeId) {
1359 if (properHead_[
static_cast<size_t>(sourceNodeId)] == InvalidPixel) {
1362 spliceProperPartsSlots(targetNodeId, sourceNodeId);
1363 bumpProperPartVersion();
1371 inline void setRoot(NodeId nodeId) {
1375 const NodeId nodeSlot = nodeId;
1376 if (rootNodeId_ == nodeSlot) {
1379 if (rootNodeId_ != InvalidNode && !isFreeSlot(rootNodeId_)) {
1380 nodeParent_[rootNodeId_] = rootNodeId_;
1384 const NodeId oldParentSlot = nodeParent_[nodeSlot];
1385 if (oldParentSlot != InvalidNode && oldParentSlot != nodeSlot) {
1386 unlinkChildSlot(oldParentSlot, nodeSlot);
1388 rootNodeId_ = nodeSlot;
1389 nodeParent_[nodeSlot] = nodeSlot;
1392 bumpTopologyVersion();
1408 void initializeNativeTopologyStorage(std::vector<NodeId> nodeParent, std::vector<NodeId>
smallestNodeMap, NodeId
root,
1411 throw std::invalid_argument(
"Native topology pixel domain must match the attached 2D grid.");
1413 if (nodeParent.size() >
static_cast<std::size_t
>(std::numeric_limits<NodeId>::max())) {
1414 throw std::invalid_argument(
"Native topology internal-node domain exceeds NodeId range.");
1416 if (
smallestNodeMap.size() >
static_cast<std::size_t
>(std::numeric_limits<PixelId>::max())) {
1417 throw std::invalid_argument(
"Native topology pixel domain exceeds PixelId range.");
1420 case MorphologicalTreeKind::Generic:
1421 case MorphologicalTreeKind::MaxTree:
1422 case MorphologicalTreeKind::MinTree:
1423 case MorphologicalTreeKind::TreeOfShapes:
1424 case MorphologicalTreeKind::ExtremumResidualTree:
1427 throw std::invalid_argument(
"Native topology kind is not supported.");
1429 const int numNodeSlots =
static_cast<int>(nodeParent.size());
1431 if (numNodeSlots <= 0) {
1432 throw std::invalid_argument(
"Native topology import requires at least one internal node.");
1435 throw std::invalid_argument(
"Native topology import requires at least one pixel.");
1437 if (root < 0 || root >= numNodeSlots) {
1438 throw std::invalid_argument(
"Native topology import requires a valid root node id.");
1443 const auto requireMatchingGrid = [
this](
const RegularGridAdjacency2D& adjacency,
const char* context) {
1444 if (!gridDomain2D_) {
1445 throw std::invalid_argument(std::string(context) +
" requires an attached 2D grid domain.");
1447 if (adjacency.getNumRows() != gridDomain2D_->rows || adjacency.getNumColumns() != gridDomain2D_->columns) {
1448 throw std::invalid_argument(std::string(context) +
" must match the attached 2D grid.");
1451 if (
const auto* context = std::get_if<SharedAdjacencyContext>(&semantics_.
constructionContext)) {
1452 requireMatchingGrid(context->adjacency,
"SharedAdjacencyContext adjacency");
1453 }
else if (
const auto* convention = std::get_if<TopographicConvention>(&semantics_.
constructionContext)) {
1454 if (convention->infinityPixel < 0) {
1455 throw std::invalid_argument(
"TopographicConvention infinity pixel must be non-negative.");
1457 if (!gridDomain2D_) {
1458 throw std::invalid_argument(
"TopographicConvention requires an attached 2D grid domain.");
1460 const std::int64_t extension = convention->domainExtension == TopographicDomainExtension::ExteriorRing ? 1 : -1;
1461 const std::int64_t activeRows = 2 *
static_cast<std::int64_t
>(gridDomain2D_->rows) + extension;
1462 const std::int64_t activeColumns = 2 *
static_cast<std::int64_t
>(gridDomain2D_->columns) + extension;
1463 if (activeRows <= 0 || activeColumns <= 0 ||
static_cast<std::int64_t
>(convention->infinityPixel) >= activeRows * activeColumns) {
1464 throw std::invalid_argument(
"TopographicConvention infinity pixel must belong to the active topographic domain.");
1466 if (
const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention->immersion)) {
1467 requireMatchingGrid(immersion->complementaryAdjacencies.minAdjacency,
"Topographic minimum adjacency");
1468 requireMatchingGrid(immersion->complementaryAdjacencies.maxAdjacency,
"Topographic maximum adjacency");
1473 nodeParent_ = std::move(nodeParent);
1474 firstChild_.assign(
static_cast<size_t>(numNodeSlots), InvalidNode);
1475 nextSibling_.assign(
static_cast<size_t>(numNodeSlots), InvalidNode);
1476 prevSibling_.assign(
static_cast<size_t>(numNodeSlots), InvalidNode);
1477 lastChild_.assign(
static_cast<size_t>(numNodeSlots), InvalidNode);
1478 numChildrenByNode_.assign(
static_cast<size_t>(numNodeSlots), 0);
1479 alive_.assign(
static_cast<size_t>(numNodeSlots), 1);
1480 freeNodeIds_.clear();
1481 properHead_.assign(
static_cast<size_t>(numNodeSlots), InvalidPixel);
1482 properTail_.assign(
static_cast<size_t>(numNodeSlots), InvalidPixel);
1483 properPartCardinalityByNode_.assign(
static_cast<size_t>(numNodeSlots), 0);
1488 numNodes_ =
static_cast<int>(numNodeSlots);
1490 int selfParentedRoots = 0;
1491 for (NodeId nodeId = 0; nodeId < numNodeSlots; ++nodeId) {
1492 const NodeId parentId = nodeParent_[
static_cast<size_t>(nodeId)];
1493 if (parentId == nodeId) {
1494 if (nodeId !=
root) {
1495 throw std::invalid_argument(
"Native topology import found a detached self-parented non-root node.");
1497 ++selfParentedRoots;
1500 if (parentId < 0 || parentId >= numNodeSlots) {
1501 throw std::invalid_argument(
"Native topology import found a parent outside the internal-node domain.");
1504 prevSibling_[
static_cast<size_t>(nodeId)] = lastChild_[
static_cast<size_t>(parentId)];
1505 if (lastChild_[
static_cast<size_t>(parentId)] ==
InvalidNode) {
1506 firstChild_[
static_cast<size_t>(parentId)] = nodeId;
1508 nextSibling_[
static_cast<size_t>(lastChild_[
static_cast<size_t>(parentId)])] = nodeId;
1510 lastChild_[
static_cast<size_t>(parentId)] = nodeId;
1511 ++numChildrenByNode_[
static_cast<size_t>(parentId)];
1513 if (selfParentedRoots != 1) {
1514 throw std::invalid_argument(
"Native topology import must encode exactly one self-parented root.");
1517 rebuildProperPartLinksFromSmallestNodeMap();
1518 invalidateDfsIntervalCache();
1519 invalidateAllIterators();
1520 preservedExternalNodeIdOffset_.reset();
1532 void initializeNativeTopology(std::span<const NodeId> nodeParent, std::span<const NodeId>
smallestNodeMap, NodeId
root,
1534 if (nodeParent.size() >
static_cast<std::size_t
>(std::numeric_limits<NodeId>::max())) {
1535 throw std::invalid_argument(
"Native topology internal-node domain exceeds NodeId range.");
1537 const int numNodeSlots =
static_cast<int>(nodeParent.size());
1539 if (smallestNodeId < 0 || smallestNodeId >= numNodeSlots) {
1540 throw std::invalid_argument(
"Native topology import found a smallest-node-map entry outside the internal-node domain.");
1543 initializeNativeTopologyStorage(std::vector<NodeId>(nodeParent.begin(), nodeParent.end()),
1560 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::vector<NodeId>&& nodeParent, std::vector<NodeId>&&
smallestNodeMap, NodeId
root,
1568 MorphologicalTree() =
default;
1578 void moveCommittedStateFrom(MorphologicalTree&& other) {
1579 rootNodeId_ = std::exchange(other.rootNodeId_, InvalidNode);
1580 semantics_ = std::move(other.semantics_);
1581 other.semantics_ = MorphologicalTreeSemantics{};
1582 gridDomain2D_ = std::move(other.gridDomain2D_);
1583 other.gridDomain2D_.reset();
1584 numNodes_ = std::exchange(other.numNodes_, 0);
1585 preservedExternalNodeIdOffset_ = std::move(other.preservedExternalNodeIdOffset_);
1586 other.preservedExternalNodeIdOffset_.reset();
1587 editSessionOpen_ =
false;
1588 editValidationStatistics_ = other.editValidationStatistics_;
1590 smallestNodeMap_ = std::move(other.smallestNodeMap_);
1591 nodeParent_ = std::move(other.nodeParent_);
1592 firstChild_ = std::move(other.firstChild_);
1593 nextSibling_ = std::move(other.nextSibling_);
1594 prevSibling_ = std::move(other.prevSibling_);
1595 lastChild_ = std::move(other.lastChild_);
1596 numChildrenByNode_ = std::move(other.numChildrenByNode_);
1597 alive_ = std::move(other.alive_);
1598 freeNodeIds_ = std::move(other.freeNodeIds_);
1599 properHead_ = std::move(other.properHead_);
1600 properTail_ = std::move(other.properTail_);
1601 properPartCardinalityByNode_ = std::move(other.properPartCardinalityByNode_);
1602 nextProperPart_ = std::move(other.nextProperPart_);
1603 prevProperPart_ = std::move(other.prevProperPart_);
1605 dfsIntervalCache_ = std::move(other.dfsIntervalCache_);
1606 other.dfsIntervalCache_ = {};
1608 other.lcaCache_.reset();
1609 nodeSupportMetadataCache_ = std::move(other.nodeSupportMetadataCache_);
1610 other.nodeSupportMetadataCache_ = {};
1612 nodeStructureVersion_ = other.nodeStructureVersion_;
1613 topologyVersion_ = other.topologyVersion_;
1614 properPartVersion_ = other.properPartVersion_;
1615 mutationVersion_ = other.mutationVersion_;
1619 ++other.nodeStructureVersion_;
1620 ++other.topologyVersion_;
1621 ++other.properPartVersion_;
1622 ++other.mutationVersion_;
1647 other.requireNotEditing(
"MorphologicalTree move construction");
1648 moveCommittedStateFrom(std::move(
other));
1662 other.requireNotEditing(
"MorphologicalTree move assignment source");
1663 if (
this != &
other) {
1668 moveCommittedStateFrom(std::move(
other));
1737 cloned.rootNodeId_ = rootNodeId_;
1738 cloned.semantics_ = semantics_;
1739 cloned.gridDomain2D_ = gridDomain2D_;
1740 cloned.numNodes_ = numNodes_;
1741 cloned.preservedExternalNodeIdOffset_ = preservedExternalNodeIdOffset_;
1742 cloned.editSessionOpen_ =
false;
1743 cloned.editValidationStatistics_ = editValidationStatistics_;
1744 cloned.smallestNodeMap_ = smallestNodeMap_;
1745 cloned.nodeParent_ = nodeParent_;
1746 cloned.firstChild_ = firstChild_;
1747 cloned.nextSibling_ = nextSibling_;
1748 cloned.prevSibling_ = prevSibling_;
1749 cloned.lastChild_ = lastChild_;
1750 cloned.numChildrenByNode_ = numChildrenByNode_;
1752 cloned.freeNodeIds_ = freeNodeIds_;
1753 cloned.properHead_ = properHead_;
1754 cloned.properTail_ = properTail_;
1755 cloned.properPartCardinalityByNode_ = properPartCardinalityByNode_;
1756 cloned.nextProperPart_ = nextProperPart_;
1757 cloned.prevProperPart_ = prevProperPart_;
1758 cloned.dfsIntervalCache_ = dfsIntervalCache_;
1759 cloned.lcaCache_.reset();
1760 cloned.nodeSupportMetadataCache_ = nodeSupportMetadataCache_;
1761 cloned.nodeStructureVersion_ = nodeStructureVersion_;
1762 cloned.topologyVersion_ = topologyVersion_;
1763 cloned.properPartVersion_ = properPartVersion_;
1764 cloned.mutationVersion_ = mutationVersion_;
1769 class AliveNodeIterator;
1770 class AliveNodeRange;
1771 class ChildrenIterator;
1772 class ChildrenRange;
1773 class ProperPartIterator;
1774 class ProperPartRange;
1775 class NodeSupportIterator;
1776 class NodeSupportRange;
1777 class PostOrderNodeIterator;
1778 class PostOrderNodeRange;
1779 class BreadthFirstNodeIterator;
1780 class BreadthFirstNodeRange;
1781 class AncestorNodeIterator;
1782 class AncestorNodeRange;
1783 class PathBetweenNodesIterator;
1784 class PathBetweenNodesRange;
1785 class SubtreeNodeIterator;
1786 class SubtreeNodeRange;
1787 class DescendantNodeRange;
1807 throw std::logic_error(std::string(
context) +
" cannot be used after the referenced tree topology has changed."));
1824 inline int numPixels()
const {
return static_cast<int>(smallestNodeMap_.size()); }
1836 if (!preservedExternalNodeIdOffset_) {
1837 throw std::runtime_error(
"This tree does not preserve an imported Higra node-id space.");
1853 case NodeIdSpace::MorphologicalTree:
1855 case NodeIdSpace::Higra:
1858 throw std::runtime_error(
"Unknown NodeIdSpace.");
1871 if (!preservedExternalNodeIdOffset_ || !
isNode(nodeId) || !
isAlive(nodeId)) {
1874 return *preservedExternalNodeIdOffset_ + nodeId;
1952 requireAliveNode(nodeId,
"MorphologicalTree::numChildren");
1953 return numChildrenByNode_[nodeId];
1963 requireAliveNode(nodeId,
"MorphologicalTree::numDescendants");
1964 ensureDfsIntervalCache();
1965 return (dfsIntervalCache_.exitIndex[nodeId] - dfsIntervalCache_.entryIndex[nodeId] - 1) / 2;
1975 requireAliveNode(nodeId,
"MorphologicalTree::numSiblings");
1990 requireAliveNode(nodeId,
"MorphologicalTree::dfsEntryIndex");
1991 ensureDfsIntervalCache();
1992 return dfsIntervalCache_.entryIndex[nodeId];
2002 requireAliveNode(nodeId,
"MorphologicalTree::dfsExitIndex");
2003 ensureDfsIntervalCache();
2004 return dfsIntervalCache_.exitIndex[nodeId];
2014 requireAliveNode(nodeId,
"MorphologicalTree::getFirstChild");
2015 return firstChild_[nodeId];
2025 requireAliveNode(nodeId,
"MorphologicalTree::getNextSibling");
2026 return nextSibling_[nodeId];
2044 requireAliveNode(nodeId,
"MorphologicalTree::properPartCardinality");
2045 return properPartCardinalityByNode_[nodeId];
2064 requireAliveNode(
parentNodeId,
"MorphologicalTree::hasChild");
2065 requireAliveNode(
childId,
"MorphologicalTree::hasChild");
2078 requireAliveNode(nodeId,
"MorphologicalTree::parent");
2099 if constexpr (contract::validationsEnabled) {
2102 return smallestNodeMap_[
static_cast<size_t>(pixel)];
2118 std::vector<NodeId>
leaves;
2123 s.push(this->rootNodeId_);
2125 while (!
s.empty()) {
2127 if (numChildrenByNode_[
id] == 0) {
2196 if (convention ==
nullptr) {
2199 const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention->
immersion);
2200 return immersion ? &immersion->complementaryAdjacencies :
nullptr;
2231 throw std::logic_error(std::string(
context) +
" requires a committed MorphologicalTree; an edit session is still open."));
2244 if (numNodes_ == 0) {
2252 if (!
isAlive(nodeId) || nodeId == rootNodeId_) {
2255 if (nodeParent_[nodeId] == nodeId) {
2283 if (!gridDomain2D_) {
2284 throw std::invalid_argument(std::string(
context) +
" requires a regular 2D pixel domain.");
2286 return *gridDomain2D_;
2311 if (numNodes_ <= 0) {
2312 throw std::runtime_error(
"Connected-tree validation requires at least one live node.");
2315 throw std::runtime_error(
"Connected-tree validation requires a live root.");
2317 if (nodeParent_[rootNodeId_] != rootNodeId_) {
2318 throw std::runtime_error(
"Connected-tree validation requires the root to point to itself.");
2333 if (nodeId == rootNodeId_) {
2337 throw std::runtime_error(
"Connected-tree validation found an alive node with no parent.");
2340 throw std::runtime_error(
"Connected-tree validation found a detached alive node.");
2343 throw std::runtime_error(
"Connected-tree validation found an alive node whose parent is outside the alive node domain.");
2349 throw std::runtime_error(
"Connected-tree validation found numNodes() out of sync with the alive node slots.");
2363 throw std::runtime_error(
"Connected-tree validation found a child list that references a non-alive node.");
2365 if (nodeParent_[
static_cast<size_t>(
childNodeId)] != nodeId) {
2366 throw std::runtime_error(
"Connected-tree validation found a child whose parent pointer disagrees with the child list.");
2369 throw std::runtime_error(
"Connected-tree validation found broken previous-sibling links.");
2372 throw std::runtime_error(
"Connected-tree validation found a node referenced by multiple child lists.");
2378 throw std::runtime_error(
"Connected-tree validation found a cycle in a child list.");
2382 if (firstChild_[
static_cast<size_t>(nodeId)] ==
InvalidNode) {
2383 if (lastChild_[
static_cast<size_t>(nodeId)] !=
InvalidNode) {
2384 throw std::runtime_error(
"Connected-tree validation found an empty child list with a non-empty tail pointer.");
2388 throw std::runtime_error(
"Connected-tree validation found an incorrect last-child pointer.");
2391 throw std::runtime_error(
"Connected-tree validation found a child list whose tail still points to a next sibling.");
2396 throw std::runtime_error(
"Connected-tree validation found an incorrect child count cache.");
2399 throw std::runtime_error(
"Connected-tree validation found child lists out of sync with parent pointers.");
2403 if (
seenAsChild[
static_cast<size_t>(rootNodeId_)] != 0) {
2404 throw std::runtime_error(
"Connected-tree validation found the root inside a child list.");
2407 if (!
isAlive(nodeId) || nodeId == rootNodeId_) {
2410 if (
seenAsChild[
static_cast<size_t>(nodeId)] == 0) {
2411 throw std::runtime_error(
"Connected-tree validation found a non-root alive node missing from the child lists.");
2415 std::vector<NodeId> traversal;
2416 traversal.reserve(
static_cast<size_t>(
aliveCount));
2417 traversal.push_back(rootNodeId_);
2418 for (
size_t index = 0; index < traversal.size(); ++index) {
2419 const NodeId nodeId = traversal[index];
2424 if (traversal.size() !=
static_cast<size_t>(
aliveCount)) {
2425 throw std::runtime_error(
"Connected-tree validation found nodes outside the rooted component or a cycle.");
2429 const NodeId smallestNodeId = smallestNodeMap_[
static_cast<size_t>(pixel)];
2430 if (!
isAlive(smallestNodeId)) {
2431 throw std::runtime_error(
"Connected-tree validation found a pixel without a live smallest node.");
2445 pixel = nextProperPart_[
static_cast<size_t>(pixel)]) {
2447 throw std::runtime_error(
"Connected-tree validation found an invalid pixel id in a proper-part list.");
2449 if (smallestNodeMap_[
static_cast<size_t>(pixel)] != nodeId) {
2450 throw std::runtime_error(
"Connected-tree validation found a proper-part list that disagrees with the smallest-node map.");
2453 throw std::runtime_error(
"Connected-tree validation found broken previous-proper-part links.");
2456 throw std::runtime_error(
"Connected-tree validation found a proper part referenced multiple times.");
2462 throw std::runtime_error(
"Connected-tree validation found a cycle in a proper-part list.");
2466 if (properHead_[
static_cast<size_t>(nodeId)] ==
InvalidPixel) {
2467 if (properTail_[
static_cast<size_t>(nodeId)] !=
InvalidPixel) {
2468 throw std::runtime_error(
"Connected-tree validation found an empty proper-part list with a non-empty tail pointer.");
2472 throw std::runtime_error(
"Connected-tree validation found an incorrect proper-part tail pointer.");
2475 throw std::runtime_error(
"Connected-tree validation found a proper-part list whose tail still points forward.");
2480 throw std::runtime_error(
"Connected-tree validation found an incorrect direct proper-part count cache.");
2483 throw std::runtime_error(
"Connected-tree validation found proper-part lists out of sync with the smallest-node map.");
2489 throw std::runtime_error(
"Connected-tree validation found a pixel missing from the proper-part lists.");
2494 for (
auto it = traversal.rbegin();
it != traversal.rend(); ++
it) {
2497 throw std::runtime_error(
"Connected-tree validation found a live node whose subtree support is empty.");
2499 if (nodeId != rootNodeId_) {
2500 const NodeId parentId = nodeParent_[
static_cast<size_t>(nodeId)];
2515 }
catch (
const std::exception&
ex) {
2516 return {
false,
ex.what()};
2518 return {
false,
"Connected-tree validation failed with an unknown error."};
2529 if (numNodes_ <= 0) {
2533 if (
isAlive(nodeId) && properPartCardinalityByNode_[
static_cast<std::size_t
>(nodeId)] == 0) {
2548 if (
isAlive(nodeId) && properPartCardinalityByNode_[
static_cast<std::size_t
>(nodeId)] == 0) {
2549 throw std::runtime_error(
"Tree-of-partial-partitions validation found a node with an empty proper part: " + std::to_string(nodeId) +
".");
2562 requireAliveNode(
u,
"MorphologicalTree::isAncestor");
2563 requireAliveNode(
v,
"MorphologicalTree::isAncestor");
2564 ensureDfsIntervalCache();
2567 return dfsIntervalCache_.entryIndex[
slotU] <= dfsIntervalCache_.entryIndex[
slotV] &&
2568 dfsIntervalCache_.exitIndex[
slotU] >= dfsIntervalCache_.exitIndex[
slotV];
2579 requireAliveNode(
u,
"MorphologicalTree::isDescendant");
2580 requireAliveNode(
v,
"MorphologicalTree::isDescendant");
2581 ensureDfsIntervalCache();
2584 return dfsIntervalCache_.entryIndex[
slotV] <= dfsIntervalCache_.entryIndex[
slotU] &&
2585 dfsIntervalCache_.exitIndex[
slotV] >= dfsIntervalCache_.exitIndex[
slotU];
2641 return lowestCommonAncestorEstablished(
u,
v);
2655 [[
nodiscard]]
inline std::vector<NodeId>
2657 std::vector<std::pair<NodeId, NodeId>>
liveQueries;
2692 requireAliveNode(nodeId,
"MorphologicalTree::children");
2693 return ChildrenRange(
this, firstChild_[nodeId], topologyVersion_);
2703 requireAliveNode(nodeId,
"MorphologicalTree::properPart");
2704 return ProperPartRange(
this, properHead_[nodeId], properPartVersion_);
2717 requireAliveNode(nodeId,
"MorphologicalTree::nodeSupport");
2718 return NodeSupportRange(
this, nodeId, topologyVersion_, properPartVersion_);
2735 requireAliveNode(
rootNodeId,
"MorphologicalTree::postOrder");
2753 requireAliveNode(
rootNodeId,
"MorphologicalTree::breadthFirstTraversal");
2767 requireAliveNode(nodeId,
"MorphologicalTree::ancestors");
2779 requireAliveNode(
sourceNodeId,
"MorphologicalTree::getPathBetweenNodes");
2780 requireAliveNode(
targetNodeId,
"MorphologicalTree::getPathBetweenNodes");
2791 requireAliveNode(nodeId,
"MorphologicalTree::subtreeNodes");
2802 requireAliveNode(nodeId,
"MorphologicalTree::descendants");
2831 requireAliveNonRootNode(nodeId,
"MorphologicalTree::pruneNode");
2834 throw std::invalid_argument(
"MorphologicalTree::pruneNode requires an attached non-root node.");
2879 requireAliveNonRootNode(nodeId,
"MorphologicalTree::mergeNodeIntoParent");
2882 throw std::invalid_argument(
"MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2886 reserveFreeNodeSlots(1);
2891 bumpProperPartVersion();
2892 releaseNode(nodeId);
2939 std::vector<NodeId> euler_;
2941 std::vector<int> depth_;
2943 std::vector<int> firstOccurrence_;
2945 std::vector<int> log2_;
2947 std::vector<int> sparseTable_;
2949 int sparseTableStride_ = 0;
2960 std::vector<std::pair<NodeId, NodeId>>
stack;
2961 stack.emplace_back(nodeId, tree_->firstChild_[nodeId]);
2962 firstOccurrence_[
static_cast<size_t>(nodeId)] =
static_cast<int>(euler_.size());
2963 euler_.push_back(nodeId);
2965 while (!
stack.empty()) {
2967 if (
child == InvalidNode) {
2969 if (!
stack.empty()) {
2974 stack.back().
second = tree_->nextSibling_[child];
2975 firstOccurrence_[
static_cast<size_t>(child)] =
static_cast<int>(euler_.size());
2976 euler_.push_back(child);
2977 depth_.push_back(currentDepth +
static_cast<int>(stack.size()));
2978 stack.emplace_back(child, tree_->firstChild_[child]);
2986 void buildSparseTable() {
2987 const int n =
static_cast<int>(depth_.size());
2990 sparseTable_.clear();
2991 sparseTableStride_ = 0;
2995 log2_.assign(
static_cast<size_t>(n + 1), 0);
2996 for (
int i = 2; i <= n; ++i) {
2997 log2_[
static_cast<size_t>(i)] = log2_[
static_cast<size_t>(i / 2)] + 1;
3000 sparseTableStride_ = 1;
3001 while ((1 << sparseTableStride_) <= n) {
3002 ++sparseTableStride_;
3005 sparseTable_.assign(
static_cast<size_t>(n) *
static_cast<size_t>(sparseTableStride_), 0);
3007 for (
int i = 0; i < n; ++i) {
3008 sparseTable_[sparseTableIndex(i, 0)] = i;
3011 for (
int j = 1; j < sparseTableStride_; ++j) {
3012 const int blockSize = 1 << j;
3013 const int halfBlock = blockSize >> 1;
3014 for (
int i = 0; i + blockSize <= n; ++i) {
3015 const int leftIndex = sparseTable_[sparseTableIndex(i, j - 1)];
3016 const int rightIndex = sparseTable_[sparseTableIndex(i + halfBlock, j - 1)];
3017 sparseTable_[sparseTableIndex(i, j)] =
3018 depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
3030 std::size_t sparseTableIndex(
int row,
int column)
const {
3031 return static_cast<std::size_t
>(row) *
static_cast<std::size_t
>(sparseTableStride_) +
static_cast<std::size_t
>(column);
3041 int rmq(
int left,
int right)
const {
3042 const int length = right - left + 1;
3043 const int logLength = log2_[
static_cast<size_t>(length)];
3044 const int leftIndex = sparseTable_[sparseTableIndex(left, logLength)];
3045 const int rightIndex = sparseTable_[sparseTableIndex(right - (1 << logLength) + 1, logLength)];
3046 return depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
3055 explicit LCAEulerRMQ(
const MorphologicalTree* tree) : tree_(tree) {
3056 if (tree_ ==
nullptr || tree_->
root() == InvalidNode) {
3060 euler_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
numNodes() - 1)));
3061 depth_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
numNodes() - 1)));
3064 depthFirstTraversal(tree_->
root(), 0);
3075 NodeId findLowestCommonAncestor(NodeId u, NodeId v)
const {
3076 const int firstU = firstOccurrence_[
static_cast<size_t>(u)];
3077 const int firstV = firstOccurrence_[
static_cast<size_t>(v)];
3078 if (firstU < 0 || firstV < 0) {
3085 std::swap(left, right);
3087 return euler_[
static_cast<size_t>(rmq(left, right))];
3106 std::size_t expectedVersion_ = 0;
3116 T_->checkNodeIteratorVersion(expectedVersion_);
3120 if (current_ >= end_) {
3161 T_->checkNodeIteratorVersion(expectedVersion_);
3175 T_->checkNodeIteratorVersion(expectedVersion_);
3208 std::size_t expectedVersion_ = 0;
3252 std::size_t expectedVersion_ = 0;
3287 T_->checkTopologyIteratorVersion(expectedVersion_);
3289 currentLocal_ = T_->nextSibling_[currentLocal_];
3300 T_->checkTopologyIteratorVersion(expectedVersion_);
3301 return currentLocal_;
3331 std::size_t expectedVersion_ = 0;
3374 std::size_t expectedVersion_ = 0;
3409 T_->checkProperPartIteratorVersion(expectedVersion_);
3411 currentProperPart_ = T_->nextProperPart_[currentProperPart_];
3422 T_->checkProperPartIteratorVersion(expectedVersion_);
3423 return currentProperPart_;
3453 std::size_t expectedVersion_ = 0;
3494 std::vector<NodeId> nodeStack_;
3498 std::size_t expectedTopologyVersion_ = 0;
3500 std::size_t expectedProperPartVersion_ = 0;
3505 void checkVersions()
const {
3506 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
3507 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
3515 void pushChildren(
NodeId nodeId) {
3521 nodeStack_.push_back(*
it);
3530 while (currentProperPart_ ==
InvalidPixel && !nodeStack_.empty()) {
3531 const NodeId nodeId = nodeStack_.back();
3532 nodeStack_.pop_back();
3533 pushChildren(nodeId);
3534 currentProperPart_ = T_->properHead_[
static_cast<size_t>(nodeId)];
3579 currentProperPart_ = T_->nextProperPart_[
static_cast<size_t>(currentProperPart_)];
3592 return currentProperPart_;
3622 std::size_t expectedTopologyVersion_ = 0;
3624 std::size_t expectedProperPartVersion_ = 0;
3673 std::vector<Item> stack_;
3677 std::size_t expectedVersion_ = 0;
3683 T_->checkTopologyIteratorVersion(expectedVersion_);
3684 while (!stack_.empty()) {
3685 Item&
top = stack_.back();
3686 if (!
top.expanded) {
3687 top.expanded =
true;
3690 stack_.push_back({
child,
false});
3737 T_->checkTopologyIteratorVersion(expectedVersion_);
3738 if (!stack_.empty()) {
3751 T_->checkTopologyIteratorVersion(expectedVersion_);
3782 std::size_t expectedVersion_ = 0;
3825 std::size_t expectedVersion_ = 0;
3863 T_->checkTopologyIteratorVersion(expectedVersion_);
3864 if (!queue_.empty()) {
3879 T_->checkTopologyIteratorVersion(expectedVersion_);
3880 return queue_.front();
3910 std::size_t expectedVersion_ = 0;
3953 std::size_t expectedVersion_ = 0;
3988 T_->checkTopologyIteratorVersion(expectedVersion_);
3990 if (T_->
isRoot(current_)) {
3993 current_ = T_->
parent(current_);
4005 T_->checkTopologyIteratorVersion(expectedVersion_);
4036 std::size_t expectedVersion_ = 0;
4077 const std::vector<NodeId>* path_ =
nullptr;
4079 std::size_t index_ = 0;
4081 std::size_t expectedVersion_ = 0;
4117 T_->checkTopologyIteratorVersion(expectedVersion_);
4130 T_->checkTopologyIteratorVersion(expectedVersion_);
4131 return (*path_)[index_];
4159 std::vector<NodeId> path_;
4161 std::size_t expectedVersion_ = 0;
4196 std::vector<NodeId>
path;
4263 std::vector<NodeId> stack_;
4265 std::size_t expectedVersion_ = 0;
4303 T_->checkTopologyIteratorVersion(expectedVersion_);
4304 if (!stack_.empty()) {
4309 stack_.push_back(
child);
4321 T_->checkTopologyIteratorVersion(expectedVersion_);
4322 return stack_.back();
4352 std::size_t expectedVersion_ = 0;
4395 std::size_t expectedVersion_ = 0;