mmcfilters
Public API documentation
Loading...
Searching...
No Matches
MorphologicalTree.hpp
1#pragma once
2
3#include "../utils/RegularGridAdjacency2D.hpp"
4#include "../utils/Assert.hpp"
5#include "../utils/Altitude.hpp"
6#include "../utils/Common.hpp"
7#include "../utils/Contract.hpp"
8#include "../dataStructure/FastQueue.hpp"
10#include "ProperPartDomain.hpp"
11#include "detail/MorphologicalTreeConstructionTag.hpp"
12#include <algorithm>
13#include <cassert>
14#include <cstddef>
15#include <cstdint>
16#include <functional>
17#include <limits>
18#include <list>
19#include <memory>
20#include <optional>
21#include <set>
22#include <span>
23#include <stdexcept>
24#include <string>
25#include <tuple>
26#include <unordered_map>
27#include <unordered_set>
28#include <utility>
29#include <vector>
30
31namespace mmcfilters {
32
33template <AltitudeValue T> class ValuedMorphologicalTree;
34template <class T> struct NativeHierarchyOwned;
35
36namespace detail {
37class TreeKernelAccess;
38
39} // namespace detail
40
44enum class NodeIdSpace { MorphologicalTree, Higra };
45
55 bool ok = false;
56
58 std::string message;
59
63 explicit operator bool() const noexcept { return ok; }
64};
65
78
79enum class TreeEditValidationMode { Complete, Incremental };
80
81// Forward declaration for the edit-session wrapper.
82class TreeEditor;
83
118 private:
119 friend class TreeEditor;
120 friend class MorphologicalTreeFactory;
121 friend class detail::TreeKernelAccess;
130 template <AltitudeValue T>
132 std::optional<NodeId> preservedExternalNodeIdOffset,
133 detail::MaterializedVersionPolicy versionPolicy);
134
135 class LCAEulerRMQ; // Forward declaration for the LCA cache implementation.
136
137 // ========================= Private attributes ========================= //
139 NodeId rootNodeId_ = InvalidNode;
143 std::optional<GridDomain2D> gridDomain2D_;
145 int numNodes_ = 0;
147 std::optional<NodeId> preservedExternalNodeIdOffset_;
149 bool editSessionOpen_ = false;
151 TreeEditValidationStatistics editValidationStatistics_;
152
153 // Smallest-node map, indexed by pixel id [0, numPixels()).
155 std::vector<NodeId> smallestNodeMap_;
156
157 // Parent links, indexed by local node-slot id [0, numInternalNodeSlots()).
159 std::vector<NodeId> nodeParent_;
160
161 // Internal hierarchy linked structure, indexed by local node-slot id [0, numInternalNodeSlots()).
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_;
172
173 // Free slot management and alive-node iteration, indexed by local node-slot id [0, numInternalNodeSlots()).
175 std::vector<uint8_t> alive_;
177 std::vector<NodeId> freeNodeIds_;
178
179 // Per-node proper-part pixel lists, indexed by local node-slot id.
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_;
190
191 // Structural caches for traversal-based queries, indexed by local node-slot id [0, numInternalNodeSlots()).
193 struct DfsIntervalCache {
195 std::vector<int> entryIndex;
197 std::vector<int> exitIndex;
199 bool valid = false;
201 void invalidate() noexcept { valid = false; }
202 };
204 mutable DfsIntervalCache dfsIntervalCache_;
206 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
207
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();
220
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();
226 }
227 };
229 mutable NodeSupportMetadataCache nodeSupportMetadataCache_;
230
231 // Version counters for iterator invalidation.
233 std::size_t nodeStructureVersion_ = 0;
235 std::size_t topologyVersion_ = 0;
237 std::size_t properPartVersion_ = 0;
239 std::size_t mutationVersion_ = 0;
240
242 enum class ChildSplicePolicy { AppendToTargetTail, ReplaceSourceSlotWhenDirectChild };
243
244 // ========================= Private methods ========================= //
250 inline NodeId allocateSlot() {
251 if (!freeNodeIds_.empty()) {
252 const NodeId slotId = freeNodeIds_.back();
253 freeNodeIds_.pop_back();
254
255 nodeParent_[slotId] = InvalidNode;
256 firstChild_[slotId] = InvalidNode;
257 nextSibling_[slotId] = InvalidNode;
258 prevSibling_[slotId] = InvalidNode;
259 lastChild_[slotId] = InvalidNode;
260 numChildrenByNode_[slotId] = 0;
261 alive_[slotId] = 1;
262 properHead_[slotId] = InvalidPixel;
263 properTail_[slotId] = InvalidPixel;
264 properPartCardinalityByNode_[slotId] = 0;
265 return slotId;
266 }
267
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);
275 alive_.push_back(1);
276 properHead_.push_back(InvalidPixel);
277 properTail_.push_back(InvalidPixel);
278 properPartCardinalityByNode_.push_back(0);
279 return slotId;
280 }
281
287 inline void initializeProperPartStorage(size_t numPixels) {
288 nextProperPart_.assign(numPixels, InvalidPixel);
289 prevProperPart_.assign(numPixels, InvalidPixel);
290 }
291
295 inline void invalidateHigraNodeIdSpace() noexcept { preservedExternalNodeIdOffset_.reset(); }
296
306 inline void preserveExternalNodeIdOffset(NodeId internalNodeOffset) {
307 if (internalNodeOffset < 0) {
308 throw std::invalid_argument("An external internal-node offset must be non-negative.");
309 }
310 preservedExternalNodeIdOffset_ = internalNodeOffset;
311 }
312
316 inline void beginEditSession() {
317 if (editSessionOpen_) {
318 throw std::logic_error("A MorphologicalTree edit session is already open.");
319 }
320 editSessionOpen_ = true;
321 }
322
326 inline void endEditSession() noexcept { editSessionOpen_ = false; }
327
333 inline void recordEditCommit(TreeEditValidationMode mode) noexcept {
334 if (mode == TreeEditValidationMode::Incremental) {
335 ++editValidationStatistics_.incrementalValidationCommits;
336 } else {
337 ++editValidationStatistics_.completeValidationCommits;
338 }
339 }
340
346 inline void initializeEmptyStorage(size_t numPixels) {
347 rootNodeId_ = InvalidNode;
348 numNodes_ = 0;
349 smallestNodeMap_.assign(numPixels, InvalidNode);
350
351 nodeParent_.clear();
352 firstChild_.clear();
353 nextSibling_.clear();
354 prevSibling_.clear();
355 lastChild_.clear();
356 numChildrenByNode_.clear();
357 alive_.clear();
358 freeNodeIds_.clear();
359 properHead_.clear();
360 properTail_.clear();
361 properPartCardinalityByNode_.clear();
362 initializeProperPartStorage(numPixels);
363 invalidateHigraNodeIdSpace();
364
365 dfsIntervalCache_.entryIndex.clear();
366 dfsIntervalCache_.exitIndex.clear();
367 invalidateDfsIntervalCache();
368 nodeSupportMetadataCache_.cardinalityByNode.clear();
369 nodeSupportMetadataCache_.smallestPixelByNode.clear();
370 nodeSupportMetadataCache_.invalidate();
371 invalidateAllIterators();
372 }
373
379 inline void releaseSlotStorage(NodeId slotId) {
380 // Grow the free-list before changing the slot. If allocation fails, the
381 // live topology is therefore left untouched instead of terminating from
382 // inside a falsely-noexcept operation or publishing a half-released slot.
383 freeNodeIds_.push_back(slotId);
384 nodeParent_[slotId] = InvalidNode;
385 firstChild_[slotId] = InvalidNode;
386 nextSibling_[slotId] = InvalidNode;
387 prevSibling_[slotId] = InvalidNode;
388 lastChild_[slotId] = InvalidNode;
389 numChildrenByNode_[slotId] = 0;
390 alive_[slotId] = 0;
391 properHead_[slotId] = InvalidPixel;
392 properTail_[slotId] = InvalidPixel;
393 properPartCardinalityByNode_[slotId] = 0;
394 }
395
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.");
404 }
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));
410 }
411 }
412
419 inline bool isFreeSlot(NodeId slotId) const noexcept { return slotId >= 0 && slotId < static_cast<NodeId>(alive_.size()) && alive_[slotId] == 0; }
420
427 inline void requireAliveNode(NodeId nodeId, const char* context) const {
428 MMCFILTERS_CONTRACT_REQUIRE(isAlive(nodeId), throw std::invalid_argument(std::string(context) + " requires a live internal NodeId."));
429 }
430
437 inline void requireAliveNonRootNode(NodeId nodeId, const char* context) const {
438 requireAliveNode(nodeId, context);
439 MMCFILTERS_CONTRACT_REQUIRE(!isRoot(nodeId), throw std::invalid_argument(std::string(context) + " cannot target the root node."));
440 }
441
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());
450
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)) {
454 continue;
455 }
456
457 const PixelId tailProperPartId = properTail_[smallestNodeSlotId];
458 if (tailProperPartId == InvalidPixel) {
459 properHead_[smallestNodeSlotId] = pixel;
460 properTail_[smallestNodeSlotId] = pixel;
461 } else {
462 nextProperPart_[tailProperPartId] = pixel;
463 prevProperPart_[pixel] = tailProperPartId;
464 properTail_[smallestNodeSlotId] = pixel;
465 }
466 properPartCardinalityByNode_[smallestNodeSlotId]++;
467 }
468 }
469
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)];
479
480 if (prev == InvalidPixel) {
481 properHead_[static_cast<size_t>(smallestNodeSlotId)] = next;
482 } else {
483 nextProperPart_[static_cast<size_t>(prev)] = next;
484 }
485
486 if (next == InvalidPixel) {
487 properTail_[static_cast<size_t>(smallestNodeSlotId)] = prev;
488 } else {
489 prevProperPart_[static_cast<size_t>(next)] = prev;
490 }
491
492 nextProperPart_[static_cast<size_t>(pixel)] = InvalidPixel;
493 prevProperPart_[static_cast<size_t>(pixel)] = InvalidPixel;
494 --properPartCardinalityByNode_[static_cast<size_t>(smallestNodeSlotId)];
495 }
496
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;
509 } else {
510 nextProperPart_[static_cast<size_t>(tail)] = pixel;
511 prevProperPart_[static_cast<size_t>(pixel)] = tail;
512 properTail_[static_cast<size_t>(targetSlotId)] = pixel;
513 }
514 ++properPartCardinalityByNode_[static_cast<size_t>(targetSlotId)];
515 }
516
523 inline void spliceProperPartsSlots(NodeId targetSlotId, NodeId sourceSlotId) noexcept {
524 const PixelId sourceHead = properHead_[static_cast<size_t>(sourceSlotId)];
525 if (sourceHead == InvalidPixel) {
526 return;
527 }
528
529 for (PixelId pixel = sourceHead; pixel != InvalidPixel; pixel = nextProperPart_[static_cast<size_t>(pixel)]) {
530 smallestNodeMap_[static_cast<size_t>(pixel)] = targetSlotId;
531 }
532
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)];
536
537 if (targetTail == InvalidPixel) {
538 properHead_[static_cast<size_t>(targetSlotId)] = sourceHead;
539 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
540 } else {
541 nextProperPart_[static_cast<size_t>(targetTail)] = sourceHead;
542 prevProperPart_[static_cast<size_t>(sourceHead)] = targetTail;
543 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
544 }
545
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;
550 }
551
557 inline void releaseSlotNode(NodeId slotNodeId) {
558 releaseSlotStorage(slotNodeId);
559 numNodes_--;
560 invalidateDfsIntervalCache();
561 bumpNodeStructureVersion();
562 }
563
567 inline void invalidateAllIterators() noexcept {
568 nodeStructureVersion_ = 0;
569 topologyVersion_ = 0;
570 properPartVersion_ = 0;
571 ++mutationVersion_;
572 lcaCache_.reset();
573 nodeSupportMetadataCache_.invalidate();
574 }
575
579 inline void invalidateLcaCache() const noexcept { lcaCache_.reset(); }
580
584 inline void bumpNodeStructureVersion() noexcept {
585 ++nodeStructureVersion_;
586 ++mutationVersion_;
587 invalidateLcaCache();
588 invalidateHigraNodeIdSpace();
589 }
590
594 inline void bumpTopologyVersion() noexcept {
595 ++topologyVersion_;
596 ++mutationVersion_;
597 invalidateLcaCache();
598 invalidateDfsIntervalCache();
599 invalidateHigraNodeIdSpace();
600 }
601
605 inline void bumpProperPartVersion() noexcept {
606 ++properPartVersion_;
607 ++mutationVersion_;
608 invalidateHigraNodeIdSpace();
609 }
610
616 inline void checkNodeIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
617 assert(expectedVersion == nodeStructureVersion_ && "Alive-node iterator invalidated by node-structure mutation.");
618 }
619
625 inline void checkTopologyIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
626 assert(expectedVersion == topologyVersion_ && "Topology iterator invalidated by tree-structure mutation.");
627 }
628
634 inline void checkProperPartIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
635 assert(expectedVersion == properPartVersion_ && "Proper-parts iterator invalidated by proper-part mutation.");
636 }
637
641 inline void invalidateDfsIntervalCache() const noexcept { dfsIntervalCache_.invalidate(); }
642
646 inline void recomputeDfsIntervalCache() const {
647 dfsIntervalCache_.entryIndex.assign(nodeParent_.size(), -1);
648 dfsIntervalCache_.exitIndex.assign(nodeParent_.size(), -1);
649
650 if (rootNodeId_ == InvalidNode) {
651 dfsIntervalCache_.valid = true;
652 return;
653 }
654
655 // Explicit frames preserve child order without using the call stack on deep trees.
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++;
664 stack.pop_back();
665 } else {
666 const NodeId child = nextChild;
667 nextChild = nextSibling_[child];
668 dfsIntervalCache_.entryIndex[child] = nextDfsEventIndex++;
669 stack.emplace_back(child, firstChild_[child]);
670 }
671 }
672
673 dfsIntervalCache_.valid = true;
674 }
675
679 inline void ensureDfsIntervalCache() const {
680 if (!dfsIntervalCache_.valid) {
681 recomputeDfsIntervalCache();
682 }
683 }
684
690 inline const LCAEulerRMQ& ensureLcaCache() const {
691 if (!lcaCache_) {
692 lcaCache_ = std::make_unique<LCAEulerRMQ>(this);
693 }
694 return *lcaCache_;
695 }
696
704 [[nodiscard]] inline std::optional<NodeId> lowestCommonAncestorFromDfsIntervalsEstablished(NodeId u, NodeId v) const {
705 if (u == v) {
706 return u;
707 }
708 ensureDfsIntervalCache();
709 if (dfsIntervalCache_.entryIndex[static_cast<std::size_t>(u)] < 0 ||
710 dfsIntervalCache_.entryIndex[static_cast<std::size_t>(v)] < 0) {
711 return InvalidNode;
712 }
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)];
718 };
719 if (isAncestorCached(u, v)) {
720 return u;
721 }
722 if (isAncestorCached(v, u)) {
723 return v;
724 }
725 return std::nullopt;
726 }
727
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);
737 }
738
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;
747 blockSize *= 2) {
748 ++rmqLevels;
749 }
750 return static_cast<long double>(nodeParent_.size()) * static_cast<long double>(36 + 8 * rmqLevels);
751 }
752
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) {
769 return;
770 }
771
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));
776 }
777 return;
778 }
779
780 const std::size_t numSlots = nodeParent_.size();
781 std::size_t numUnresolvedQueries = numQueries;
782 if (!forceTarjan) {
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);
789 continue;
790 }
791 ++numUnresolvedQueries;
792 }
793 }
794 if (numUnresolvedQueries == 0 || rootNodeId_ == InvalidNode) {
795 return;
796 }
797
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()) {
806 continue;
807 }
808 consumeLca(queryIndex, ensureLcaCache().findLowestCommonAncestor(first, second));
809 }
810 return;
811 }
812
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()) {
818 continue;
819 }
820 ++queryOffsets[static_cast<std::size_t>(first) + 1];
821 ++queryOffsets[static_cast<std::size_t>(second) + 1];
822 }
823 for (std::size_t index = 1; index < queryOffsets.size(); ++index) {
824 queryOffsets[index] += queryOffsets[index - 1];
825 }
826
827 struct QueryReference {
828 NodeId otherEndpoint = InvalidNode;
829 std::uint32_t queryIndex = 0;
830 };
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()) {
835 continue;
836 }
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)};
841 }
842 for (std::size_t index = numSlots; index > 0; --index) {
843 queryOffsets[index] = queryOffsets[index - 1];
844 }
845 queryOffsets[0] = QueryOffset{0};
846
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)];
851 }
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;
855 node = next;
856 }
857 return representative;
858 };
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) {
863 return firstRoot;
864 }
865 if (setRanks[static_cast<std::size_t>(firstRoot)] < setRanks[static_cast<std::size_t>(secondRoot)]) {
866 std::swap(firstRoot, secondRoot);
867 }
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)];
871 }
872 return firstRoot;
873 };
874
875 struct TraversalFrame {
876 NodeId node = InvalidNode;
877 NodeId nextChild = InvalidNode;
878 };
879
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_)]});
889
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)]});
898 continue;
899 }
900
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)]);
909 }
910 }
911
912 traversalStack.pop_back();
913 if (!traversalStack.empty()) {
914 const NodeId parent = traversalStack.back().node;
915 const NodeId representative = uniteSets(setParents, setRanks, parent, node);
916 ancestors[static_cast<std::size_t>(representative)] = parent;
917 }
918 }
919 }
920
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; });
932 return lcas;
933 }
934
946 template <typename QueryAccessor, typename ResultConsumer>
947 inline void forEachGeneratedLowestCommonAncestorEstablished(std::size_t numQueries, QueryAccessor&& queryForIndex,
948 ResultConsumer&& consumeLca) const {
949 if (numQueries == 0) {
950 return;
951 }
952 if (lcaCache_) {
953 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
954 const auto [first, second] = queryForIndex(queryIndex);
955 consumeLca(queryIndex, lowestCommonAncestorEstablished(first, second));
956 }
957 return;
958 }
959
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;
972 break;
973 }
974 }
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);
979 }
980 forEachLowestCommonAncestorEstablished(
981 numQueries, [&](std::size_t queryIndex) { return allPairs[queryIndex]; }, std::forward<ResultConsumer>(consumeLca), true);
982 return;
983 }
984 }
985
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);
992 } else {
993 ++numUnresolvedQueries;
994 }
995 }
996 if (numUnresolvedQueries == 0) {
997 return;
998 }
999
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));
1010 }
1011 }
1012 return;
1013 }
1014
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));
1024 }
1025 }
1026 forEachLowestCommonAncestorEstablished(
1027 unresolvedPairs.size(), [&](std::size_t queryIndex) { return unresolvedPairs[queryIndex]; },
1028 [&](std::size_t queryIndex, NodeId lca) { consumeLca(unresolvedQueryIndices[queryIndex], lca); }, true);
1029 }
1030
1031 // ========================= Internal topology helpers ========================= //
1032 // These helpers mutate the dense node-indexed topology storage directly.
1039 void linkChildSlot(NodeId parentSlotId, NodeId childId) {
1040 if (parentSlotId < 0 || childId < 0)
1041 return;
1042
1043 // If the child already has a parent, detach it before reading sibling links.
1044 if (nodeParent_[childId] != InvalidNode) {
1045 unlinkChildSlot(nodeParent_[childId], childId);
1046 }
1047
1048 nodeParent_[childId] = parentSlotId;
1049 prevSibling_[childId] = lastChild_[parentSlotId];
1050 nextSibling_[childId] = InvalidNode;
1051
1052 if (firstChild_[parentSlotId] == InvalidNode) {
1053 firstChild_[parentSlotId] = lastChild_[parentSlotId] = childId;
1054 } else {
1055 nextSibling_[lastChild_[parentSlotId]] = childId;
1056 lastChild_[parentSlotId] = childId;
1057 }
1058 ++numChildrenByNode_[parentSlotId];
1059 bumpTopologyVersion();
1060 }
1061
1068 inline void unlinkChildSlot(NodeId parentSlotId, NodeId childId) {
1069 if (parentSlotId < 0 || childId < 0)
1070 return;
1071 if (nodeParent_[childId] != parentSlotId)
1072 return;
1073
1074 const NodeId prev = prevSibling_[childId];
1075 const NodeId next = nextSibling_[childId];
1076
1077 if (prev == InvalidNode)
1078 firstChild_[parentSlotId] = next;
1079 else
1080 nextSibling_[prev] = next;
1081
1082 if (next == InvalidNode)
1083 lastChild_[parentSlotId] = prev;
1084 else
1085 prevSibling_[next] = prev;
1086
1087 if (numChildrenByNode_[parentSlotId] > 0)
1088 --numChildrenByNode_[parentSlotId];
1089
1090 nodeParent_[childId] = InvalidNode;
1091 prevSibling_[childId] = InvalidNode;
1092 nextSibling_[childId] = InvalidNode;
1093 bumpTopologyVersion();
1094 }
1095
1103 inline void spliceChildrenSlots(NodeId toId, NodeId fromId, ChildSplicePolicy policy = ChildSplicePolicy::AppendToTargetTail) {
1104 if (toId < 0 || fromId < 0 || toId == fromId)
1105 return;
1106
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;
1111
1112 for (NodeId childId = firstFrom; childId != InvalidNode; childId = nextSibling_[childId]) {
1113 nodeParent_[childId] = toId;
1114 }
1115
1116 if (replaceSourceSlot) {
1117 const NodeId prev = prevSibling_[fromId];
1118 const NodeId next = nextSibling_[fromId];
1119
1120 if (firstFrom == InvalidNode) {
1121 if (prev == InvalidNode) {
1122 firstChild_[toId] = next;
1123 } else {
1124 nextSibling_[prev] = next;
1125 }
1126
1127 if (next == InvalidNode) {
1128 lastChild_[toId] = prev;
1129 } else {
1130 prevSibling_[next] = prev;
1131 }
1132 } else {
1133 if (prev == InvalidNode) {
1134 firstChild_[toId] = firstFrom;
1135 prevSibling_[firstFrom] = InvalidNode;
1136 } else {
1137 nextSibling_[prev] = firstFrom;
1138 prevSibling_[firstFrom] = prev;
1139 }
1140
1141 if (next == InvalidNode) {
1142 lastChild_[toId] = lastFrom;
1143 nextSibling_[lastFrom] = InvalidNode;
1144 } else {
1145 prevSibling_[next] = lastFrom;
1146 nextSibling_[lastFrom] = next;
1147 }
1148 }
1149
1150 nodeParent_[fromId] = InvalidNode;
1151 prevSibling_[fromId] = InvalidNode;
1152 nextSibling_[fromId] = InvalidNode;
1153 numChildrenByNode_[toId] += movedCount - 1;
1154 } else {
1155 if (firstFrom == InvalidNode)
1156 return;
1157
1158 if (firstChild_[toId] == InvalidNode) {
1159 firstChild_[toId] = firstFrom;
1160 lastChild_[toId] = lastFrom;
1161 } else {
1162 nextSibling_[lastChild_[toId]] = firstFrom;
1163 prevSibling_[firstFrom] = lastChild_[toId];
1164 lastChild_[toId] = lastFrom;
1165 }
1166
1167 numChildrenByNode_[toId] += movedCount;
1168 }
1169
1170 firstChild_[fromId] = InvalidNode;
1171 lastChild_[fromId] = InvalidNode;
1172 numChildrenByNode_[fromId] = 0;
1173 bumpTopologyVersion();
1174 }
1175
1181 inline void releaseNode(NodeId nodeId) {
1182 if (!isNode(nodeId) || !isAlive(nodeId) || isRoot(nodeId)) {
1183 return;
1184 }
1185 const NodeId nodeSlot = nodeId;
1186 if (nodeParent_[nodeSlot] != nodeSlot) {
1187 return;
1188 }
1189 if (numChildrenByNode_[nodeSlot] != 0 || properPartCardinalityByNode_[nodeSlot] != 0) {
1190 return;
1191 }
1192 releaseSlotNode(nodeSlot);
1193 }
1194
1200 inline NodeId allocateNode() {
1201 if (freeNodeIds_.empty()) {
1202 return InvalidNode;
1203 }
1204 const NodeId nodeSlot = allocateSlot();
1205 nodeParent_[nodeSlot] = nodeSlot;
1206 numNodes_++;
1207 invalidateDfsIntervalCache();
1208 bumpNodeStructureVersion();
1209 return nodeSlot;
1210 }
1211
1221 inline NodeId createDetachedNode() {
1222 const NodeId nodeSlot = allocateSlot();
1223 nodeParent_[static_cast<size_t>(nodeSlot)] = nodeSlot;
1224 numNodes_++;
1225 invalidateDfsIntervalCache();
1226 bumpNodeStructureVersion();
1227 return nodeSlot;
1228 }
1229
1237 inline void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag) {
1238 if (!isAlive(parentNodeId) || !isAlive(childId) || nodeParent_[static_cast<std::size_t>(childId)] != parentNodeId) {
1239 return;
1240 }
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);
1246 }
1247 unlinkChildSlot(parentSlotId, childSlotId);
1248 nodeParent_[childSlotId] = childSlotId;
1249 invalidateDfsIntervalCache();
1250 if (releaseNodeFlag) {
1251 releaseNode(childId);
1252 }
1253 }
1254
1261 inline void attachNode(NodeId parentNodeId, NodeId nodeId) {
1262 if (!isAlive(parentNodeId) || !isAlive(nodeId) || isRoot(nodeId) || parentNodeId == nodeId) {
1263 return;
1264 }
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);
1270 }
1271 nodeParent_[nodeSlotId] = InvalidNode;
1272 linkChildSlot(parentSlotId, nodeSlotId);
1273 }
1274
1280 inline void detachNode(NodeId nodeId) {
1281 if (!isAlive(nodeId) || isRoot(nodeId)) {
1282 return;
1283 }
1284 const NodeId nodeSlotId = nodeId;
1285 const NodeId parentSlotId = nodeParent_[nodeSlotId];
1286 if (parentSlotId == InvalidNode || parentSlotId == nodeSlotId) {
1287 return;
1288 }
1289 unlinkChildSlot(parentSlotId, nodeSlotId);
1290 nodeParent_[nodeSlotId] = nodeSlotId;
1291 }
1292
1299 inline void moveNode(NodeId nodeId, NodeId newParentId) {
1300 if (!isAlive(nodeId) || !isAlive(newParentId) || isRoot(nodeId) || nodeId == newParentId) {
1301 return;
1302 }
1303 const NodeId nodeSlotId = nodeId;
1304 const NodeId newParentSlotId = newParentId;
1305 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
1306 if (oldParentSlotId == newParentSlotId) {
1307 return;
1308 }
1309 if (oldParentSlotId != InvalidNode && oldParentSlotId != nodeSlotId) {
1310 unlinkChildSlot(oldParentSlotId, nodeSlotId);
1311 }
1312 nodeParent_[nodeSlotId] = InvalidNode;
1313 linkChildSlot(newParentSlotId, nodeSlotId);
1314 }
1315
1322 inline void moveChildren(NodeId parentNodeId, NodeId sourceId) {
1323 if (!isAlive(parentNodeId) || !isAlive(sourceId) || parentNodeId == sourceId) {
1324 return;
1325 }
1326 spliceChildrenSlots(parentNodeId, sourceId);
1327 }
1328
1336 inline void movePixelToProperPart(NodeId targetNodeId, NodeId sourceNodeId, PixelId pixel) {
1337 if (!isAlive(targetNodeId) || !isAlive(sourceNodeId) || !isPixel(pixel) || targetNodeId == sourceNodeId) {
1338 return;
1339 }
1340 const NodeId sourceSlotId = sourceNodeId;
1341 if (smallestNodeMap_[pixel] != sourceSlotId) {
1342 return;
1343 }
1344 unlinkPixelFromProperPart(sourceSlotId, pixel);
1345 appendDetachedProperPart(targetNodeId, pixel);
1346 bumpProperPartVersion();
1347 }
1348
1355 inline void mergeProperParts(NodeId targetNodeId, NodeId sourceNodeId) {
1356 if (!isAlive(targetNodeId) || !isAlive(sourceNodeId) || targetNodeId == sourceNodeId) {
1357 return;
1358 }
1359 if (properHead_[static_cast<size_t>(sourceNodeId)] == InvalidPixel) {
1360 return;
1361 }
1362 spliceProperPartsSlots(targetNodeId, sourceNodeId);
1363 bumpProperPartVersion();
1364 }
1365
1371 inline void setRoot(NodeId nodeId) {
1372 if (!isAlive(nodeId)) {
1373 return;
1374 }
1375 const NodeId nodeSlot = nodeId;
1376 if (rootNodeId_ == nodeSlot) {
1377 return;
1378 }
1379 if (rootNodeId_ != InvalidNode && !isFreeSlot(rootNodeId_)) {
1380 nodeParent_[rootNodeId_] = rootNodeId_;
1381 prevSibling_[rootNodeId_] = InvalidNode;
1382 nextSibling_[rootNodeId_] = InvalidNode;
1383 }
1384 const NodeId oldParentSlot = nodeParent_[nodeSlot];
1385 if (oldParentSlot != InvalidNode && oldParentSlot != nodeSlot) {
1386 unlinkChildSlot(oldParentSlot, nodeSlot);
1387 }
1388 rootNodeId_ = nodeSlot;
1389 nodeParent_[nodeSlot] = nodeSlot;
1390 prevSibling_[nodeSlot] = InvalidNode;
1391 nextSibling_[nodeSlot] = InvalidNode;
1392 bumpTopologyVersion();
1393 }
1394
1408 void initializeNativeTopologyStorage(std::vector<NodeId> nodeParent, std::vector<NodeId> smallestNodeMap, NodeId root,
1409 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics) {
1410 if (gridDomain2D && smallestNodeMap.size() != gridDomain2D->size("Native topology 2D pixel domain")) {
1411 throw std::invalid_argument("Native topology pixel domain must match the attached 2D grid.");
1412 }
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.");
1415 }
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.");
1418 }
1419 switch (semantics.kind) {
1420 case MorphologicalTreeKind::Generic:
1421 case MorphologicalTreeKind::MaxTree:
1422 case MorphologicalTreeKind::MinTree:
1423 case MorphologicalTreeKind::TreeOfShapes:
1424 case MorphologicalTreeKind::ExtremumResidualTree:
1425 break;
1426 default:
1427 throw std::invalid_argument("Native topology kind is not supported.");
1428 }
1429 const int numNodeSlots = static_cast<int>(nodeParent.size());
1430 const std::size_t numPixels = smallestNodeMap.size();
1431 if (numNodeSlots <= 0) {
1432 throw std::invalid_argument("Native topology import requires at least one internal node.");
1433 }
1434 if (numPixels == 0) {
1435 throw std::invalid_argument("Native topology import requires at least one pixel.");
1436 }
1437 if (root < 0 || root >= numNodeSlots) {
1438 throw std::invalid_argument("Native topology import requires a valid root node id.");
1439 }
1441 semantics_ = std::move(semantics);
1442 gridDomain2D_ = gridDomain2D;
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.");
1446 }
1447 if (adjacency.getNumRows() != gridDomain2D_->rows || adjacency.getNumColumns() != gridDomain2D_->columns) {
1448 throw std::invalid_argument(std::string(context) + " must match the attached 2D grid.");
1449 }
1450 };
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.");
1456 }
1457 if (!gridDomain2D_) {
1458 throw std::invalid_argument("TopographicConvention requires an attached 2D grid domain.");
1459 }
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.");
1465 }
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");
1469 }
1470 }
1471
1472 initializeEmptyStorage(numPixels);
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);
1484 smallestNodeMap_ = std::move(smallestNodeMap);
1485 initializeProperPartStorage(numPixels);
1486
1487 rootNodeId_ = root;
1488 numNodes_ = static_cast<int>(numNodeSlots);
1489
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.");
1496 }
1497 ++selfParentedRoots;
1498 continue;
1499 }
1500 if (parentId < 0 || parentId >= numNodeSlots) {
1501 throw std::invalid_argument("Native topology import found a parent outside the internal-node domain.");
1502 }
1503
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;
1507 } else {
1508 nextSibling_[static_cast<size_t>(lastChild_[static_cast<size_t>(parentId)])] = nodeId;
1509 }
1510 lastChild_[static_cast<size_t>(parentId)] = nodeId;
1511 ++numChildrenByNode_[static_cast<size_t>(parentId)];
1512 }
1513 if (selfParentedRoots != 1) {
1514 throw std::invalid_argument("Native topology import must encode exactly one self-parented root.");
1515 }
1516
1517 rebuildProperPartLinksFromSmallestNodeMap();
1518 invalidateDfsIntervalCache();
1519 invalidateAllIterators();
1520 preservedExternalNodeIdOffset_.reset();
1521 }
1522
1532 void initializeNativeTopology(std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1533 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics) {
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.");
1536 }
1537 const int numNodeSlots = static_cast<int>(nodeParent.size());
1538 for (NodeId smallestNodeId : smallestNodeMap) {
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.");
1541 }
1542 }
1543 initializeNativeTopologyStorage(std::vector<NodeId>(nodeParent.begin(), nodeParent.end()),
1544 std::vector<NodeId>(smallestNodeMap.begin(), smallestNodeMap.end()), root, gridDomain2D, std::move(semantics));
1546 }
1547
1560 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::vector<NodeId>&& nodeParent, std::vector<NodeId>&& smallestNodeMap, NodeId root,
1561 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics) {
1562 initializeNativeTopologyStorage(std::move(nodeParent), std::move(smallestNodeMap), root, gridDomain2D, std::move(semantics));
1563 }
1564
1568 MorphologicalTree() = default;
1569
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_;
1589
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_);
1604
1605 dfsIntervalCache_ = std::move(other.dfsIntervalCache_);
1606 other.dfsIntervalCache_ = {};
1607 lcaCache_.reset();
1608 other.lcaCache_.reset();
1609 nodeSupportMetadataCache_ = std::move(other.nodeSupportMetadataCache_);
1610 other.nodeSupportMetadataCache_ = {};
1611
1612 nodeStructureVersion_ = other.nodeStructureVersion_;
1613 topologyVersion_ = other.topologyVersion_;
1614 properPartVersion_ = other.properPartVersion_;
1615 mutationVersion_ = other.mutationVersion_;
1616
1617 // Iterators into the moved-from owner must not accidentally validate
1618 // merely because its previous token happened to be zero.
1619 ++other.nodeStructureVersion_;
1620 ++other.topologyVersion_;
1621 ++other.properPartVersion_;
1622 ++other.mutationVersion_;
1623 }
1624
1625 public:
1630
1637
1647 other.requireNotEditing("MorphologicalTree move construction");
1648 moveCommittedStateFrom(std::move(other));
1649 }
1650
1661 requireNotEditing("MorphologicalTree move assignment destination");
1662 other.requireNotEditing("MorphologicalTree move assignment source");
1663 if (this != &other) {
1664 const std::size_t previousNodeVersion = nodeStructureVersion_;
1665 const std::size_t previousTopologyVersion = topologyVersion_;
1666 const std::size_t previousProperPartVersion = properPartVersion_;
1667 const std::size_t previousMutationVersion = mutationVersion_;
1668 moveCommittedStateFrom(std::move(other));
1669 nodeStructureVersion_ = std::max(nodeStructureVersion_, previousNodeVersion) + 1;
1670 topologyVersion_ = std::max(topologyVersion_, previousTopologyVersion) + 1;
1671 properPartVersion_ = std::max(properPartVersion_, previousProperPartVersion) + 1;
1672 mutationVersion_ = std::max(mutationVersion_, previousMutationVersion) + 1;
1673 }
1674 return *this;
1675 }
1676
1680 virtual ~MorphologicalTree() = default;
1681
1700 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1701 int rows, int columns, MorphologicalTreeSemantics semantics) {
1702 initializeNativeTopology(nodeParent, smallestNodeMap, root, GridDomain2D{rows, columns}, std::move(semantics));
1703 }
1704
1718 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1720 initializeNativeTopology(nodeParent, smallestNodeMap, root, std::nullopt, std::move(semantics));
1721 }
1722
1735 requireNotEditing("MorphologicalTree::clone");
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_;
1751 cloned.alive_ = alive_;
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_;
1765 return cloned;
1766 }
1767
1768 // Forward declarations for nested iterator/range types whose definitions stay at the end of the class.
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;
1788
1789 public:
1790 // ========================= Public methods ========================= //
1791
1797 std::size_t getMutationVersion() const noexcept { return mutationVersion_; }
1798
1805 void requireMutationVersion(std::size_t expectedVersion, const char* context) const {
1806 MMCFILTERS_CONTRACT_REQUIRE(mutationVersion_ == expectedVersion,
1807 throw std::logic_error(std::string(context) + " cannot be used after the referenced tree topology has changed."));
1808 }
1809
1817 inline int numInternalNodeSlots() const { return static_cast<int>(nodeParent_.size()); }
1818
1824 inline int numPixels() const { return static_cast<int>(smallestNodeMap_.size()); }
1825
1835 inline int getNumHigraNodes() const {
1836 if (!preservedExternalNodeIdOffset_) {
1837 throw std::runtime_error("This tree does not preserve an imported Higra node-id space.");
1838 }
1839 return *preservedExternalNodeIdOffset_ + numInternalNodeSlots();
1840 }
1841
1851 inline int getNodeIdSpaceSize(NodeIdSpace outputSpace) const {
1852 switch (outputSpace) {
1853 case NodeIdSpace::MorphologicalTree:
1854 return numInternalNodeSlots();
1855 case NodeIdSpace::Higra:
1856 return getNumHigraNodes();
1857 }
1858 throw std::runtime_error("Unknown NodeIdSpace.");
1859 }
1860
1870 inline NodeId getHigraNodeId(NodeId nodeId) const noexcept {
1871 if (!preservedExternalNodeIdOffset_ || !isNode(nodeId) || !isAlive(nodeId)) {
1872 return InvalidNode;
1873 }
1874 return *preservedExternalNodeIdOffset_ + nodeId;
1875 }
1876
1882 inline NodeId root() const { return rootNodeId_; }
1883
1890 inline bool isNode(NodeId nodeId) const noexcept { return nodeId >= 0 && nodeId < static_cast<int>(nodeParent_.size()); }
1891
1898 inline bool isPixel(PixelId pixel) const noexcept { return pixel >= 0 && pixel < static_cast<int>(smallestNodeMap_.size()); }
1899
1906 inline bool isAlive(NodeId nodeId) const {
1907 if (!isNode(nodeId)) {
1908 return false;
1909 }
1910 const NodeId localId = nodeId;
1911 return localId >= 0 && localId < static_cast<NodeId>(nodeParent_.size()) && !isFreeSlot(localId) &&
1912 (nodeParent_[localId] != InvalidNode || localId == rootNodeId_);
1913 }
1914
1921 inline bool isRoot(NodeId nodeId) const { return nodeId == root(); }
1922
1928 inline int getNumFreeNodeSlots() const { return static_cast<int>(freeNodeIds_.size()); }
1929
1935 inline int numLeafNodes() const {
1936 int count = 0;
1937 for (NodeId nodeId : aliveNodeIds()) {
1938 if (isLeaf(nodeId)) {
1939 ++count;
1940 }
1941 }
1942 return count;
1943 }
1944
1951 inline int numChildren(NodeId nodeId) const {
1952 requireAliveNode(nodeId, "MorphologicalTree::numChildren");
1953 return numChildrenByNode_[nodeId];
1954 }
1955
1962 inline int numDescendants(NodeId nodeId) const {
1963 requireAliveNode(nodeId, "MorphologicalTree::numDescendants");
1964 ensureDfsIntervalCache();
1965 return (dfsIntervalCache_.exitIndex[nodeId] - dfsIntervalCache_.entryIndex[nodeId] - 1) / 2;
1966 }
1967
1974 inline int numSiblings(NodeId nodeId) const {
1975 requireAliveNode(nodeId, "MorphologicalTree::numSiblings");
1976 if (isRoot(nodeId)) {
1977 return 0;
1978 }
1979 const NodeId parentNodeId = parent(nodeId);
1980 return std::max(0, numChildren(parentNodeId) - 1);
1981 }
1982
1989 inline int dfsEntryIndex(NodeId nodeId) const {
1990 requireAliveNode(nodeId, "MorphologicalTree::dfsEntryIndex");
1991 ensureDfsIntervalCache();
1992 return dfsIntervalCache_.entryIndex[nodeId];
1993 }
1994
2001 inline int dfsExitIndex(NodeId nodeId) const {
2002 requireAliveNode(nodeId, "MorphologicalTree::dfsExitIndex");
2003 ensureDfsIntervalCache();
2004 return dfsIntervalCache_.exitIndex[nodeId];
2005 }
2006
2013 inline NodeId getFirstChild(NodeId nodeId) const {
2014 requireAliveNode(nodeId, "MorphologicalTree::getFirstChild");
2015 return firstChild_[nodeId];
2016 }
2017
2024 inline NodeId getNextSibling(NodeId nodeId) const {
2025 requireAliveNode(nodeId, "MorphologicalTree::getNextSibling");
2026 return nextSibling_[nodeId];
2027 }
2028
2035 inline bool isLeaf(NodeId nodeId) const { return getFirstChild(nodeId) == InvalidNode; }
2036
2043 inline int properPartCardinality(NodeId nodeId) const {
2044 requireAliveNode(nodeId, "MorphologicalTree::properPartCardinality");
2045 return properPartCardinalityByNode_[nodeId];
2046 }
2047
2054 inline bool hasEmptyProperPart(NodeId nodeId) const { return properPartCardinality(nodeId) == 0; }
2055
2064 requireAliveNode(parentNodeId, "MorphologicalTree::hasChild");
2065 requireAliveNode(childId, "MorphologicalTree::hasChild");
2066 return parent(childId) == parentNodeId;
2067 }
2068
2077 inline NodeId parent(NodeId nodeId) const {
2078 requireAliveNode(nodeId, "MorphologicalTree::parent");
2079 if (isRoot(nodeId)) {
2080 return nodeId;
2081 }
2082 const NodeId parentSlot = nodeParent_[nodeId];
2083 if (parentSlot == InvalidNode) {
2084 return InvalidNode;
2085 }
2086 if (parentSlot == nodeId) {
2087 return nodeId;
2088 }
2089 return parentSlot;
2090 }
2091
2098 inline NodeId smallestNode(PixelId pixel) const {
2099 if constexpr (contract::validationsEnabled) {
2100 return isPixel(pixel) ? smallestNodeMap_[static_cast<size_t>(pixel)] : InvalidNode;
2101 }
2102 return smallestNodeMap_[static_cast<size_t>(pixel)];
2103 }
2104
2110 [[nodiscard]] inline std::span<const NodeId> smallestNodeMap() const noexcept { return smallestNodeMap_; }
2111
2117 std::vector<NodeId> leaves() const {
2118 std::vector<NodeId> leaves;
2119 if (rootNodeId_ == InvalidNode) {
2120 return leaves;
2121 }
2123 s.push(this->rootNodeId_);
2124
2125 while (!s.empty()) {
2126 const NodeId id = s.pop();
2127 if (numChildrenByNode_[id] == 0) {
2128 leaves.push_back(id);
2129 } else {
2130 for (NodeId c = firstChild_[id]; c != InvalidNode; c = nextSibling_[c]) {
2131 s.push(c);
2132 }
2133 }
2134 }
2135 return leaves;
2136 }
2137
2146 inline MorphologicalTreeKind kind() const noexcept { return semantics_.kind; }
2147
2153 inline const MorphologicalTreeSemantics& semantics() const noexcept { return semantics_; }
2154
2161
2168
2171 return std::get_if<SharedAdjacencyContext>(&semantics_.constructionContext);
2172 }
2173
2179 if (const auto* context = sharedAdjacencyContext()) {
2180 return &context->adjacency;
2181 }
2182 return nullptr;
2183 }
2184
2187 return std::get_if<TopographicConvention>(&semantics_.constructionContext);
2188 }
2189
2195 const TopographicConvention* convention = topographicConvention();
2196 if (convention == nullptr) {
2197 return nullptr;
2198 }
2199 const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention->immersion);
2200 return immersion ? &immersion->complementaryAdjacencies : nullptr;
2201 }
2202
2208 inline int numNodes() const noexcept { return numNodes_; }
2209
2215 inline bool isEditing() const noexcept { return editSessionOpen_; }
2216
2222 [[nodiscard]] const TreeEditValidationStatistics& getEditValidationStatistics() const noexcept { return editValidationStatistics_; }
2223
2229 inline void requireNotEditing(const char* context) const {
2231 throw std::logic_error(std::string(context) + " requires a committed MorphologicalTree; an edit session is still open."));
2232 }
2233
2244 if (numNodes_ == 0) {
2245 return false;
2246 }
2247 if (rootNodeId_ == InvalidNode || !isAlive(rootNodeId_)) {
2248 return true;
2249 }
2250
2251 for (NodeId nodeId = 0; nodeId < static_cast<NodeId>(nodeParent_.size()); ++nodeId) {
2252 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
2253 continue;
2254 }
2255 if (nodeParent_[nodeId] == nodeId) {
2256 return true;
2257 }
2258 }
2259 return false;
2260 }
2261
2267 [[nodiscard]] inline bool hasGridDomain2D() const noexcept { return gridDomain2D_.has_value(); }
2268
2274 [[nodiscard]] inline const std::optional<GridDomain2D>& gridDomain2D() const noexcept { return gridDomain2D_; }
2275
2282 inline const GridDomain2D& requireGridDomain2D(const char* context) const {
2283 if (!gridDomain2D_) {
2284 throw std::invalid_argument(std::string(context) + " requires a regular 2D pixel domain.");
2285 }
2286 return *gridDomain2D_;
2287 }
2288
2294 inline int numRows() const { return requireGridDomain2D("MorphologicalTree::numRows").rows; }
2295
2301 inline int numColumns() const { return requireGridDomain2D("MorphologicalTree::numColumns").columns; }
2302
2311 if (numNodes_ <= 0) {
2312 throw std::runtime_error("Connected-tree validation requires at least one live node.");
2313 }
2314 if (!isAlive(rootNodeId_)) {
2315 throw std::runtime_error("Connected-tree validation requires a live root.");
2316 }
2317 if (nodeParent_[rootNodeId_] != rootNodeId_) {
2318 throw std::runtime_error("Connected-tree validation requires the root to point to itself.");
2319 }
2320
2321 const int numSlots = numInternalNodeSlots();
2322 const int domainPixelCount = numPixels();
2323 std::vector<int> expectedChildrenByNode(static_cast<size_t>(numSlots), 0);
2324 std::vector<int> expectedProperPartCardinalityByNode(static_cast<size_t>(numSlots), 0);
2325 int aliveCount = 0;
2326
2327 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2328 if (!isAlive(nodeId)) {
2329 continue;
2330 }
2331 ++aliveCount;
2332 const NodeId parentNodeId = nodeParent_[static_cast<size_t>(nodeId)];
2333 if (nodeId == rootNodeId_) {
2334 continue;
2335 }
2336 if (parentNodeId == InvalidNode) {
2337 throw std::runtime_error("Connected-tree validation found an alive node with no parent.");
2338 }
2339 if (parentNodeId == nodeId) {
2340 throw std::runtime_error("Connected-tree validation found a detached alive node.");
2341 }
2343 throw std::runtime_error("Connected-tree validation found an alive node whose parent is outside the alive node domain.");
2344 }
2345 expectedChildrenByNode[static_cast<size_t>(parentNodeId)] += 1;
2346 }
2347
2348 if (aliveCount != numNodes_) {
2349 throw std::runtime_error("Connected-tree validation found numNodes() out of sync with the alive node slots.");
2350 }
2351
2352 std::vector<uint8_t> seenAsChild(static_cast<size_t>(numSlots), 0);
2353 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2354 if (!isAlive(nodeId)) {
2355 continue;
2356 }
2357
2358 int actualChildCount = 0;
2360 for (NodeId childNodeId = firstChild_[static_cast<size_t>(nodeId)]; childNodeId != InvalidNode;
2361 childNodeId = nextSibling_[static_cast<size_t>(childNodeId)]) {
2363 throw std::runtime_error("Connected-tree validation found a child list that references a non-alive node.");
2364 }
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.");
2367 }
2368 if (prevSibling_[static_cast<size_t>(childNodeId)] != previousChildId) {
2369 throw std::runtime_error("Connected-tree validation found broken previous-sibling links.");
2370 }
2371 if (seenAsChild[static_cast<size_t>(childNodeId)] != 0) {
2372 throw std::runtime_error("Connected-tree validation found a node referenced by multiple child lists.");
2373 }
2374 seenAsChild[static_cast<size_t>(childNodeId)] = 1;
2378 throw std::runtime_error("Connected-tree validation found a cycle in a child list.");
2379 }
2380 }
2381
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.");
2385 }
2386 } else {
2387 if (lastChild_[static_cast<size_t>(nodeId)] != previousChildId) {
2388 throw std::runtime_error("Connected-tree validation found an incorrect last-child pointer.");
2389 }
2390 if (nextSibling_[static_cast<size_t>(previousChildId)] != InvalidNode) {
2391 throw std::runtime_error("Connected-tree validation found a child list whose tail still points to a next sibling.");
2392 }
2393 }
2394
2395 if (actualChildCount != numChildrenByNode_[static_cast<size_t>(nodeId)]) {
2396 throw std::runtime_error("Connected-tree validation found an incorrect child count cache.");
2397 }
2398 if (actualChildCount != expectedChildrenByNode[static_cast<size_t>(nodeId)]) {
2399 throw std::runtime_error("Connected-tree validation found child lists out of sync with parent pointers.");
2400 }
2401 }
2402
2403 if (seenAsChild[static_cast<size_t>(rootNodeId_)] != 0) {
2404 throw std::runtime_error("Connected-tree validation found the root inside a child list.");
2405 }
2406 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2407 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
2408 continue;
2409 }
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.");
2412 }
2413 }
2414
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];
2420 for (NodeId childId = firstChild_[static_cast<size_t>(nodeId)]; childId != InvalidNode; childId = nextSibling_[static_cast<size_t>(childId)]) {
2421 traversal.push_back(childId);
2422 }
2423 }
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.");
2426 }
2427
2428 for (PixelId pixel = 0; pixel < domainPixelCount; ++pixel) {
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.");
2432 }
2433 expectedProperPartCardinalityByNode[static_cast<size_t>(smallestNodeId)] += 1;
2434 }
2435
2436 std::vector<uint8_t> seenProperPart(static_cast<size_t>(domainPixelCount), 0);
2437 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2438 if (!isAlive(nodeId)) {
2439 continue;
2440 }
2441
2444 for (PixelId pixel = properHead_[static_cast<size_t>(nodeId)]; pixel != InvalidPixel;
2445 pixel = nextProperPart_[static_cast<size_t>(pixel)]) {
2446 if (!isPixel(pixel)) {
2447 throw std::runtime_error("Connected-tree validation found an invalid pixel id in a proper-part list.");
2448 }
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.");
2451 }
2452 if (prevProperPart_[static_cast<size_t>(pixel)] != previousProperPartId) {
2453 throw std::runtime_error("Connected-tree validation found broken previous-proper-part links.");
2454 }
2455 if (seenProperPart[static_cast<size_t>(pixel)] != 0) {
2456 throw std::runtime_error("Connected-tree validation found a proper part referenced multiple times.");
2457 }
2458 seenProperPart[static_cast<size_t>(pixel)] = 1;
2459 previousProperPartId = pixel;
2462 throw std::runtime_error("Connected-tree validation found a cycle in a proper-part list.");
2463 }
2464 }
2465
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.");
2469 }
2470 } else {
2471 if (properTail_[static_cast<size_t>(nodeId)] != previousProperPartId) {
2472 throw std::runtime_error("Connected-tree validation found an incorrect proper-part tail pointer.");
2473 }
2474 if (nextProperPart_[static_cast<size_t>(previousProperPartId)] != InvalidPixel) {
2475 throw std::runtime_error("Connected-tree validation found a proper-part list whose tail still points forward.");
2476 }
2477 }
2478
2479 if (actualProperPartCardinality != properPartCardinalityByNode_[static_cast<size_t>(nodeId)]) {
2480 throw std::runtime_error("Connected-tree validation found an incorrect direct proper-part count cache.");
2481 }
2482 if (actualProperPartCardinality != expectedProperPartCardinalityByNode[static_cast<size_t>(nodeId)]) {
2483 throw std::runtime_error("Connected-tree validation found proper-part lists out of sync with the smallest-node map.");
2484 }
2485 }
2486
2487 for (PixelId pixel = 0; pixel < domainPixelCount; ++pixel) {
2488 if (seenProperPart[static_cast<size_t>(pixel)] == 0) {
2489 throw std::runtime_error("Connected-tree validation found a pixel missing from the proper-part lists.");
2490 }
2491 }
2492
2494 for (auto it = traversal.rbegin(); it != traversal.rend(); ++it) {
2495 const NodeId nodeId = *it;
2496 if (subtreeSupport[static_cast<size_t>(nodeId)] == 0) {
2497 throw std::runtime_error("Connected-tree validation found a live node whose subtree support is empty.");
2498 }
2499 if (nodeId != rootNodeId_) {
2500 const NodeId parentId = nodeParent_[static_cast<size_t>(nodeId)];
2501 subtreeSupport[static_cast<size_t>(parentId)] += subtreeSupport[static_cast<size_t>(nodeId)];
2502 }
2503 }
2504 }
2505
2512 try {
2514 return {true, ""};
2515 } catch (const std::exception& ex) {
2516 return {false, ex.what()};
2517 } catch (...) {
2518 return {false, "Connected-tree validation failed with an unknown error."};
2519 }
2520 }
2521
2529 if (numNodes_ <= 0) {
2530 return false;
2531 }
2532 for (NodeId nodeId = 0; nodeId < numInternalNodeSlots(); ++nodeId) {
2533 if (isAlive(nodeId) && properPartCardinalityByNode_[static_cast<std::size_t>(nodeId)] == 0) {
2534 return false;
2535 }
2536 }
2537 return true;
2538 }
2539
2547 for (NodeId nodeId = 0; nodeId < numInternalNodeSlots(); ++nodeId) {
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) + ".");
2550 }
2551 }
2552 }
2553
2561 inline bool isAncestor(NodeId u, NodeId v) const {
2562 requireAliveNode(u, "MorphologicalTree::isAncestor");
2563 requireAliveNode(v, "MorphologicalTree::isAncestor");
2564 ensureDfsIntervalCache();
2565 const NodeId slotU = u;
2566 const NodeId slotV = v;
2567 return dfsIntervalCache_.entryIndex[slotU] <= dfsIntervalCache_.entryIndex[slotV] &&
2568 dfsIntervalCache_.exitIndex[slotU] >= dfsIntervalCache_.exitIndex[slotV];
2569 }
2570
2578 inline bool isDescendant(NodeId u, NodeId v) const {
2579 requireAliveNode(u, "MorphologicalTree::isDescendant");
2580 requireAliveNode(v, "MorphologicalTree::isDescendant");
2581 ensureDfsIntervalCache();
2582 const NodeId slotU = u;
2583 const NodeId slotV = v;
2584 return dfsIntervalCache_.entryIndex[slotV] <= dfsIntervalCache_.entryIndex[slotU] &&
2585 dfsIntervalCache_.exitIndex[slotV] >= dfsIntervalCache_.exitIndex[slotU];
2586 }
2594 inline bool isComparable(NodeId u, NodeId v) const { return isAncestor(u, v) || isAncestor(v, u); }
2595
2603 inline bool isStrictAncestor(NodeId u, NodeId v) const { return u != v && isAncestor(u, v); }
2604
2612 inline bool isStrictDescendant(NodeId u, NodeId v) const { return u != v && isDescendant(u, v); }
2613
2621 inline bool isStrictComparable(NodeId u, NodeId v) const { return isStrictAncestor(u, v) || isStrictAncestor(v, u); }
2622
2638 if (!isAlive(u) || !isAlive(v)) {
2639 return InvalidNode;
2640 }
2641 return lowestCommonAncestorEstablished(u, v);
2642 }
2643
2655 [[nodiscard]] inline std::vector<NodeId>
2656 lowestCommonAncestors(std::span<const std::pair<NodeId, NodeId>> queries) const {
2657 std::vector<std::pair<NodeId, NodeId>> liveQueries;
2658 std::vector<std::size_t> liveQueryIndices;
2659 std::vector<NodeId> lcas(queries.size(), InvalidNode);
2660 liveQueries.reserve(queries.size());
2661 liveQueryIndices.reserve(queries.size());
2662 for (std::size_t queryIndex = 0; queryIndex < queries.size(); ++queryIndex) {
2663 const auto [first, second] = queries[queryIndex];
2664 if (!isAlive(first) || !isAlive(second)) {
2665 continue;
2666 }
2667 liveQueries.emplace_back(first, second);
2668 liveQueryIndices.push_back(queryIndex);
2669 }
2670
2671 const std::vector<NodeId> liveLcas = lowestCommonAncestorsEstablished(liveQueries);
2672 for (std::size_t liveIndex = 0; liveIndex < liveLcas.size(); ++liveIndex) {
2674 }
2675 return lcas;
2676 }
2677
2683 inline AliveNodeRange aliveNodeIds() const { return AliveNodeRange(this, 0, numInternalNodeSlots(), nodeStructureVersion_); }
2684
2691 inline ChildrenRange children(NodeId nodeId) const {
2692 requireAliveNode(nodeId, "MorphologicalTree::children");
2693 return ChildrenRange(this, firstChild_[nodeId], topologyVersion_);
2694 }
2695
2702 inline ProperPartRange properPart(NodeId nodeId) const {
2703 requireAliveNode(nodeId, "MorphologicalTree::properPart");
2704 return ProperPartRange(this, properHead_[nodeId], properPartVersion_);
2705 }
2706
2716 inline NodeSupportRange nodeSupport(NodeId nodeId) const {
2717 requireAliveNode(nodeId, "MorphologicalTree::nodeSupport");
2718 return NodeSupportRange(this, nodeId, topologyVersion_, properPartVersion_);
2719 }
2720
2726 inline PostOrderNodeRange postOrder() const { return PostOrderNodeRange(this, root(), topologyVersion_); }
2727
2735 requireAliveNode(rootNodeId, "MorphologicalTree::postOrder");
2736 return PostOrderNodeRange(this, rootNodeId, topologyVersion_);
2737 }
2738
2744 inline BreadthFirstNodeRange breadthFirstTraversal() const { return BreadthFirstNodeRange(this, root(), topologyVersion_); }
2745
2753 requireAliveNode(rootNodeId, "MorphologicalTree::breadthFirstTraversal");
2754 return BreadthFirstNodeRange(this, rootNodeId, topologyVersion_);
2755 }
2756
2766 inline AncestorNodeRange ancestors(NodeId nodeId) const {
2767 requireAliveNode(nodeId, "MorphologicalTree::ancestors");
2768 return AncestorNodeRange(this, nodeId, topologyVersion_);
2769 }
2770
2779 requireAliveNode(sourceNodeId, "MorphologicalTree::getPathBetweenNodes");
2780 requireAliveNode(targetNodeId, "MorphologicalTree::getPathBetweenNodes");
2781 return PathBetweenNodesRange(this, sourceNodeId, targetNodeId, topologyVersion_);
2782 }
2783
2790 inline SubtreeNodeRange subtreeNodes(NodeId nodeId) const {
2791 requireAliveNode(nodeId, "MorphologicalTree::subtreeNodes");
2792 return SubtreeNodeRange(this, nodeId, topologyVersion_);
2793 }
2794
2802 requireAliveNode(nodeId, "MorphologicalTree::descendants");
2803 return DescendantNodeRange(this, nodeId, topologyVersion_);
2804 }
2805
2819
2829 inline void pruneNode(NodeId nodeId) {
2830 requireNotEditing("MorphologicalTree::pruneNode");
2831 requireAliveNonRootNode(nodeId, "MorphologicalTree::pruneNode");
2832 const NodeId parentNodeId = parent(nodeId);
2833 if (parentNodeId == InvalidNode || parentNodeId == nodeId) {
2834 throw std::invalid_argument("MorphologicalTree::pruneNode requires an attached non-root node.");
2835 }
2837 std::size_t releaseCount = 0;
2838 for ([[maybe_unused]] NodeId subtreeNode : subtreeNodes(nodeId)) {
2839 ++releaseCount;
2840 }
2841 reserveFreeNodeSlots(releaseCount);
2842
2843 const auto descendToDeepestLastChild = [this](NodeId startId) {
2845 while (firstChild_[currentId] != InvalidNode) {
2846 currentId = lastChild_[currentId];
2847 }
2848 return currentId;
2849 };
2850
2852 while (true) {
2853 const NodeId currentParentSlotId = nodeParent_[currentId];
2854 mergeProperParts(parentSlotId, currentId);
2856 unlinkChildSlot(currentParentSlotId, currentId);
2857 }
2858 nodeParent_[currentId] = currentId;
2859 releaseNode(currentId);
2860
2861 if (currentId == nodeId) {
2862 break;
2863 }
2865 }
2866 }
2867
2877 inline void mergeNodeIntoParent(NodeId nodeId) {
2878 requireNotEditing("MorphologicalTree::mergeNodeIntoParent");
2879 requireAliveNonRootNode(nodeId, "MorphologicalTree::mergeNodeIntoParent");
2880 const NodeId parentNodeId = parent(nodeId);
2881 if (parentNodeId == InvalidNode || parentNodeId == nodeId) {
2882 throw std::invalid_argument("MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2883 }
2885 const NodeId nodeSlotId = nodeId;
2886 reserveFreeNodeSlots(1);
2887
2888 spliceProperPartsSlots(parentSlotId, nodeSlotId);
2889 spliceChildrenSlots(parentSlotId, nodeSlotId, ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild);
2890 nodeParent_[nodeSlotId] = nodeSlotId;
2891 bumpProperPartVersion();
2892 releaseNode(nodeId);
2893 }
2894
2895 private:
2896 // ========================= Internal classes ========================= //
2936 class LCAEulerRMQ {
2937 private:
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;
2951 const MorphologicalTree* tree_ = nullptr;
2952
2959 void depthFirstTraversal(NodeId nodeId, int currentDepth) {
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);
2964 depth_.push_back(currentDepth);
2965 while (!stack.empty()) {
2966 const NodeId child = stack.back().second;
2967 if (child == InvalidNode) {
2968 stack.pop_back();
2969 if (!stack.empty()) {
2970 euler_.push_back(stack.back().first);
2971 depth_.push_back(currentDepth + static_cast<int>(stack.size()) - 1);
2972 }
2973 } else {
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]);
2979 }
2980 }
2981 }
2982
2986 void buildSparseTable() {
2987 const int n = static_cast<int>(depth_.size());
2988 if (n == 0) {
2989 log2_.clear();
2990 sparseTable_.clear();
2991 sparseTableStride_ = 0;
2992 return;
2993 }
2994
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;
2998 }
2999
3000 sparseTableStride_ = 1;
3001 while ((1 << sparseTableStride_) <= n) {
3002 ++sparseTableStride_;
3003 }
3004
3005 sparseTable_.assign(static_cast<size_t>(n) * static_cast<size_t>(sparseTableStride_), 0);
3006
3007 for (int i = 0; i < n; ++i) {
3008 sparseTable_[sparseTableIndex(i, 0)] = i;
3009 }
3010
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;
3019 }
3020 }
3021 }
3022
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);
3032 }
3033
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;
3047 }
3048
3049 public:
3055 explicit LCAEulerRMQ(const MorphologicalTree* tree) : tree_(tree) {
3056 if (tree_ == nullptr || tree_->root() == InvalidNode) {
3057 return;
3058 }
3059
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)));
3062 firstOccurrence_.assign(static_cast<size_t>(tree_->numInternalNodeSlots()), -1);
3063
3064 depthFirstTraversal(tree_->root(), 0);
3065 buildSparseTable();
3066 }
3067
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) {
3079 return InvalidNode;
3080 }
3081
3082 int left = firstU;
3083 int right = firstV;
3084 if (left > right) {
3085 std::swap(left, right);
3086 }
3087 return euler_[static_cast<size_t>(rmq(left, right))];
3088 }
3089 };
3090
3091 public:
3092 // ========================= Internal classes and iterators ========================= //
3093
3098 private:
3100 const MorphologicalTree* T_ = nullptr;
3102 NodeId current_ = InvalidNode;
3104 NodeId end_ = InvalidNode;
3106 std::size_t expectedVersion_ = 0;
3107
3111 void settle_() {
3112 if (!T_) {
3113 current_ = InvalidNode;
3114 return;
3115 }
3116 T_->checkNodeIteratorVersion(expectedVersion_);
3117 while (current_ != InvalidNode && current_ < end_ && !T_->isAlive(current_)) {
3118 ++current_;
3119 }
3120 if (current_ >= end_) {
3121 current_ = InvalidNode;
3122 }
3123 }
3124
3125 public:
3127 using iterator_category = std::input_iterator_tag;
3131 using difference_type = std::ptrdiff_t;
3133 using pointer = const NodeId*;
3135 using reference = const NodeId&;
3136
3141
3151 : T_(tree), current_(current), end_(end), expectedVersion_(expectedVersion) {
3152 settle_();
3153 }
3154
3161 T_->checkNodeIteratorVersion(expectedVersion_);
3162 if (current_ != InvalidNode) {
3163 ++current_;
3164 settle_();
3165 }
3166 return *this;
3167 }
3168
3175 T_->checkNodeIteratorVersion(expectedVersion_);
3176 return current_;
3177 }
3178
3185 bool operator==(const AliveNodeIterator& other) const { return current_ == other.current_; }
3186
3193 bool operator!=(const AliveNodeIterator& other) const { return !(*this == other); }
3194 };
3195
3200 private:
3202 const MorphologicalTree* T_ = nullptr;
3204 NodeId begin_ = InvalidNode;
3206 NodeId end_ = InvalidNode;
3208 std::size_t expectedVersion_ = 0;
3209
3210 public:
3214 AliveNodeRange() = default;
3215
3225 : T_(tree), begin_(begin), end_(end), expectedVersion_(expectedVersion) {}
3226
3232 AliveNodeIterator begin() const { return AliveNodeIterator(T_, begin_, end_, expectedVersion_); }
3233
3239 AliveNodeIterator end() const { return AliveNodeIterator(T_, InvalidNode, end_, expectedVersion_); }
3240 };
3241
3246 private:
3248 const MorphologicalTree* T_ = nullptr;
3250 NodeId currentLocal_ = InvalidNode;
3252 std::size_t expectedVersion_ = 0;
3253
3254 public:
3256 using iterator_category = std::forward_iterator_tag;
3260 using difference_type = std::ptrdiff_t;
3262 using pointer = const NodeId*;
3264 using reference = const NodeId&;
3265
3269 ChildrenIterator() = default;
3270
3279 : T_(tree), currentLocal_(currentLocal), expectedVersion_(expectedVersion) {}
3280
3287 T_->checkTopologyIteratorVersion(expectedVersion_);
3288 if (T_ && currentLocal_ != InvalidNode) {
3289 currentLocal_ = T_->nextSibling_[currentLocal_];
3290 }
3291 return *this;
3292 }
3293
3300 T_->checkTopologyIteratorVersion(expectedVersion_);
3301 return currentLocal_;
3302 }
3303
3310 bool operator==(const ChildrenIterator& other) const { return currentLocal_ == other.currentLocal_; }
3311
3318 bool operator!=(const ChildrenIterator& other) const { return !(*this == other); }
3319 };
3320
3325 private:
3327 const MorphologicalTree* T_ = nullptr;
3329 NodeId firstLocal_ = InvalidNode;
3331 std::size_t expectedVersion_ = 0;
3332
3333 public:
3337 ChildrenRange() = default;
3338
3347 : T_(tree), firstLocal_(firstLocal), expectedVersion_(expectedVersion) {}
3348
3354 ChildrenIterator begin() const { return ChildrenIterator(T_, firstLocal_, expectedVersion_); }
3355
3361 ChildrenIterator end() const { return ChildrenIterator(T_, InvalidNode, expectedVersion_); }
3362 };
3363
3368 private:
3370 const MorphologicalTree* T_ = nullptr;
3372 PixelId currentProperPart_ = InvalidPixel;
3374 std::size_t expectedVersion_ = 0;
3375
3376 public:
3378 using iterator_category = std::forward_iterator_tag;
3382 using difference_type = std::ptrdiff_t;
3384 using pointer = const PixelId*;
3386 using reference = const PixelId&;
3387
3392
3401 : T_(tree), currentProperPart_(currentProperPart), expectedVersion_(expectedVersion) {}
3402
3409 T_->checkProperPartIteratorVersion(expectedVersion_);
3410 if (T_ && currentProperPart_ != InvalidPixel) {
3411 currentProperPart_ = T_->nextProperPart_[currentProperPart_];
3412 }
3413 return *this;
3414 }
3415
3422 T_->checkProperPartIteratorVersion(expectedVersion_);
3423 return currentProperPart_;
3424 }
3425
3432 bool operator==(const ProperPartIterator& other) const { return currentProperPart_ == other.currentProperPart_; }
3433
3440 bool operator!=(const ProperPartIterator& other) const { return !(*this == other); }
3441 };
3442
3447 private:
3449 const MorphologicalTree* T_ = nullptr;
3451 PixelId firstProperPart_ = InvalidPixel;
3453 std::size_t expectedVersion_ = 0;
3454
3455 public:
3459 ProperPartRange() = default;
3460
3469 : T_(tree), firstProperPart_(firstProperPart), expectedVersion_(expectedVersion) {}
3470
3476 ProperPartIterator begin() const { return ProperPartIterator(T_, firstProperPart_, expectedVersion_); }
3477
3483 ProperPartIterator end() const { return ProperPartIterator(T_, InvalidPixel, expectedVersion_); }
3484 };
3485
3490 private:
3492 const MorphologicalTree* T_ = nullptr;
3494 std::vector<NodeId> nodeStack_;
3496 PixelId currentProperPart_ = InvalidPixel;
3498 std::size_t expectedTopologyVersion_ = 0;
3500 std::size_t expectedProperPartVersion_ = 0;
3501
3505 void checkVersions() const {
3506 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
3507 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
3508 }
3509
3515 void pushChildren(NodeId nodeId) {
3516 std::vector<NodeId> children;
3517 for (NodeId childId : T_->children(nodeId)) {
3518 children.push_back(childId);
3519 }
3520 for (auto it = children.rbegin(); it != children.rend(); ++it) {
3521 nodeStack_.push_back(*it);
3522 }
3523 }
3524
3528 void settle() {
3529 checkVersions();
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)];
3535 }
3536 }
3537
3538 public:
3540 using iterator_category = std::forward_iterator_tag;
3544 using difference_type = std::ptrdiff_t;
3546 using pointer = const PixelId*;
3548 using reference = const PixelId&;
3549
3554
3564 : T_(tree), expectedTopologyVersion_(expectedTopologyVersion), expectedProperPartVersion_(expectedProperPartVersion) {
3565 if (T_ && rootNodeId != InvalidNode) {
3566 nodeStack_.push_back(rootNodeId);
3567 settle();
3568 }
3569 }
3570
3577 checkVersions();
3578 if (currentProperPart_ != InvalidPixel) {
3579 currentProperPart_ = T_->nextProperPart_[static_cast<size_t>(currentProperPart_)];
3580 }
3581 settle();
3582 return *this;
3583 }
3584
3591 checkVersions();
3592 return currentProperPart_;
3593 }
3594
3601 bool operator==(const NodeSupportIterator& other) const { return currentProperPart_ == other.currentProperPart_; }
3602
3609 bool operator!=(const NodeSupportIterator& other) const { return !(*this == other); }
3610 };
3611
3616 private:
3618 const MorphologicalTree* T_ = nullptr;
3620 NodeId rootNodeId_ = InvalidNode;
3622 std::size_t expectedTopologyVersion_ = 0;
3624 std::size_t expectedProperPartVersion_ = 0;
3625
3626 public:
3630 NodeSupportRange() = default;
3631
3641 : T_(tree), rootNodeId_(rootNodeId), expectedTopologyVersion_(expectedTopologyVersion), expectedProperPartVersion_(expectedProperPartVersion) {}
3642
3648 NodeSupportIterator begin() const { return NodeSupportIterator(T_, rootNodeId_, expectedTopologyVersion_, expectedProperPartVersion_); }
3649
3656 };
3657
3662 private:
3664 struct Item {
3666 NodeId id;
3668 bool expanded;
3669 };
3671 const MorphologicalTree* T_ = nullptr;
3673 std::vector<Item> stack_;
3675 NodeId current_ = InvalidNode;
3677 std::size_t expectedVersion_ = 0;
3678
3682 void settle_() {
3683 T_->checkTopologyIteratorVersion(expectedVersion_);
3684 while (!stack_.empty()) {
3685 Item& top = stack_.back();
3686 if (!top.expanded) {
3687 top.expanded = true;
3688 // Reverse sibling links preserve child order without a temporary vector.
3689 for (NodeId child = T_->lastChild_[top.id]; child != InvalidNode; child = T_->prevSibling_[child]) {
3690 stack_.push_back({child, false});
3691 }
3692 } else {
3693 current_ = top.id;
3694 return;
3695 }
3696 }
3697 current_ = InvalidNode;
3698 }
3699
3700 public:
3702 using iterator_category = std::input_iterator_tag;
3706 using difference_type = std::ptrdiff_t;
3708 using pointer = const NodeId*;
3710 using reference = const NodeId&;
3711
3716
3724 PostOrderNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
3725 if (T_ && rootNodeId != InvalidNode) {
3726 stack_.push_back({rootNodeId, false});
3727 settle_();
3728 }
3729 }
3730
3737 T_->checkTopologyIteratorVersion(expectedVersion_);
3738 if (!stack_.empty()) {
3739 stack_.pop_back();
3740 settle_();
3741 }
3742 return *this;
3743 }
3744
3751 T_->checkTopologyIteratorVersion(expectedVersion_);
3752 return current_;
3753 }
3754
3761 bool operator==(const PostOrderNodeIterator& other) const { return current_ == other.current_; }
3762
3769 bool operator!=(const PostOrderNodeIterator& other) const { return !(*this == other); }
3770 };
3771
3776 private:
3778 const MorphologicalTree* T_ = nullptr;
3780 NodeId rootNodeId_ = InvalidNode;
3782 std::size_t expectedVersion_ = 0;
3783
3784 public:
3789
3798 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3799
3805 PostOrderNodeIterator begin() const { return PostOrderNodeIterator(T_, rootNodeId_, expectedVersion_); }
3806
3813 };
3814
3819 private:
3821 const MorphologicalTree* T_ = nullptr;
3823 FastQueue<NodeId> queue_;
3825 std::size_t expectedVersion_ = 0;
3826
3827 public:
3829 using iterator_category = std::input_iterator_tag;
3833 using difference_type = std::ptrdiff_t;
3835 using pointer = const NodeId*;
3837 using reference = const NodeId&;
3838
3843
3851 BreadthFirstNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
3852 if (T_ && rootNodeId != InvalidNode) {
3853 queue_.push(rootNodeId);
3854 }
3855 }
3856
3863 T_->checkTopologyIteratorVersion(expectedVersion_);
3864 if (!queue_.empty()) {
3865 NodeId current = queue_.pop();
3866 for (NodeId childId : T_->children(current)) {
3867 queue_.push(childId);
3868 }
3869 }
3870 return *this;
3871 }
3872
3879 T_->checkTopologyIteratorVersion(expectedVersion_);
3880 return queue_.front();
3881 }
3882
3889 bool operator==(const BreadthFirstNodeIterator& other) const { return queue_.empty() == other.queue_.empty(); }
3890
3897 bool operator!=(const BreadthFirstNodeIterator& other) const { return !(*this == other); }
3898 };
3899
3904 private:
3906 const MorphologicalTree* T_ = nullptr;
3908 NodeId rootNodeId_ = InvalidNode;
3910 std::size_t expectedVersion_ = 0;
3911
3912 public:
3917
3926 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3927
3933 BreadthFirstNodeIterator begin() const { return BreadthFirstNodeIterator(T_, rootNodeId_, expectedVersion_); }
3934
3941 };
3942
3947 private:
3949 const MorphologicalTree* T_ = nullptr;
3951 NodeId current_ = InvalidNode;
3953 std::size_t expectedVersion_ = 0;
3954
3955 public:
3957 using iterator_category = std::input_iterator_tag;
3961 using difference_type = std::ptrdiff_t;
3963 using pointer = const NodeId*;
3965 using reference = const NodeId&;
3966
3971
3980 : T_(tree), current_(current), expectedVersion_(expectedVersion) {}
3981
3988 T_->checkTopologyIteratorVersion(expectedVersion_);
3989 if (T_ && current_ != InvalidNode) {
3990 if (T_->isRoot(current_)) {
3991 current_ = InvalidNode;
3992 } else {
3993 current_ = T_->parent(current_);
3994 }
3995 }
3996 return *this;
3997 }
3998
4005 T_->checkTopologyIteratorVersion(expectedVersion_);
4006 return current_;
4007 }
4008
4015 bool operator==(const AncestorNodeIterator& other) const { return current_ == other.current_; }
4016
4023 bool operator!=(const AncestorNodeIterator& other) const { return !(*this == other); }
4024 };
4025
4030 private:
4032 const MorphologicalTree* T_ = nullptr;
4034 NodeId start_ = InvalidNode;
4036 std::size_t expectedVersion_ = 0;
4037
4038 public:
4043
4052 : T_(tree), start_(start), expectedVersion_(expectedVersion) {}
4053
4059 AncestorNodeIterator begin() const { return AncestorNodeIterator(T_, start_, expectedVersion_); }
4060
4067 };
4068
4073 private:
4075 const MorphologicalTree* T_ = nullptr;
4077 const std::vector<NodeId>* path_ = nullptr;
4079 std::size_t index_ = 0;
4081 std::size_t expectedVersion_ = 0;
4082
4083 public:
4085 using iterator_category = std::input_iterator_tag;
4089 using difference_type = std::ptrdiff_t;
4091 using pointer = const NodeId*;
4093 using reference = const NodeId&;
4094
4099
4108 PathBetweenNodesIterator(const MorphologicalTree* tree, const std::vector<NodeId>* path, std::size_t index, std::size_t expectedVersion)
4109 : T_(tree), path_(path), index_(index), expectedVersion_(expectedVersion) {}
4110
4117 T_->checkTopologyIteratorVersion(expectedVersion_);
4118 if (path_ && index_ < path_->size()) {
4119 ++index_;
4120 }
4121 return *this;
4122 }
4123
4130 T_->checkTopologyIteratorVersion(expectedVersion_);
4131 return (*path_)[index_];
4132 }
4133
4140 bool operator==(const PathBetweenNodesIterator& other) const { return path_ == other.path_ && index_ == other.index_; }
4141
4148 bool operator!=(const PathBetweenNodesIterator& other) const { return !(*this == other); }
4149 };
4150
4155 private:
4157 const MorphologicalTree* T_ = nullptr;
4159 std::vector<NodeId> path_;
4161 std::size_t expectedVersion_ = 0;
4162
4171 static std::vector<NodeId> buildPath(const MorphologicalTree* tree, NodeId sourceNodeId, NodeId targetNodeId) {
4172 if (tree == nullptr || !tree->isAlive(sourceNodeId) || !tree->isAlive(targetNodeId)) {
4173 return {};
4174 }
4175
4176 auto componentAnchor = [tree](NodeId nodeId) {
4177 NodeId currentNodeId = nodeId;
4178 while (true) {
4179 const NodeId parentNodeId = tree->parent(currentNodeId);
4181 return currentNodeId;
4182 }
4184 }
4185 };
4186
4188 return {};
4189 }
4190
4192 if (lcaNodeId == InvalidNode) {
4193 return {};
4194 }
4195
4196 std::vector<NodeId> path;
4198 path.push_back(currentNodeId);
4199 if (currentNodeId == lcaNodeId) {
4200 break;
4201 }
4202
4203 const NodeId parentNodeId = tree->parent(currentNodeId);
4205 return {};
4206 }
4207 }
4208
4209 std::vector<NodeId> descendingTail;
4211 descendingTail.push_back(currentNodeId);
4212
4213 const NodeId parentNodeId = tree->parent(currentNodeId);
4215 return {};
4216 }
4217 }
4218
4219 path.insert(path.end(), descendingTail.rbegin(), descendingTail.rend());
4220 return path;
4221 }
4222
4223 public:
4228
4238 : T_(tree), path_(buildPath(tree, sourceNodeId, targetNodeId)), expectedVersion_(expectedVersion) {}
4239
4245 PathBetweenNodesIterator begin() const { return PathBetweenNodesIterator(T_, &path_, 0, expectedVersion_); }
4246
4252 PathBetweenNodesIterator end() const { return PathBetweenNodesIterator(T_, &path_, path_.size(), expectedVersion_); }
4253 };
4254
4259 private:
4261 const MorphologicalTree* T_ = nullptr;
4263 std::vector<NodeId> stack_;
4265 std::size_t expectedVersion_ = 0;
4266
4267 public:
4269 using iterator_category = std::input_iterator_tag;
4273 using difference_type = std::ptrdiff_t;
4275 using pointer = const NodeId*;
4277 using reference = const NodeId&;
4278
4283
4291 SubtreeNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
4292 if (T_ && rootNodeId != InvalidNode) {
4293 stack_.push_back(rootNodeId);
4294 }
4295 }
4296
4303 T_->checkTopologyIteratorVersion(expectedVersion_);
4304 if (!stack_.empty()) {
4305 NodeId current = stack_.back();
4306 stack_.pop_back();
4307 // Push in reverse sibling order so the first child is visited next.
4308 for (NodeId child = T_->lastChild_[current]; child != InvalidNode; child = T_->prevSibling_[child]) {
4309 stack_.push_back(child);
4310 }
4311 }
4312 return *this;
4313 }
4314
4321 T_->checkTopologyIteratorVersion(expectedVersion_);
4322 return stack_.back();
4323 }
4324
4331 bool operator==(const SubtreeNodeIterator& other) const { return stack_.empty() == other.stack_.empty(); }
4332
4339 bool operator!=(const SubtreeNodeIterator& other) const { return !(*this == other); }
4340 };
4341
4346 private:
4348 const MorphologicalTree* T_ = nullptr;
4350 NodeId rootNodeId_ = InvalidNode;
4352 std::size_t expectedVersion_ = 0;
4353
4354 public:
4358 SubtreeNodeRange() = default;
4359
4368 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
4369
4375 SubtreeNodeIterator begin() const { return SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_); }
4376
4383 };
4384
4389 private:
4391 const MorphologicalTree* T_ = nullptr;
4393 NodeId rootNodeId_ = InvalidNode;
4395 std::size_t expectedVersion_ = 0;
4396
4397 public:
4402
4411 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
4412
4419 auto it = SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_);
4420 ++it;
4421 return it;
4422 }
4423
4430 };
4431};
4432
4433} // namespace mmcfilters
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
Definition Common.hpp:43
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
Immutable scientific metadata attached to a morphological tree.
std::variant< NoConstructionContext, SharedAdjacencyContext, TopographicConvention > MorphologicalTreeConstructionContext
Tagged construction context retained by morphological-tree semantics.
NodeAltitudeOrder
Global ordering constraint of node altitudes along parent-child arcs.
MorphologicalTreeKind
Declared construction family of one morphological tree.
void validateMorphologicalTreeSemantics(const MorphologicalTreeSemantics &semantics)
Validates the scientific compatibility of a semantics descriptor.
Public construction facade for all high-level morphological trees.
Iterator over live node ids in the dense internal-node domain.
AliveNodeIterator(const MorphologicalTree *tree, NodeId current, NodeId end, std::size_t expectedVersion)
Creates an iterator over [current, end) with fail-fast version checking.
AliveNodeIterator & operator++()
Advances to the next live node slot.
NodeId operator*() const
Returns the current live node id.
bool operator!=(const AliveNodeIterator &other) const
Compares iterator positions for inequality.
AliveNodeIterator()=default
Creates the end/sentinel iterator.
bool operator==(const AliveNodeIterator &other) const
Compares iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for iterating over live node ids.
AliveNodeRange(const MorphologicalTree *tree, NodeId begin, NodeId end, std::size_t expectedVersion)
Creates a fail-fast live-node range over dense slots.
AliveNodeIterator begin() const
Returns an iterator positioned at the first live slot.
AliveNodeIterator end() const
Returns the sentinel iterator for the live-node range.
AliveNodeRange()=default
Creates an empty live-node range.
Iterator that walks from a node towards the root.
AncestorNodeIterator()=default
Creates the end/sentinel rootward-path iterator.
AncestorNodeIterator & operator++()
Advances one step toward the root.
AncestorNodeIterator(const MorphologicalTree *tree, NodeId current, std::size_t expectedVersion)
Creates an iterator starting at one node and walking to the root.
bool operator==(const AncestorNodeIterator &other) const
Compares path-to-root iterator positions.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator!=(const AncestorNodeIterator &other) const
Compares path-to-root iterator positions for inequality.
NodeId operator*() const
Returns the current node on the rootward path.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
Range wrapper for rootward path traversal.
AncestorNodeRange()=default
Creates an empty rootward-path range.
AncestorNodeIterator begin() const
Returns an iterator at the path start node.
AncestorNodeIterator end() const
Returns the rootward-path range sentinel.
AncestorNodeRange(const MorphologicalTree *tree, NodeId start, std::size_t expectedVersion)
Creates a range from start to the connected root.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator==(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state.
bool operator!=(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state for inequality.
BreadthFirstNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first iterator rooted at rootNodeId.
BreadthFirstNodeIterator & operator++()
Advances to the next node in breadth-first order.
BreadthFirstNodeIterator()=default
Creates the end/sentinel breadth-first iterator.
NodeId operator*() const
Returns the current breadth-first node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for breadth-first subtree traversal.
BreadthFirstNodeIterator end() const
Returns the breadth-first range sentinel.
BreadthFirstNodeIterator begin() const
Returns an iterator at the first breadth-first node.
BreadthFirstNodeRange()=default
Creates an empty breadth-first range.
BreadthFirstNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first range rooted at rootNodeId.
Iterator over the direct children of one node.
ChildrenIterator()=default
Creates the end/sentinel child iterator.
ChildrenIterator(const MorphologicalTree *tree, NodeId currentLocal, std::size_t expectedVersion)
Creates a child iterator starting from a linked-list slot.
bool operator==(const ChildrenIterator &other) const
Compares child iterator positions.
NodeId operator*() const
Returns the current child node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const ChildrenIterator &other) const
Compares child iterator positions for inequality.
ChildrenIterator & operator++()
Advances to the next sibling in the child list.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for direct-child iteration.
ChildrenIterator end() const
Returns the child-range sentinel iterator.
ChildrenIterator begin() const
Returns an iterator at the first child.
ChildrenRange()=default
Creates an empty child range.
ChildrenRange(const MorphologicalTree *tree, NodeId firstLocal, std::size_t expectedVersion)
Creates a range over a linked list of direct children.
Range wrapper over the proper descendants of one node.
DescendantNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a range over proper descendants of rootNodeId.
DescendantNodeRange()=default
Creates an empty descendant range.
SubtreeNodeIterator begin() const
Returns an iterator at the first proper descendant.
SubtreeNodeIterator end() const
Returns the descendant range sentinel.
Iterator over all pixels in one node support.
PixelId operator*() const
Returns the current pixel identifier.
bool operator==(const NodeSupportIterator &other) const
Compares node-support iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeSupportIterator & operator++()
Advances to the next pixel in the node support.
NodeSupportIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates an iterator over every proper part in a rooted subtree.
bool operator!=(const NodeSupportIterator &other) const
Compares node-support iterator positions for inequality.
NodeSupportIterator()=default
Creates the end/sentinel node-support iterator.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for node-support pixel iteration.
NodeSupportRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates a range over all proper parts in the subtree of rootNodeId.
NodeSupportIterator end() const
Returns the node-support range sentinel.
NodeSupportRange()=default
Creates an empty node-support range.
NodeSupportIterator begin() const
Returns an iterator at the first pixel in the node support.
Iterator over a materialised path between two nodes.
bool operator==(const PathBetweenNodesIterator &other) const
Compares path iterator positions.
PathBetweenNodesIterator()=default
Creates the end/sentinel path-between-nodes iterator.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
PathBetweenNodesIterator & operator++()
Advances to the next node in the materialised path.
bool operator!=(const PathBetweenNodesIterator &other) const
Compares path iterator positions for inequality.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeId operator*() const
Returns the current node id in the materialised path.
PathBetweenNodesIterator(const MorphologicalTree *tree, const std::vector< NodeId > *path, std::size_t index, std::size_t expectedVersion)
Creates an iterator over a materialised node path.
Range wrapper for the path connecting two nodes in the same component.
PathBetweenNodesIterator begin() const
Returns an iterator at the first node in the materialised path.
PathBetweenNodesRange()=default
Creates an empty path-between-nodes range.
PathBetweenNodesIterator end() const
Returns the materialised path range sentinel.
PathBetweenNodesRange(const MorphologicalTree *tree, NodeId sourceNodeId, NodeId targetNodeId, std::size_t expectedVersion)
Materialises and owns the path between two nodes.
bool operator==(const PostOrderNodeIterator &other) const
Compares post-order iterator positions.
PostOrderNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order iterator rooted at rootNodeId.
PostOrderNodeIterator & operator++()
Advances to the next node in post-order.
NodeId operator*() const
Returns the current post-order node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const PostOrderNodeIterator &other) const
Compares post-order iterator positions for inequality.
PostOrderNodeIterator()=default
Creates the end/sentinel post-order iterator.
Range wrapper for post-order subtree traversal.
PostOrderNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order range rooted at rootNodeId.
PostOrderNodeIterator begin() const
Returns an iterator at the first post-order node.
PostOrderNodeIterator end() const
Returns the post-order range sentinel.
PostOrderNodeRange()=default
Creates an empty post-order range.
Iterator over the pixels in one node's proper part.
ProperPartIterator & operator++()
Advances to the next pixel in the proper part.
ProperPartIterator(const MorphologicalTree *tree, PixelId currentProperPart, std::size_t expectedVersion)
Creates a proper-part iterator starting from a linked-list entry.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const ProperPartIterator &other) const
Compares proper-part iterator positions for inequality.
ProperPartIterator()=default
Creates the end/sentinel proper-part iterator.
PixelId operator*() const
Returns the current pixel identifier.
bool operator==(const ProperPartIterator &other) const
Compares proper-part iterator positions.
Range wrapper for direct proper-part iteration.
ProperPartRange()=default
Creates an empty direct proper-part range.
ProperPartIterator begin() const
Returns an iterator at the first direct proper part.
ProperPartRange(const MorphologicalTree *tree, PixelId firstProperPart, std::size_t expectedVersion)
Creates a range over one node's direct proper-part list.
ProperPartIterator end() const
Returns the direct proper-part range sentinel.
Depth-first iterator over a subtree in pre-order.
SubtreeNodeIterator & operator++()
Advances to the next node in pre-order subtree traversal.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator!=(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state for inequality.
bool operator==(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state.
SubtreeNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order subtree iterator rooted at rootNodeId.
NodeId operator*() const
Returns the current pre-order subtree node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
SubtreeNodeIterator()=default
Creates the end/sentinel subtree iterator.
Range wrapper for pre-order subtree traversal.
SubtreeNodeIterator end() const
Returns the subtree range sentinel.
SubtreeNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order range over the subtree rooted at rootNodeId.
SubtreeNodeRange()=default
Creates an empty subtree range.
SubtreeNodeIterator begin() const
Returns an iterator at the subtree root.
Mutable connected-subset tree on a finite pixel domain.
int numRows() const
Returns the number of rows in the regular 2D pixel domain.
void validateTreeOfPartialPartitions() const
Validates the connected-subset invariants and the non-empty proper-part specialization.
const TreeEditValidationStatistics & getEditValidationStatistics() const noexcept
Returns validation-path counters for committed edit sessions.
const SharedAdjacencyContext * sharedAdjacencyContext() const noexcept
Returns the shared-adjacency context, or nullptr.
ProperPartRange properPart(NodeId nodeId) const
Returns a fail-fast range over the pixels in the proper part of nodeId.
int numNodes() const noexcept
Returns the number of currently live nodes.
int numInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
bool isNode(NodeId nodeId) const noexcept
Tests whether nodeId belongs to the internal-node id domain.
SubtreeNodeRange subtreeNodes(NodeId nodeId) const
Returns a pre-order traversal range over the subtree of nodeId.
TreeEditor edit()
Opens the only public entrypoint for staged structural mutations.
PostOrderNodeRange postOrder() const
Returns a post-order traversal range rooted at the connected root.
const ComplementaryAdjacencies * complementaryAdjacencies() const noexcept
Returns the complementary adjacencies explicitly retained by a topographic immersion.
bool isLeaf(NodeId nodeId) const
Tests whether nodeId has no direct children.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
MorphologicalTree(MorphologicalTree &&other)
Moves a complete committed topology.
bool isStrictComparable(NodeId u, NodeId v) const
Tests whether u and v are strictly comparable in the ancestry order.
int properPartCardinality(NodeId nodeId) const
Returns the cardinality of the proper part of nodeId.
NodeId getNextSibling(NodeId nodeId) const
Returns the next sibling of nodeId, or InvalidNode.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
bool hasGridDomain2D() const noexcept
Tests whether pixel ids have an attached row/column layout.
const MorphologicalTreeSemantics & semantics() const noexcept
Returns all generic semantic capabilities of this hierarchy.
int getNumFreeNodeSlots() const
Returns the number of currently reusable node slots.
NodeAltitudeOrder nodeAltitudeOrder() const noexcept
Returns the global parent-to-child altitude ordering constraint.
void validateConnectedRootedTree() const
Validates one rooted tree with a complete smallest-node map and non-empty supports.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > smallestNodeMap, NodeId root, MorphologicalTreeSemantics semantics)
Imports a native hierarchy over an abstract finite pixel set.
MorphologicalTree & operator=(const MorphologicalTree &)=delete
Copy assignment is disabled to keep topology storage explicit.
PostOrderNodeRange postOrder(NodeId rootNodeId) const
Returns a post-order traversal range rooted at rootNodeId.
NodeId getHigraNodeId(NodeId nodeId) const noexcept
Returns the preserved imported Higra node id for one live tree node.
NodeSupportRange nodeSupport(NodeId nodeId) const
Returns a fail-fast range over the pixels in the support of nodeId.
int numDescendants(NodeId nodeId) const
Returns the number of internal descendants of nodeId.
bool isComparable(NodeId u, NodeId v) const
Tests whether u and v are comparable in the ancestry order.
bool isEditing() const noexcept
Tests whether a staged edit session is currently open.
MorphologicalTree clone() const
Creates an independent copy of the structural tree state.
int numSiblings(NodeId nodeId) const
Returns the number of siblings of nodeId.
void pruneNode(NodeId nodeId)
Prunes the subtree of nodeId, moving all its support to the parent.
int numPixels() const
Returns the cardinality of the pixel domain.
DescendantNodeRange descendants(NodeId nodeId) const
Returns a range over all proper descendants of nodeId.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
int getNumHigraNodes() const
Returns the size of the preserved imported Higra node-id domain.
std::vector< NodeId > leaves() const
Returns all live leaf nodes in the current hierarchy.
const TopographicConvention * topographicConvention() const noexcept
Returns the topographic convention, or nullptr.
bool isTreeOfPartialPartitions() const
Tests whether every live node has a non-empty proper part.
AncestorNodeRange ancestors(NodeId nodeId) const
Returns nodeId and its proper ancestors through the connected root.
bool isPixel(PixelId pixel) const noexcept
Tests whether pixel belongs to the pixel domain.
int dfsExitIndex(NodeId nodeId) const
Returns the zero-based DFS exit-event index of nodeId.
const RegularGridAdjacency2D * constructionAdjacency() const noexcept
Returns the adjacency retained by a shared construction context.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
int dfsEntryIndex(NodeId nodeId) const
Returns the zero-based DFS entry-event index of nodeId.
const MorphologicalTreeConstructionContext & constructionContext() const noexcept
Returns the typed construction context retained by this tree.
bool hasChild(NodeId parentNodeId, NodeId childId) const
Tests whether childId is a direct child of parentNodeId.
BreadthFirstNodeRange breadthFirstTraversal(NodeId rootNodeId) const
Returns a breadth-first traversal range rooted at rootNodeId.
int numLeafNodes() const
Counts the live nodes that currently have no children.
bool isStrictAncestor(NodeId u, NodeId v) const
Tests whether u is a strict ancestor of v.
PathBetweenNodesRange getPathBetweenNodes(NodeId sourceNodeId, NodeId targetNodeId) const
Returns the path that connects sourceNodeId and targetNodeId.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > smallestNodeMap, NodeId root, int rows, int columns, MorphologicalTreeSemantics semantics)
Tag-protected import from native mmcfilters topology buffers.
std::span< const NodeId > smallestNodeMap() const noexcept
Returns the pixel-indexed smallest-node map.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
NodeId smallestNode(PixelId pixel) const
Returns the smallest node containing pixel.
MorphologicalTreeKind kind() const noexcept
Returns the optional descriptive hierarchy-family label.
int getNodeIdSpaceSize(NodeIdSpace outputSpace) const
Returns the size of the requested node-id domain.
int numChildren(NodeId nodeId) const
Returns the number of direct children of nodeId.
BreadthFirstNodeRange breadthFirstTraversal() const
Returns a breadth-first traversal range rooted at the connected root.
bool hasDetachedAliveNodes() const noexcept
Returns whether the tree currently contains alive detached nodes.
TreeValidationResult validateConnectedRootedTreeResult() const noexcept
Runs strong validation and returns the result instead of throwing.
virtual ~MorphologicalTree()=default
Destroys the topology storage and cached traversal state.
bool isDescendant(NodeId u, NodeId v) const
Tests whether u is a descendant of v.
bool isAncestor(NodeId u, NodeId v) const
Tests whether u is an ancestor of v.
std::vector< NodeId > lowestCommonAncestors(std::span< const std::pair< NodeId, NodeId > > queries) const
Returns lowest common ancestors for a batch of node pairs.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
NodeId lowestCommonAncestor(NodeId u, NodeId v) const
Returns the lowest common ancestor of u and v.
const GridDomain2D & requireGridDomain2D(const char *context) const
Returns the regular 2D domain or rejects a geometry-dependent call.
ChildrenRange children(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
NodeId getFirstChild(NodeId nodeId) const
Returns the first direct child of nodeId, or InvalidNode.
NodeId parent(NodeId nodeId) const
Returns the direct parent of nodeId.
const std::optional< GridDomain2D > & gridDomain2D() const noexcept
Returns the optional regular 2D pixel domain.
bool hasEmptyProperPart(NodeId nodeId) const
Tests whether nodeId has an empty proper part.
MorphologicalTree & operator=(MorphologicalTree &&other)
Move-assigns a complete committed topology.
friend ValuedMorphologicalTree< T > detail::materializeNativeHierarchy(NativeHierarchyOwned< T > &&hierarchy, std::optional< NodeId > preservedExternalNodeIdOffset, detail::MaterializedVersionPolicy versionPolicy)
Allows the native hierarchy materializer to initialize owned topology buffers and construction metada...
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
bool isStrictDescendant(NodeId u, NodeId v) const
Tests whether u is a strict descendant of v.
MorphologicalTree(const MorphologicalTree &)=delete
Copying is disabled to keep topology storage explicit.
NodeId root() const
Returns the current hierarchy root.
Immutable regular-grid 2D adjacency with allocation-free traversal.
Thin edit-session facade for multi-step topology updates.
Ordered pair of complementary minimum and maximum adjacencies.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.
AttributeNames first
Layout used to interpret second; kept public for tuple-like access.
Shape metadata optionally attached to the pixel domain.
Immutable scientific interpretation attached to one morphological tree.
NodeAltitudeOrder nodeAltitudeOrder
Global parent-child altitude order.
MorphologicalTreeConstructionContext constructionContext
Typed retained construction context.
MorphologicalTreeKind kind
Declared construction/result kind.
Records one adjacency shared by both construction polarities.
Complete discrete convention retained by a tree-of-shapes result.
TreeOfShapesImmersion immersion
Selected topographic immersion.
Counts committed edit sessions by validation strategy.
std::size_t incrementalValidationCommits
Number of commits checked with incremental proof validation.
std::size_t completeValidationCommits
Number of commits checked with complete validation.
Non-throwing validation result returned by edit-session checks.
std::string message
Human-readable diagnostic, especially useful when ok is false.