diff --git a/README.md b/README.md index 6480dfb..80a8735 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ In: Huber, K. and Gusfield, D. (eds.) Proceedings of WABI 2019. LIPIcs. 143, Sch ## Requirements -For the main program, there are no strict dependencies other than C++ version 14. +For the main program, there are no strict dependencies other than C++ version 17. To read in **compressed** fasta/fastq files, it could be necessary to install zlib: ``` sudo apt install libz-dev @@ -129,6 +129,7 @@ Specify your input by `-i ` where `` is either a file-of-files or in If you want a **tree**, use `-f strict`. In this case, `-N ` can be used to write the resulting tree into a newick file; instead or additionally to `-o `. If you want a **network**, use one of the following filters: * `weakly`: a split is kept if it is weakly compatible to all previously filtered splits (see publication for definition of "weak compatibility"). +* `planar`: a split is kept if the resulting set of splits can be displayed in the plain without any edges crossing (a.k.a. circular compatible, outer-labeled planar) * `2-tree`: two sets of compatible splits (=trees) are maintained. A split is added to the first if possible (compatible); if not to the second if possible. `3-tree`: three sets of compatible splits (=trees) are maintained. A split is added to the first if possible (compatible); if not to the second if possible; if not to the third if possible. @@ -348,6 +349,7 @@ SANS is provided as a service of the [German Network for Bioinformatics Infrastr * The sparse-map library is licensed under the [MIT license](https://github.com/Tessil/sparse-map/blob/master/LICENSE). * The Bifrost library is licensed under the [BSD-2 license](https://github.com/pmelsted/bifrost/blob/master/LICENSE). +* The [PC-tree library](https://github.com/N-Coder/pc-tree) is licensed under the [OGDF license](https://github.com/N-Coder/pc-tree/blob/main/LICENSE.txt). * SANS uses gzstream, licensed under the [LGPL license](/src/gz/COPYING.LIB). * SANS is licensed under the [GNU general public license](/LICENSE). diff --git a/makefile b/makefile index a2b636d..038185d 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ # MAX. K-MER LENGTH, NUMBER OF FILES -CC = g++ -O3 -march=native -DmaxK=64 -DmaxN=64 -std=c++14 +CC = g++ -O3 -march=native -DmaxK=64 -DmaxN=64 -std=c++17 XX = -lpthread -lz ## IF DEBUG @@ -16,6 +16,7 @@ CFLAGS = gcc -O3 -march=native SRCDIR := src BUILDDIR := obj + # Wrap Windows / Unix commands ifeq ($(OS), Windows_NT) TD = $(BUILDDIR) @@ -39,9 +40,9 @@ endif all: makefile start SANS done SANS: makefile $(BUILDDIR)/main.o - $(CC) -o SANS $(BUILDDIR)/nexus_color.o $(BUILDDIR)/main.o $(BUILDDIR)/graph.o $(BUILDDIR)/kmer.o $(BUILDDIR)/kmerAmino.o $(BUILDDIR)/color.o $(BUILDDIR)/util.o $(BUILDDIR)/translator.o $(BUILDDIR)/cleanliness.o $(BUILDDIR)/gzstream.o $(XX) + $(CC) -o SANS $(BUILDDIR)/nexus_color.o $(BUILDDIR)/main.o $(BUILDDIR)/graph.o $(BUILDDIR)/kmer.o $(BUILDDIR)/kmerAmino.o $(BUILDDIR)/color.o $(BUILDDIR)/util.o $(BUILDDIR)/translator.o $(BUILDDIR)/cleanliness.o $(BUILDDIR)/gzstream.o $(BUILDDIR)/PCTree_basic.o $(BUILDDIR)/PCTree_construction.o $(BUILDDIR)/PCTreeForest.o $(BUILDDIR)/PCTree_restriction.o $(BUILDDIR)/PCTree_intersect.o $(BUILDDIR)/PCNode.o $(XX) -$(BUILDDIR)/main.o: makefile $(SRCDIR)/main.cpp $(SRCDIR)/main.h $(BUILDDIR)/color.o $(BUILDDIR)/translator.o $(BUILDDIR)/graph.o $(BUILDDIR)/util.o $(BUILDDIR)/cleanliness.o $(BUILDDIR)/gzstream.o $(BUILDDIR)/nexus_color.o +$(BUILDDIR)/main.o: makefile $(SRCDIR)/main.cpp $(SRCDIR)/main.h $(BUILDDIR)/color.o $(BUILDDIR)/translator.o $(BUILDDIR)/graph.o $(BUILDDIR)/util.o $(BUILDDIR)/cleanliness.o $(BUILDDIR)/gzstream.o $(BUILDDIR)/nexus_color.o $(BUILDDIR)/PCTree_construction.o $(BUILDDIR)/PCTree_basic.o $(BUILDDIR)/PCTreeForest.o $(BUILDDIR)/PCTree_restriction.o $(BUILDDIR)/PCTree_intersect.o $(BUILDDIR)/PCNode.o $(CC) -c $(SRCDIR)/main.cpp -o $(BUILDDIR)/main.o $(BUILDDIR)/graph.o: makefile $(SRCDIR)/graph.cpp $(SRCDIR)/graph.h $(BUILDDIR)/kmer.o $(BUILDDIR)/kmerAmino.o $(BUILDDIR)/color.o @@ -56,7 +57,7 @@ $(BUILDDIR)/kmerAmino.o: makefile $(SRCDIR)/kmerAmino.cpp $(SRCDIR)/kmerAmino.h $(BUILDDIR)/color.o: makefile $(SRCDIR)/color.cpp $(SRCDIR)/color.h $(CC) -c $(SRCDIR)/color.cpp -o $(BUILDDIR)/color.o -$(BUILDDIR)/nexus_color.o: makefile $(SRCDIR)/nexus_color.cpp $(SRCDIR)/nexus_color.h +$(BUILDDIR)/nexus_color.o: $(SRCDIR)/nexus_color.cpp $(SRCDIR)/nexus_color.h $(CC) -c $(SRCDIR)/nexus_color.cpp -o $(BUILDDIR)/nexus_color.o $(BUILDDIR)/util.o: $(SRCDIR)/util.cpp $(SRCDIR)/util.h @@ -72,6 +73,29 @@ $(BUILDDIR)/gzstream.o: $(SRCDIR)/gz/gzstream.C $(SRCDIR)/gz/gzstream.h $(CFLAGS) -c $(SRCDIR)/gz/gzstream.C -o $(BUILDDIR)/gzstream.o +# PC-Tree + + +$(BUILDDIR)/PCNode.o: $(SRCDIR)/pctree/PCNode.cpp $(SRCDIR)/pctree/PCNode.h $(BUILDDIR)/PCTreeForest.o + $(CC) -c $(SRCDIR)/pctree/PCNode.cpp -o $(BUILDDIR)/PCNode.o + +$(BUILDDIR)/PCTree_basic.o: $(SRCDIR)/pctree/PCTree_basic.cpp $(BUILDDIR)/PCNode.o + $(CC) -c $(SRCDIR)/pctree/PCTree_basic.cpp -o $(BUILDDIR)/PCTree_basic.o + +$(BUILDDIR)/PCTree_construction.o: $(SRCDIR)/pctree/PCTree_construction.cpp $(BUILDDIR)/PCNode.o + $(CC) -c $(SRCDIR)/pctree/PCTree_construction.cpp -o $(BUILDDIR)/PCTree_construction.o + +$(BUILDDIR)/PCTreeForest.o: $(SRCDIR)/pctree/PCTreeForest.cpp $(SRCDIR)/pctree/PCTreeForest.h + $(CC) -c $(SRCDIR)/pctree/PCTreeForest.cpp -o $(BUILDDIR)/PCTreeForest.o + +$(BUILDDIR)/PCTree_intersect.o: $(SRCDIR)/pctree/PCTree_intersect.cpp + $(CC) -c $(SRCDIR)/pctree/PCTree_intersect.cpp -o $(BUILDDIR)/PCTree_intersect.o + +$(BUILDDIR)/PCTree_restriction.o: $(SRCDIR)/pctree/PCTree_restriction.cpp $(BUILDDIR)/PCNode.o + $(CC) -c $(SRCDIR)/pctree/PCTree_restriction.cpp -o $(BUILDDIR)/PCTree_restriction.o + + + # [Internal rules] # Print info at compile start diff --git a/src/graph.cpp b/src/graph.cpp index c267ce6..83f3bf1 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -1,5 +1,6 @@ #include "graph.h" #include "util.h" +#include "pctree/PCTree.h" #include #include #include @@ -1727,6 +1728,58 @@ void graph::filter_weakly(multimap_& split_list, bool& verbose) } } +/** + * This function filters a planar graph compatible subset. + * + * @param split_list list of splits to be filtered + * @param verbose print progress + */ +void graph::filter_planar(multimap_& split_list, bool& verbose) { + + auto it = split_list.begin(); + color_t split_copy; + + // single bit of split + int state = 0; + + // construct PC-Tree with maxN nodes + vector leaves; + pc_tree::PCTree tree(maxN, &leaves); + + +loop: + while(it != split_list.end()) { + + vector consecutiveLeaves = {}; + + // current split + split_copy = it->second; + + // read all bits of the current split + for(int j=0;j>= 01u; + + //collect all leaves which should be consecutive + if (state == 1) { + consecutiveLeaves.push_back(leaves.at(j)); + } + } + + // if possible, insert the new split in pc-tree + if (tree.makeConsecutive(consecutiveLeaves)) { + ++it; goto loop; + } + + // delete the split, if it is not compatible to the pc-tree + it = split_list.erase(it); + + + } + +} + + /** * This function filters a greedy maximum weight n-tree compatible subset and returns a string with all trees in newick format. * diff --git a/src/graph.h b/src/graph.h index b23fcca..cf2b26e 100644 --- a/src/graph.h +++ b/src/graph.h @@ -455,6 +455,14 @@ class graph { */ static void filter_weakly(multimap_& split_list, bool& verbose); + /** + * This function filters a planar graph compatible subset. + * @param split_list list of splits to be filtered + * @param verbose print progress + */ + static void filter_planar(multimap_& split_list, bool& verbose); + + /** * This function filters a greedy maximum weight n-tree compatible subset. * diff --git a/src/main.cpp b/src/main.cpp index e3ab552..e6dafe6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,6 +88,8 @@ int main(int argc, char* argv[]) { // cout << " \t filtered splits w.r.t. original splits" << endl; cout << " \t options: strict: compatible to a tree" << endl; cout << " \t weakly: weakly compatible network" << endl; + cout << " \t planar: compatible to a planar graph" << endl; + cout << " \t (a.k.a. circular compatible, outer labeled planar)" << endl; cout << " \t n-tree: compatible to a union of n trees" << endl; cout << " \t (where n is an arbitrary number, e.g. 2-tree)" << endl; cout << endl; @@ -382,7 +384,7 @@ int main(int argc, char* argv[]) { else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--filter") == 0) { catch_missing_dependent_args(argv[i + 1], argv[i]); filter = argv[++i]; // Filter a greedy maximum weight subset - if (filter == "strict" || filter == "tree") { + if (filter == "strict" || filter == "tree" | filter == "planar") { // compatible to a tree } else if (filter == "weakly") { @@ -1737,6 +1739,9 @@ void apply_filter(string filter, string newick, std::function + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "util/RegisteredArray.h" +#include "util/RegisteredSet.h" +#include "util/defines.h" + +#include + +namespace pc_tree { +enum class OGDF_EXPORT NodeLabel { Unknown, Partial, Full, Empty = Unknown }; + +enum class OGDF_EXPORT PCNodeType { PNode, CNode, Leaf }; + +class OGDF_EXPORT PCTree; + +class OGDF_EXPORT PCTreeForest; + +class OGDF_EXPORT PCTreeRegistry; + +class OGDF_EXPORT PCNode; + +#define OGDF_DECL_REG_ARRAY_TYPE(v, c) pc_tree::RegisteredArray +OGDF_DECL_REG_ARRAY(PCTreeNodeArray) +#undef PCTREE_DECL_REG_ARRAY_TYPE + +template +using PCTreeNodeSet = pc_tree::RegisteredSet; + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, pc_tree::NodeLabel); + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, pc_tree::PCNodeType); + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, const pc_tree::PCTree*); + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, const pc_tree::PCNode*); + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, const pc_tree::PCTree&); + +OGDF_EXPORT std::ostream& operator<<(std::ostream&, const pc_tree::PCNode&); +} diff --git a/src/pctree/PCNode.cpp b/src/pctree/PCNode.cpp new file mode 100644 index 0000000..2cba7cd --- /dev/null +++ b/src/pctree/PCNode.cpp @@ -0,0 +1,591 @@ +/** \file + * \brief Implementation for pc_tree::PCNode + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCNode.h" +#include "PCTree.h" +#include "PCTreeForest.h" +#include "PCTreeIterators.h" + +using namespace pc_tree; + +void PCNode::appendChild(PCNode* child, bool begin) { + OGDF_ASSERT(child != nullptr); + OGDF_ASSERT(m_forest == child->m_forest); + OGDF_ASSERT(this != child); + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(child->isValidNode(m_forest)); + child->setParent(this); + m_childCount++; + if (m_child1 == nullptr) { + // new child of node without other children + OGDF_ASSERT(m_child2 == nullptr); + m_child1 = m_child2 = child; + } else { + // append new child + PCNode*& outerChild = begin ? m_child1 : m_child2; + outerChild->replaceSibling(nullptr, child); + child->replaceSibling(nullptr, outerChild); + outerChild = child; + } + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(child->isValidNode(m_forest)); +} + +void PCNode::insertBetween(PCNode* sib1, PCNode* sib2) { + if (sib1 == nullptr && sib2 != nullptr) { + insertBetween(sib2, sib1); + return; + } + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(sib1 != nullptr); + OGDF_ASSERT(sib1->m_forest == m_forest); + OGDF_ASSERT(sib1->isValidNode(m_forest)); + PCNode* parent = sib1->getParent(); + if (sib2 == nullptr) { + // append at one end of the list + } else if (sib1->isSiblingOf(sib2)) { + // insert within one list + OGDF_ASSERT(parent != nullptr); + OGDF_ASSERT(parent->isParentOf(sib2)); + if (sib1->isSiblingAdjacent(sib2)) { + // normal case, both nodes are adjacent children of the same parent + OGDF_ASSERT(sib2->isValidNode(m_forest)); + OGDF_ASSERT(parent->isValidNode(m_forest)); + setParent(parent); + parent->m_childCount++; + sib1->replaceSibling(sib2, this); + sib2->replaceSibling(sib1, this); + this->replaceSibling(nullptr, sib1); + this->replaceSibling(nullptr, sib2); + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(parent->isValidNode(m_forest)); + OGDF_ASSERT(sib1->isValidNode(m_forest)); + OGDF_ASSERT(sib2->isValidNode(m_forest)); + return; + } else { + // wrap around if no parent is present and both nodes are outer nodes of the same parent + OGDF_ASSERT(parent->isDetached()); + OGDF_ASSERT(parent->isChildOuter(sib1)); + OGDF_ASSERT(parent->isChildOuter(sib2)); + } + } else if (parent != nullptr && sib2->isParentOf(parent)) { + // sib2 is the parent of `parent`, sib1 is an outer child of `parent` + } else { + std::swap(sib1, sib2); + parent = sib1->getParent(); + // now we should have the same situation as before + OGDF_ASSERT(parent != nullptr); + OGDF_ASSERT(sib2->isParentOf(parent)); + } + + OGDF_ASSERT(parent != nullptr); + OGDF_ASSERT(parent->isValidNode(m_forest)); + OGDF_ASSERT(sib2 == nullptr || sib2->isValidNode(m_forest)); + setParent(parent); + parent->m_childCount++; + + sib1->replaceSibling(nullptr, this); + parent->replaceOuterChild(sib1, this); + this->replaceSibling(nullptr, sib1); + + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(parent->isValidNode(m_forest)); + OGDF_ASSERT(sib1->isValidNode(m_forest)); + OGDF_ASSERT(sib2 == nullptr || sib2->isValidNode(m_forest)); +} + +void PCNode::detach() { +#ifdef OGDF_DEBUG + OGDF_ASSERT(isValidNode()); + PCNode* parent = getParent(); + OGDF_ASSERT(parent == nullptr || parent->isValidNode(m_forest)); + forceDetach(); + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(parent == nullptr || parent->isValidNode(m_forest)); +#else + forceDetach(); +#endif +} + +void PCNode::forceDetach() { + PCNode* parent = getParent(); + if (m_sibling1 != nullptr) { + m_sibling1->replaceSibling(this, m_sibling2); + } else if (parent != nullptr) { + parent->replaceOuterChild(this, m_sibling2); + } + if (m_sibling2 != nullptr) { + m_sibling2->replaceSibling(this, m_sibling1); + } else if (parent != nullptr) { + parent->replaceOuterChild(this, m_sibling1); + } + if (parent != nullptr) { + OGDF_ASSERT(!parent->isChildOuter(this)); + parent->m_childCount--; + } + m_parentCNodeId = UNIONFINDINDEX_EMPTY; + m_parentPNode = nullptr; + m_sibling1 = m_sibling2 = nullptr; +} + +void PCNode::replaceWith(PCNode* repl) { + OGDF_ASSERT(repl != nullptr); + OGDF_ASSERT(repl != this); + OGDF_ASSERT(m_forest == repl->m_forest); + OGDF_ASSERT(repl->isDetached()); + OGDF_ASSERT(repl->isValidNode(m_forest)); + OGDF_ASSERT(this != repl); + PCNode* parent = getParent(); + OGDF_ASSERT(parent == nullptr || parent->isValidNode(m_forest)); + repl->m_parentCNodeId = m_parentCNodeId; + repl->m_parentPNode = m_parentPNode; + repl->m_sibling1 = m_sibling1; + repl->m_sibling2 = m_sibling2; + if (repl->m_sibling1 != nullptr) { + repl->m_sibling1->replaceSibling(this, repl); + } + if (repl->m_sibling2 != nullptr) { + repl->m_sibling2->replaceSibling(this, repl); + } + while (parent != nullptr && parent->isChildOuter(this)) { + parent->replaceOuterChild(this, repl); + } + + m_parentCNodeId = UNIONFINDINDEX_EMPTY; + m_parentPNode = nullptr; + m_sibling1 = m_sibling2 = nullptr; + + OGDF_ASSERT(isValidNode()); + OGDF_ASSERT(repl->isValidNode()); + OGDF_ASSERT(parent == nullptr || parent->isValidNode(m_forest)); +} + +void PCNode::mergeIntoParent() { + OGDF_ASSERT(m_nodeType == PCNodeType::CNode); + OGDF_ASSERT(isValidNode()); + PCNode* parent = getParent(); + OGDF_ASSERT(parent->m_nodeType == PCNodeType::CNode); + OGDF_ASSERT(parent->isValidNode(m_forest)); + + UnionFindIndex pcid = m_forest->m_parents.link(m_nodeListIndex, parent->m_nodeListIndex); + if (pcid == this->m_nodeListIndex) { + std::swap(m_forest->m_cNodes[this->m_nodeListIndex], + m_forest->m_cNodes[parent->m_nodeListIndex]); + std::swap(this->m_nodeListIndex, parent->m_nodeListIndex); + } else { + OGDF_ASSERT(pcid == parent->m_nodeListIndex); + } + parent->m_childCount += m_childCount - 1; + + if (m_sibling1 != nullptr) { + m_sibling1->replaceSibling(this, m_child1); + m_child1->replaceSibling(nullptr, m_sibling1); + } else { + parent->replaceOuterChild(this, m_child1); + } + if (m_sibling2 != nullptr) { + m_sibling2->replaceSibling(this, m_child2); + m_child2->replaceSibling(nullptr, m_sibling2); + } else { + parent->replaceOuterChild(this, m_child2); + } + + m_child1 = m_child2 = nullptr; + m_sibling1 = m_sibling2 = nullptr; + m_parentCNodeId = UNIONFINDINDEX_EMPTY; + m_parentPNode = nullptr; + m_childCount = 0; + + OGDF_ASSERT(parent->isValidNode(m_forest)); +} + +void PCNode::replaceSibling(PCNode* oldS, PCNode* newS) { + OGDF_ASSERT((newS == nullptr) || (m_forest == newS->m_forest)); + OGDF_ASSERT(newS != this); + if (oldS == m_sibling1) { + m_sibling1 = newS; + } else { + OGDF_ASSERT(oldS == m_sibling2); + m_sibling2 = newS; + } +} + +void PCNode::replaceOuterChild(PCNode* oldC, PCNode* newC) { + OGDF_ASSERT((newC == nullptr) || (m_forest == newC->m_forest)); + OGDF_ASSERT(newC != this); + if (oldC == m_child1) { + m_child1 = newC; + } else { + OGDF_ASSERT(oldC == m_child2); + m_child2 = newC; + } +} + +void pc_tree::proceedToNextSibling(PCNode*& pred, PCNode*& curr) { + pred = curr->getNextSibling(pred); + std::swap(pred, curr); +} + +PCNode* PCNode::getNextSibling(const PCNode* pred) const { + OGDF_ASSERT(pred == nullptr || isSiblingOf(pred)); + if (pred == m_sibling1) { + return m_sibling2; + } else { + OGDF_ASSERT(pred == m_sibling2); + return m_sibling1; + } +} + +PCNode* PCNode::getOtherOuterChild(const PCNode* child) const { + OGDF_ASSERT(isParentOf(child)); + if (child == m_child1) { + return m_child2; + } else { + OGDF_ASSERT(child == m_child2); + return m_child1; + } +} + +void PCNode::proceedToNextNeighbor(PCNode*& pred, PCNode*& curr) const { + pred = getNextNeighbor(pred, curr); + std::swap(pred, curr); +} + +PCNode* PCNode::getNextNeighbor(const PCNode* pred, const PCNode* curr) const { + OGDF_ASSERT(curr != nullptr); + + PCNode* l_next; + PCNode* l_parent = getParent(); + + if (pred == nullptr) { + if (curr == l_parent) { + l_next = m_child1; + } else { + OGDF_ASSERT(isParentOf(curr)); + l_next = (curr->m_sibling1 != nullptr) ? curr->m_sibling1 : curr->m_sibling2; + } + } else { + // find next sibling + if (pred->isSiblingOf(curr)) { + OGDF_ASSERT(isParentOf(curr)); + if (pred->isSiblingAdjacent(curr)) { + l_next = curr->getNextSibling(pred); + } else { + OGDF_ASSERT(isDetached()); + OGDF_ASSERT(isChildOuter(pred)); + OGDF_ASSERT(isChildOuter(curr)); + l_next = curr->getNextSibling(nullptr); + } + + // find second or second to last child + } else if (pred == l_parent) { + OGDF_ASSERT(isParentOf(curr)); + l_next = curr->getNextSibling(nullptr); + + // find first or last child + } else { + OGDF_ASSERT(curr == l_parent); + OGDF_ASSERT(isParentOf(pred)); + l_next = getOtherOuterChild(pred); + } + } + + if (l_next == nullptr) { + if (l_parent == nullptr) { + return getOtherOuterChild(curr); + } else { + return l_parent; + } + } else { + OGDF_ASSERT(isParentOf(l_next)); + return l_next; + } +} + +bool PCNode::areNeighborsAdjacent(const PCNode* neigh1, const PCNode* neigh2) const { + OGDF_ASSERT(neigh1 != nullptr); + OGDF_ASSERT(neigh2 != nullptr); + OGDF_ASSERT(neigh1 != neigh2); + + if (isParentOf(neigh1) && isParentOf(neigh2)) { + return neigh1->isSiblingAdjacent(neigh2) + || (isDetached() && isChildOuter(neigh1) && isChildOuter(neigh2)); + + } else { + PCNode* parent = getParent(); + if (neigh1 == parent) { + OGDF_ASSERT(isParentOf(neigh2)); + return isChildOuter(neigh2); + + } else { + OGDF_ASSERT(neigh2 == parent); + OGDF_ASSERT(isParentOf(neigh1)); + return isChildOuter(neigh1); + } + } +} + +bool PCNode::isValidNode(const PCTreeForest* ofForest) const { + if (ofForest && m_forest != ofForest) { + return false; + } + +#ifdef OGDF_DEBUG + if (m_parentCNodeId == UNIONFINDINDEX_EMPTY && m_parentPNode == nullptr) { + OGDF_ASSERT(m_sibling1 == nullptr && m_sibling2 == nullptr); + } else { + OGDF_ASSERT(m_parentCNodeId == UNIONFINDINDEX_EMPTY || m_parentPNode == nullptr); + PCNode* parent = getParent(); + OGDF_ASSERT(parent->m_forest == m_forest); + int null_sibs = 0; + if (m_sibling1 == nullptr) { + null_sibs++; + } else { + OGDF_ASSERT(m_sibling1->isSiblingAdjacent(this)); + } + if (m_sibling2 == nullptr) { + null_sibs++; + } else { + OGDF_ASSERT(m_sibling2->isSiblingAdjacent(this)); + } + int outsides = 0; + if (parent->m_child1 == this) { + outsides++; + } + if (parent->m_child2 == this) { + outsides++; + } + OGDF_ASSERT(null_sibs == outsides); + OGDF_ASSERT((null_sibs > 0) == isOuterChild()); + if (isOuterChild()) { + OGDF_ASSERT(parent->isChildOuter(this)); + } + } + + if (m_childCount == 0) { + OGDF_ASSERT(m_child1 == nullptr); + OGDF_ASSERT(m_child2 == nullptr); + } else if (m_childCount == 1) { + OGDF_ASSERT(m_child1 != nullptr); + OGDF_ASSERT(m_child2 != nullptr); + OGDF_ASSERT(m_child1 == m_child2); + } else { + OGDF_ASSERT(m_childCount >= 2); + OGDF_ASSERT(m_child1 != nullptr); + OGDF_ASSERT(m_child2 != nullptr); + OGDF_ASSERT(m_child1 != m_child2); + } +#endif + + if (m_nodeType == PCNodeType::CNode) { + OGDF_ASSERT(m_forest->m_cNodes.at(m_nodeListIndex) == this); + return (size_t)m_forest->m_parents.find(m_nodeListIndex) == m_nodeListIndex; + } else if (m_nodeType == PCNodeType::Leaf) { + OGDF_ASSERT(getDegree() <= 1); + // OGDF_ASSERT(forest->leaves.at(nodeListIndex) == this); + return true; + } else { + OGDF_ASSERT(m_nodeType == PCNodeType::PNode); + return true; + } +} + +PCNode* PCNode::getParent() const { + if (m_parentPNode != nullptr) { + OGDF_ASSERT(m_parentCNodeId == UNIONFINDINDEX_EMPTY); + OGDF_ASSERT(m_parentPNode->m_nodeType == PCNodeType::PNode + || m_parentPNode->m_nodeType == PCNodeType::Leaf); + return m_parentPNode; + } else if (m_parentCNodeId != UNIONFINDINDEX_EMPTY) { + m_parentCNodeId = m_forest->m_parents.find(m_parentCNodeId); + OGDF_ASSERT(m_forest->m_cNodes.at(m_parentCNodeId) != nullptr); + PCNode* parent = m_forest->m_cNodes[m_parentCNodeId]; + OGDF_ASSERT(parent != this); + OGDF_ASSERT(parent->m_nodeType == PCNodeType::CNode); + OGDF_ASSERT(parent->m_nodeListIndex == m_parentCNodeId); + return parent; + } else { + return nullptr; + } +} + +void PCNode::setParent(PCNode* parent) { + OGDF_ASSERT(isDetached()); + OGDF_ASSERT(parent != nullptr); + if (parent->m_nodeType == PCNodeType::CNode) { + m_parentCNodeId = parent->m_nodeListIndex; + } else { + m_parentPNode = parent; + } +} + +// only allowed on root children +void PCNode::rotateChildOutside(bool child1) { + PCNode* parent = getParent(); + OGDF_ASSERT(parent != nullptr); + + // parent must be root node + OGDF_ASSERT(parent->getParent() == nullptr); + + /* rotate node to child1/2 of parent, children might be flipped but that's perfectly fine for a + * CNode (and PNode as well) if parent is the root node */ + if (!isOuterChild()) { + PCNode* left = parent->getChild1(); + PCNode* right = parent->getChild2(); + + OGDF_ASSERT(right->isOuterChild()); + OGDF_ASSERT(left->isOuterChild()); + + // create connection between outer children + left->replaceSibling(nullptr, right); + right->replaceSibling(nullptr, left); + + // find any existing sibling of node to later promote to child1/2 + if (child1) { + parent->m_child1 = this; + parent->m_child2 = (getSibling1() != nullptr) ? getSibling1() : getSibling2(); + + OGDF_ASSERT(parent->m_child2 != nullptr); + } else { + parent->m_child2 = this; + parent->m_child1 = (getSibling1() != nullptr) ? getSibling1() : getSibling2(); + + OGDF_ASSERT(parent->m_child1 != nullptr); + } + + // cut connection between node and one of its siblings and promote them to outer children + parent->m_child1->replaceSibling(parent->m_child2, nullptr); + parent->m_child2->replaceSibling(parent->m_child1, nullptr); + } else if (child1 && parent->m_child1 != this) { + PCNode* otherChild = parent->m_child1; + parent->m_child1 = this; + parent->m_child2 = otherChild; + } else if (!child1 && parent->m_child2 != this) { + PCNode* otherChild = parent->m_child2; + parent->m_child2 = this; + parent->m_child1 = otherChild; + } + + OGDF_ASSERT((child1 && parent->m_child1 == this) || (!child1 && parent->m_child2 == this)); + OGDF_ASSERT(parent->m_child1->isOuterChild()); + OGDF_ASSERT(parent->m_child2->isOuterChild()); + OGDF_ASSERT(isOuterChild()); +} + +void PCNode::checkTimestamp() const { + OGDF_ASSERT(isValidNode()); + if (m_forest->m_timestamp != m_timestamp) { + OGDF_ASSERT(m_forest->m_timestamp > m_timestamp); + m_label = NodeLabel::Unknown; + m_timestamp = m_forest->m_timestamp; + if (!isLeaf()) { + m_temp.clear(); + } + } +} + +std::ostream& pc_tree::operator<<(std::ostream& os, const pc_tree::PCNode& node) { + return os << &node; +} + +std::ostream& pc_tree::operator<<(std::ostream& os, const pc_tree::PCNode* node) { + if (node == nullptr) { + return os << "null-Node"; + } + os << node->m_nodeType << " " << node->index(); + os << " with " << node->m_childCount << " children"; + os << " ["; + int c = 0; + for (PCNode *pred = nullptr, *curr = node->m_child1; curr != nullptr; + proceedToNextSibling(pred, curr)) { + if (c > 0) { + os << ", "; + } + os << curr->index(); + c++; + } + os << "]"; + PCNode* parent = node->getParent(); + if (parent != nullptr) { + os << " and parent "; + os << parent->m_nodeType << " " << parent->index(); + } else { + os << " and no parent"; + } + return os; +} + +PCNodeChildrenIterable PCNode::children() { return PCNodeChildrenIterable(this); } + +PCNodeNeighborsIterable PCNode::neighbors(PCNode* startWith) { + return PCNodeNeighborsIterable(this, startWith); +} + +PCNodeIterator& PCNodeIterator::operator++() { + m_node->proceedToNextNeighbor(m_pred, m_curr); + return *this; +} + +PCNodeIterator PCNodeIterator::operator++(int) { + PCNodeIterator before = *this; + m_node->proceedToNextNeighbor(m_pred, m_curr); + return before; +} + +bool PCNodeIterator::isParent() { + return m_node != nullptr && m_curr != nullptr && m_curr->isParentOf(m_node); +} + +PCNodeIterator PCNodeChildrenIterable::begin() const noexcept { + return PCNodeIterator(m_node, nullptr, m_node->m_child1); +} + +PCNodeIterator PCNodeChildrenIterable::end() const noexcept { + if (m_node->m_child1 != nullptr && !m_node->isDetached()) { + return PCNodeIterator(m_node, m_node->m_child2, m_node->getParent()); + } else { + return PCNodeIterator(m_node, m_node->m_child2, m_node->m_child1); + } +} + +unsigned long PCNodeChildrenIterable::count() const { return m_node->m_childCount; } + +PCNodeIterator PCNodeNeighborsIterable::begin() const noexcept { + return PCNodeIterator(m_node, nullptr, m_first); +} + +PCNodeIterator PCNodeNeighborsIterable::end() const noexcept { + PCNode* second = m_node->getNextNeighbor(nullptr, m_first); + PCNode* last = m_node->getNextNeighbor(second, m_first); + return PCNodeIterator(m_node, last, m_first); +} + +unsigned long PCNodeNeighborsIterable::count() const { return m_node->getDegree(); } diff --git a/src/pctree/PCNode.h b/src/pctree/PCNode.h new file mode 100644 index 0000000..6aa672e --- /dev/null +++ b/src/pctree/PCNode.h @@ -0,0 +1,481 @@ +/** \file + * \brief A node in a PC-tree that is either a P-node, C-node or leaf. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "PCEnum.h" +#include "PCTreeForest.h" +#include "util/IntrusiveList.h" + +#include +#include +#include + +namespace pc_tree { +struct OGDF_EXPORT PCNodeChildrenIterable; +struct OGDF_EXPORT PCNodeNeighborsIterable; + +/** + * A node in a PC-tree that is either a P-node, C-node or leaf. + * See https://doi.org/10.15475/cpatp.2024 Figure 8.3 for a visualization of the doubly-linked tree structure stored in each node and more details on the changes made and temporary information stored by PCTree::makeConsecutive(). + * Important terminology: + * - child: direct descendant of this node in the tree + * - outer child: first or last child + * - sibling: other node with the same direct parent + * - adjacent sibling: predecessor or successor in parent's list of children + * - neighbors: all children and the parent + */ +class OGDF_EXPORT PCNode : public IntrusiveList::node { + friend OGDF_EXPORT std::ostream&(operator<<)(std::ostream&, const pc_tree::PCTree*); + friend OGDF_EXPORT std::ostream&(operator<<)(std::ostream&, const pc_tree::PCNode*); + + friend class PCTree; + friend class PCTreeForest; + friend struct PCNodeChildrenIterable; + friend struct PCNodeNeighborsIterable; + +public: + /** + * Temporary information used during each step of the PCTree::makeConsecutive() update operation. + */ + struct TempInfo { + PCNode *predPartial = nullptr, *nextPartial = nullptr; + PCNode* tpPred = nullptr; + PCNode* tpPartialPred = nullptr; + size_t tpPartialHeight = 0; + PCNode* tpSucc = nullptr; + std::vector fullNeighbors; + PCNode *ebEnd1 = nullptr, *fbEnd1 = nullptr, *fbEnd2 = nullptr, *ebEnd2 = nullptr; + + void replaceNeighbor(PCNode* oldNeigh, PCNode* newNeigh) { + if (tpPred == oldNeigh) { + tpPred = newNeigh; + } + if (tpPartialPred == oldNeigh) { + tpPartialPred = newNeigh; + } + if (tpSucc == oldNeigh) { + tpSucc = newNeigh; + } + if (ebEnd1 == oldNeigh) { + ebEnd1 = newNeigh; + } + if (ebEnd2 == oldNeigh) { + ebEnd2 = newNeigh; + } + if (fbEnd1 == oldNeigh) { + fbEnd1 = newNeigh; + } + if (fbEnd2 == oldNeigh) { + fbEnd2 = newNeigh; + } + } + + void clear() { + nextPartial = predPartial = nullptr; + tpPred = tpPartialPred = tpSucc = nullptr; + ebEnd1 = fbEnd1 = fbEnd2 = ebEnd2 = nullptr; + tpPartialHeight = 0; + fullNeighbors.clear(); + } + }; + + using LeafUserData = std::array; + +private: + // index in registry + size_t m_id; + + // global + PCTreeForest* m_forest; + + // private + UnionFindIndex m_nodeListIndex = UNIONFINDINDEX_EMPTY; + PCNodeType m_nodeType; + PCNode* m_parentPNode = nullptr; + mutable UnionFindIndex m_parentCNodeId = UNIONFINDINDEX_EMPTY; + PCNode* m_sibling1 = nullptr; + PCNode* m_sibling2 = nullptr; + PCNode* m_child1 = nullptr; + PCNode* m_child2 = nullptr; + size_t m_childCount = 0; + mutable NodeLabel m_label = NodeLabel::Unknown; + mutable size_t m_timestamp = 0; + + // leaves need no temp info, so they can easily store user data + union { + mutable TempInfo m_temp; + LeafUserData m_userData; + }; + + PCNode(PCTreeForest* forest, size_t id, PCNodeType nodeType) + : IntrusiveList::node(), m_id(id), m_forest(forest), m_nodeType(nodeType) { + if (nodeType == PCNodeType::Leaf) { + new (&m_userData) LeafUserData; + } else { + new (&m_temp) TempInfo; + } + } + + ~PCNode() { + if (m_nodeType == PCNodeType::Leaf) { + m_userData.~array(); + } else { + m_temp.~TempInfo(); + } + } + +public: + /** + * @name Tree structure methods + * These methods allow modifying the tree structure or embedding, e.g., when manually constructing a PC-tree. + */ + //! @{ + + /** + * Append a (detached) child node to the begin or end of this nodes' children. + */ + void appendChild(PCNode* child, bool begin = false); + + /** + * Insert a (detached) child node directly between two adjacent children of this node. + */ + void insertBetween(PCNode* sib1, PCNode* sib2); + + /** + * Detach this node from its parent. Invalidates but does not change the PC-forest of the node, so don't forget to re-attach it somewhere else in a PC-tree of the same forest to make it valid again. + */ + void detach(); + + /** + * Swaps this node inplace with a (detached) other one. Afterwards, this node will be detached. + */ + void replaceWith(PCNode* repl); + + /** + * Merges this C-node into its C-node parent. + */ + void mergeIntoParent(); + + /** + * Reverse the stored order of children. + */ + void flip() { std::swap(m_child1, m_child2); } + +private: + /** + * Notify this node that one of its adjacent siblings changed. + */ + void replaceSibling(PCNode* oldS, PCNode* newS); + + /** + * Make this node an outer child of its parent. Only works for children of the root node. + */ + void rotateChildOutside(bool child1 = true); + + /** + * Notify this node that one of its outer children was replaced. + */ + void replaceOuterChild(PCNode* oldC, PCNode* newC); + + /** + * Notify this node that it has a new parent. + */ + void setParent(PCNode* parent); + + /** + * detach() without performing checks. + */ + void forceDetach(); + + /** + * Overwrite the type of this node without updating any other data structures. + */ + void changeType(PCNodeType newType) { + if (m_nodeType == PCNodeType::Leaf && newType != PCNodeType::Leaf) { + m_userData.~array(); + new (&m_temp) TempInfo; + } else if (m_nodeType != PCNodeType::Leaf && newType == PCNodeType::Leaf) { + m_temp.~TempInfo(); + new (&m_userData) LeafUserData; + } + m_nodeType = newType; + } + + //! @} + +public: + /** + * @name Iterator methods + * These methods allow easily walking around the PC-tree. + */ + //! @{ + + /** + * Given the left or right sibling \p pred, return the adjacent sibling on the other side. + */ + PCNode* getNextSibling(const PCNode* pred) const; + + /** + * Given one outer child, return the outer child on the other side. + */ + PCNode* getOtherOuterChild(const PCNode* child) const; + + /** + * Method to walk the cyclic order of all neighbors, i.e., all children plus the parent, where this node's parent is considered to be adjacent to this node's two outer children. + * The returned node is the one of the two nodes adjacent to \p curr in this cyclic order that is not \p pred, or an arbitrary one of the two if \p pred is null. + */ + PCNode* getNextNeighbor(const PCNode* pred, const PCNode* curr) const; + + /** + * Iteration-convenience version of getNextNeighbor() that updates the variables \p pred to \p curr and \p curr to the value returned by getNextNeighbor(pred, curr). + */ + void proceedToNextNeighbor(PCNode*& pred, PCNode*& curr) const; + + /** + * @return the parent node of this node, or null if this node is the root or currently detached. If the parent is a C-node, this requires a look-up in the union-find data structure. + */ + PCNode* getParent() const; + + /** + * @return iterable for all children + */ + PCNodeChildrenIterable children(); + + /** + * @return iterable for all children plus the parent, optionally selecting a starting node in this cyclic order + */ + PCNodeNeighborsIterable neighbors(PCNode* first = nullptr); + + //! @} + +public: + /** + * @name Structure check methods + * These methods help with checking the current status of this node w.r.t. its surrounding tree structure. + */ + //! @{ + + /** + * @return \c true if this node has no parent, i.e., it is the root of its PC-tree or needs to be attached to some node first before the tree can become valid again. + */ + bool isDetached() const { + if (m_parentCNodeId == UNIONFINDINDEX_EMPTY && m_parentPNode == nullptr) { + return true; + } else { + OGDF_ASSERT(m_parentCNodeId == UNIONFINDINDEX_EMPTY || m_parentPNode == nullptr); + return false; + } + } + + /** + * Perform multiple debug checks and return \c true if getForest == \p ofForest + */ + bool isValidNode(const PCTreeForest* ofForest = nullptr) const; + + bool isLeaf() const { return m_nodeType == PCNodeType::Leaf; } + + /** + * @return \c true, if other->getParent() == this + */ + bool isParentOf(const PCNode* other) const { + OGDF_ASSERT(other != nullptr); + OGDF_ASSERT(m_forest == other->m_forest); + return other->getParent() == this; + } + + /** + * @return \c true, if this->getParent() == other->getParent() + */ + bool isSiblingOf(const PCNode* other) const { + OGDF_ASSERT(other != nullptr); + OGDF_ASSERT(m_forest == other->m_forest); + return this->getParent() == other->getParent(); + } + + /** + * @return \c true, if this->getSibling1() == sibling or this->getSibling2() == sibling + */ + bool isSiblingAdjacent(const PCNode* sibling) const { + OGDF_ASSERT(isSiblingOf(sibling)); + OGDF_ASSERT(this != sibling); + return m_sibling1 == sibling || m_sibling2 == sibling; + } + + /** + * @return \c true if neigh1 and neigh2 are children of this node and isSiblingAdjacent(neigh1, neigh2) is true, + * or if one of the passed nodes is the parent of this node and the other one is an outer child of this node + */ + bool areNeighborsAdjacent(const PCNode* neigh1, const PCNode* neigh2) const; + + /** + * @return \c true, if \p child is an outer child of this node, i.e., if this->getChild1() == child or this->getChild2() == child + */ + bool isChildOuter(const PCNode* child) const { + OGDF_ASSERT(isParentOf(child)); + return m_child1 == child || m_child2 == child; + } + + /** + * @return \c true, if this node is an outer child of its parent, i.e., if this->getSibling1() == nullptr or this->getSibling2() == child + */ + bool isOuterChild() const { return m_sibling1 == nullptr || m_sibling2 == nullptr; } + + //! @} + +public: + /** + * @name makeConsecutive-related temporary information + * These methods provide access to temporary information used during a PCTree::makeConsecutive() call. + */ + //! @{ + + const TempInfo& constTempInfo() const { + checkTimestamp(); + OGDF_ASSERT(!isLeaf()); + return m_temp; + } + + bool isFull() const { return getLabel() == NodeLabel::Full; } + + NodeLabel getLabel() const { + // this operation does not reset the temp info + return m_forest->m_timestamp == m_timestamp ? m_label : NodeLabel::Empty; + } + + void setLabel(NodeLabel l) { + checkTimestamp(); + m_label = l; + } + +private: + // these methods are slightly faster if we already called checkTimestamp() + inline NodeLabel getLabelUnchecked() const { + OGDF_ASSERT(m_forest->m_timestamp == m_timestamp); + return m_label; + } + + inline void setLabelUnchecked(NodeLabel l) { + OGDF_ASSERT(m_forest->m_timestamp == m_timestamp); + m_label = l; + } + +public: + /** + * @return the user data that can be stored in leaves + */ + LeafUserData& leafUserData() { + OGDF_ASSERT(isLeaf()); + return m_userData; + } + + /** + * @return the user data that can be stored in leaves + */ + const LeafUserData& leafUserData() const { + OGDF_ASSERT(isLeaf()); + return m_userData; + } + +private: + void checkTimestamp() const; + + TempInfo& tempInfo() { + checkTimestamp(); + OGDF_ASSERT(!isLeaf()); + return m_temp; + } + + size_t addFullNeighbor(PCNode* fullNeigh) { + checkTimestamp(); + OGDF_ASSERT(!isLeaf()); + OGDF_ASSERT(fullNeigh->isFull()); + m_temp.fullNeighbors.push_back(fullNeigh); + return m_temp.fullNeighbors.size(); + } + + PCNode*& getFullNeighInsertionPoint(PCNode* nonFullNeigh) { + checkTimestamp(); + OGDF_ASSERT(!isLeaf()); + OGDF_ASSERT(nonFullNeigh != nullptr); + if (nonFullNeigh == m_temp.ebEnd1) { + OGDF_ASSERT(areNeighborsAdjacent(m_temp.ebEnd1, m_temp.fbEnd1)); + return m_temp.fbEnd1; + } else { + OGDF_ASSERT(nonFullNeigh == m_temp.ebEnd2); + OGDF_ASSERT(areNeighborsAdjacent(m_temp.ebEnd2, m_temp.fbEnd2)); + return m_temp.fbEnd2; + } + } + + //! @} + +public: + /** + * @name Getters + */ + //! @{ + size_t index() const { return m_id; } + + PCNodeType getNodeType() const { return m_nodeType; } + + size_t getChildCount() const { return m_childCount; } + + size_t getDegree() const { return isDetached() ? m_childCount : m_childCount + 1; } + + PCNode* getChild1() const { return m_child1; } + + PCNode* getChild2() const { return m_child2; } + + /** + * Check whether this node has only one child and return it. + */ + PCNode* getOnlyChild() const { + OGDF_ASSERT(m_childCount == 1); + return m_child1; + } + + PCNode* getSibling1() const { return m_sibling1; } + + PCNode* getSibling2() const { return m_sibling2; } + + PCTreeForest* getForest() const { return m_forest; } + + //! @} + + OGDF_NEW_DELETE +}; + +/** + * Iteration-convenience version of PCNode::getNextSibling() that updates the variables \p pred to \p curr and \p curr to the value returned by PCNode::getNextSibling(pred, curr). + */ +OGDF_EXPORT void proceedToNextSibling(PCNode*& pred, PCNode*& curr); +} diff --git a/src/pctree/PCRegistry.h b/src/pctree/PCRegistry.h new file mode 100644 index 0000000..e51b368 --- /dev/null +++ b/src/pctree/PCRegistry.h @@ -0,0 +1,64 @@ +/** \file + * \brief A registry that allows labelling the nodes of a PC-tree. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "PCEnum.h" + +namespace pc_tree { +/** + * A registry that allows labelling the nodes of a PC-tree. + */ +class OGDF_EXPORT PCTreeRegistry : public RegistryBase { + PCTreeForest* m_pForest; + +public: + PCTreeRegistry(PCTreeForest* pcTreeForest) : m_pForest(pcTreeForest) { } + + //! Returns the index of \p key. + static inline int keyToIndex(PCNode* key); + + //! Returns whether \p key is associated with this registry. + bool isKeyAssociated(PCNode* key) const; + + //! Returns the maximum index of all keys managed by this registry. + int maxKeyIndex() const; + + //! Returns the array size currently requested by this registry. + int calculateArraySize(int add) const; + + operator PCTreeForest&() const { return *m_pForest; } + + operator PCTreeForest*() const { return m_pForest; } + + PCTreeForest& getForest() const { return *m_pForest; } +}; +} diff --git a/src/pctree/PCTree.h b/src/pctree/PCTree.h new file mode 100644 index 0000000..82b06a7 --- /dev/null +++ b/src/pctree/PCTree.h @@ -0,0 +1,708 @@ +/** \file + * \brief The main class of the PC-tree. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "PCEnum.h" +#include "PCNode.h" +#include "PCRegistry.h" +#include "PCTreeForest.h" +#include "PCTreeIterators.h" +#include "util/IntrusiveList.h" + +#include +#include +#include +#include +#include +#include + +namespace pc_tree { +/** + * @return \c true if calling PCTree::makeConsecutive() with \p restSize out of \p leafCount total leaves never requires changes to the tree. + * This is the case for \p restSize values 0, 1, \p leafCount - 1, and \p leafCount. + */ +OGDF_EXPORT bool isTrivialRestriction(int restSize, int leafCount); + +template +R factorial(int n); + +template<> +inline int factorial(int n) { + return (int)std::tgamma(n + 1); +} + +#ifdef OGDF_DEBUG +/** + * Allows controlling the frequency of full-tree consistency checks in heavy debug mode. + * When set to a non-zero value n, only every n'th check will be performed. + * When set to 1, every check will be executed. + * When set to 0, entirely skips the checks. + * Defaults to n=10. + */ +OGDF_EXPORT extern int PCTREE_DEBUG_CHECK_FREQ; +#endif + +/** + * Functions that can be passed to PCTree::uniqueID() + */ +namespace uid_utils { +/** + * Print the index of a node \p n. + */ +OGDF_EXPORT void nodeToID(std::ostream& os, PCNode* n, int pos); + +/** + * Print the position \p pos of a node \p n. + */ +OGDF_EXPORT void nodeToPosition(std::ostream& os, PCNode* n, int pos); + +/** + * Print the index of a node \p n if it is a leaf. + */ +OGDF_EXPORT void leafToID(std::ostream& os, PCNode* n, int pos); + +/** + * Print the position \p pos of a node \p n if it is a leaf. + */ +OGDF_EXPORT void leafToPosition(std::ostream& os, PCNode* n, int pos); + +/** + * Sort nodes by ascending index. + */ +OGDF_EXPORT bool compareNodesByID(PCNode* a, PCNode* b); +} + +/** + * A PC-tree represents a set of cyclic orders of its leaves by labeling its inner nodes as either P- or C-node + * and allowing arbitrary permutations of the neighbors of P-nodes while only allowing flips of C-nodes. + * The operation PCTree::makeConsecutive() updates the tree such that a set of leaves is consecutive in all represented cyclic orders. + * + * If you are using this implementation, please cite the following work: + * \remark Simon D. Fink, Matthias Pfretzschner, and Ignaz Rutter. 2023. Experimental Comparison of PC-Trees and PQ-Trees. ACM J. Exp. Algorithmics 28, Article 1.10 (December 2023). https://doi.org/10.1145/3611653 + * + * For more details, see also (open access): + * \remark Simon D. Fink. 2024. Constrained Planarity Algorithms in Theory and Practice. Doctoral Thesis, University of Passau. https://doi.org/10.15475/cpatp.2024 + */ +class OGDF_EXPORT PCTree { + friend OGDF_EXPORT std::ostream&(operator<<)(std::ostream&, const pc_tree::PCTree*); + friend OGDF_EXPORT std::ostream&(operator<<)(std::ostream&, const pc_tree::PCNode*); + + friend class PCNode; + friend class PCTreeRegistry; + friend class PCTreeForest; + +public: + struct Observer; // pre-declaration + +private: + // global + PCTreeForest* m_forest = nullptr; + + // needs merge when trees merge + size_t m_pNodeCount = 0; + size_t m_cNodeCount = 0; + IntrusiveList m_leaves; + + // needs special merge + PCNode* m_rootNode = nullptr; + + // temp makeConsecutive variables + size_t m_partialCount = 0; + size_t m_terminalPathLength = 0; + PCNode* m_firstPartial = nullptr; + PCNode* m_lastPartial = nullptr; + PCNode* m_apexCandidate = nullptr; + bool m_apexCandidateIsFix = false; + PCNode* m_apexTPPred2 = nullptr; + + // private + bool m_externalForest = true; + std::list m_observers; + +public: + /** + * Constructs a new PCTree. + * The tree will be part of an automatically managed internal forest and thus not allow merging with other trees. + */ + explicit PCTree() : PCTree(nullptr) {}; + + /** + * Constructs a new PCTree that is part of \p forest and can thus be merged with any other PCTree of the same forest. + */ + explicit PCTree(PCTreeForest* forest) { + if (forest) { + m_forest = forest; + m_externalForest = true; + } else { + m_forest = new PCTreeForest(); + m_externalForest = false; + } + }; + + /** + * Convenience method generating a PCTree consisting of a single P-node with \p leafNum leaves, which are all copied to the optional list \p added. + * Automatically creates and manages a forest if \p forest is null. + */ + explicit PCTree(int leafNum, std::vector* added = nullptr, + PCTreeForest* forest = nullptr); + + /** + * Copy a PCTree. + * @param other The PCTree to copy. + * @param nodeMapping Will be assigned a mapping from the nodes of \p other to the newly created nodes in this tree. + * @param keep_ids Set to \c true to use the same node IDs as in \p other, otherwise consecutive IDs will be generated. + * @param forest Automatically create and manages a forest if \p forest is null. + */ + explicit PCTree(const PCTree& other, PCTreeNodeArray& nodeMapping, + bool keep_ids = false, PCTreeForest* forest = nullptr); + + /** + * Deserialize a PCTree from a string \p str as generated by operator<<(std::ostream&, const PCTree*) or PCTree::uniqueID(). + * Automatically creates and manages a forest if \p forest is null. + */ + explicit PCTree(const std::string& str, bool keep_ids = false, PCTreeForest* forest = nullptr); + + virtual ~PCTree(); + +public: + /** + * @name Node creation / destruction + */ + //! @{ + /** + * Create a new node. + * + * @param type The type of the node. + * @param parent Parent to attach this node to, may only be null if this tree is empty and thus has no root. + * @param id ID for the new node, or -1 to automatically use the next free one. + * @return the new node + */ + PCNode* newNode(PCNodeType type, PCNode* parent = nullptr, int id = -1); + + /** + * @copydoc destroyNode(PCNode* const&) + * @param node will be set to null afterwards + */ + void destroyNode(PCNode*& node) { + destroyNode((PCNode* const&)node); + node = nullptr; + } + + /** + * Destroy a node. + * The node must be detached and may not be the root of this tree. + */ + void destroyNode(PCNode* const& node); + + /** + * Attach \p count leaves to P- or C-node \p parent and optionally store the new leaves in a vector \p added. + */ + void insertLeaves(int count, PCNode* parent, std::vector* added = nullptr); + + /** + * Convert \p leaf into a P-node and attach \p leafCount new leaves to it. + */ + void replaceLeaf(int leafCount, PCNode* leaf, std::vector* added = nullptr); + + /** + * Merge multiple leaves into a single one and return it. + * + * @param consecutiveLeaves The leaves that shall be merged. + * @param assumeConsecutive Set to \c true if you already called makeConsecutive() on the leaves. + * @return The entry of \p consecutiveLeaves into which all other leaves got merged. + */ + PCNode* mergeLeaves(const std::vector& consecutiveLeaves, + bool assumeConsecutive = false) { + return mergeLeaves(consecutiveLeaves.begin(), consecutiveLeaves.end(), assumeConsecutive); + } + + /** + * Merge multiple leaves into a single one and return it. + * + * @param begin, end Iterator range spanning the leaves that shall be merged. + * @param assumeConsecutive Set to \c true if you already called makeConsecutive() on the leaves. + * @return The entry of \p consecutiveLeaves into which all other leaves got merged. + */ + template + PCNode* mergeLeaves(It begin, It end, bool assumeConsecutive = false) { + OGDF_ASSERT(begin != end); + + if (!assumeConsecutive && !makeConsecutive(begin, end)) { + return nullptr; + } + + // Remove all consecutive leaves except the first one. + It back = prev(end); + for (auto it = begin; it != back; ++it) { + destroyLeaf(*it); + } + + OGDF_HEAVY_ASSERT(checkValid()); + + // Return the remaining leaf. + return *back; + } + + /** + * Remove a leaf and also any newly-introduced inner degree-2 or -1 nodes. + * Unlike destroyNode(), the leaf \p leaf must still be attached (so that its parent's degree + * can be checked) + * @sa destroyNode() + */ + void destroyLeaf(PCNode* leaf); + + /** + * Overwrite the stored root for this PC-tree. + * + * Note that the passed \p newRoot needs to be valid as root for this tree. + * @return The old node stored as root. + * @sa changeRoot() + */ + PCNode* setRoot(PCNode* newRoot); + + /** + * Change the orientation of edges such that \p newRoot becomes the root of the tree. + * + * @return The previous root. + * @sa setRoot() + */ + PCNode* changeRoot(PCNode* newRoot); + + /** + * Change the type of a node and update all its registrations. + * @return the previous type of the node. + */ + PCNodeType changeNodeType(PCNode* node, PCNodeType newType); + + /** + * Insert tree \p tree into this tree at node \p at. + * + * Both trees need to be part of the same forest. All observers of \p tree will be moved to this tree. + * If \p at is a leaf, it will be replaced by \p tree, otherwise \p tree will be appended as child of \p at. + */ + void insertTree(PCNode* at, PCTree* tree); + +private: + void unregisterNode(PCNode* node); + + void registerNode(PCNode* node); + + //! @} + +public: + /** + * @name Restrictions + */ + //! @{ + /** + * @return \c true if calling makeConsecutive() with \p size leaves never requires changes to the tree. + * This is the case for \p size values 0, 1, getLeafCount() - 1, and getLeafCount(). + * @sa pc_tree::isTrivialRestriction() + */ + bool isTrivialRestriction(int size) const; + + bool makeConsecutive(std::initializer_list consecutiveLeaves) { + return makeConsecutive(consecutiveLeaves.begin(), consecutiveLeaves.end()); + } + + bool makeConsecutive(const std::vector& consecutiveLeaves) { + return makeConsecutive(consecutiveLeaves.begin(), consecutiveLeaves.end()); + } + + /** + * Make the leaves contained in the range denoted by iterators \p begin (inclusive) to + * \p end (exclusive) consecutive in all represented orders. + * This is equivalent to calling resetTempData() and markFull(It, It, std::vector*) followd by makeFullNodesConsecutive(). + * @return \c true if the update was successful, \c false if the leaves cannot be made + * consecutive and the tree was left unchanged. + */ + template + bool makeConsecutive(It begin, It end) { + FullLeafIter iter = [&begin, &end]() { return NextFullLeaf(begin, end); }; + for (auto obs : m_observers) { + obs->makeConsecutiveCalled(*this, iter); + } + + OGDF_HEAVY_ASSERT(checkValid()); + resetTempData(); + +#ifdef OGDF_DEBUG + for (auto it = begin; it != end; ++it) { + PCNode* leaf = *it; + OGDF_ASSERT(leaf); + OGDF_ASSERT(leaf->isLeaf()); + OGDF_ASSERT(leaf->m_forest == m_forest); + } +#endif + if (isTrivialRestriction(end - begin)) { + for (auto obs : m_observers) { + obs->makeConsecutiveDone(*this, Observer::Stage::Trivial, true); + } + return true; + } + + // PC_PROFILE_ENTER(1, "label"); + markFull(begin, end); + // PC_PROFILE_EXIT(1, "label"); + + return makeFullNodesConsecutive(); + } + + /** + * Reset all makeConsecutive()-related temporary information, especially which leaves are full (should be made consecutive). + */ + void resetTempData() { + m_forest->m_timestamp++; + m_firstPartial = m_lastPartial = nullptr; + m_partialCount = 0; + m_apexCandidate = nullptr; + m_apexCandidateIsFix = false; + m_terminalPathLength = 0; + m_apexTPPred2 = nullptr; + } + + /** + * Only marks leaves full, does not update partial/full info of parents. + * Use markFull(It, It, std::vector*) to also update parents for use with makeFullNodesConsecutive(). + */ + template + void markLeavesFull(It begin, It end) { + for (auto it = begin; it != end; ++it) { + PCNode* leaf = *it; + OGDF_ASSERT(leaf); + OGDF_ASSERT(leaf->isLeaf()); + OGDF_ASSERT(leaf->m_forest == m_forest); + leaf->setLabel(NodeLabel::Full); + } + } + + /** + * Marks the leaves contained in the range denoted by iterators \p begin (inclusive) to + * \p end (exclusive) full, that is marked for being made consecutive in all represented orders. + * Also propagates the markings to parents, which is required for makeFullNodesConsecutive(). + */ + template + void markFull(It begin, It end, std::vector* fullNodeOrder = nullptr) { + // Attention: This method no longer uses a queue to defer processing of partial/full parents to after + // all leaves are done, but now directly make parents full if all of their children are full. + if (fullNodeOrder != nullptr) { + fullNodeOrder->reserve(m_cNodeCount + m_pNodeCount); + } + + for (auto it = begin; it != end; ++it) { + PCNode* full_parent = markFull(*it, fullNodeOrder); + while (full_parent != nullptr) { + full_parent = markFull(full_parent, fullNodeOrder); + } + } + } + + /** + * Updates the tree to make all leaves marked as full consecutive in all represented orders. + * Requires labels of parents to be correctly set by markFull(It, It, std::vector*). + * @return \c true if the update was successful, \c false if the leaves cannot be made + * consecutive and the tree was left unchanged. + */ + bool makeFullNodesConsecutive(); + +private: + /* see the paper for more info on how the update works with the following methods */ + + PCNode* markFull(PCNode* full_node, std::vector* fullNodeOrder = nullptr); + + bool findTerminalPath(); + + void updateSingletonTerminalPath(); + + PCNode* createCentralNode(); + + int updateTerminalPath(PCNode* central, PCNode* tpNeigh); + + // labeling / TP finding + + void addPartialNode(PCNode* partial); + + void removePartialNode(PCNode* partial); + + bool checkTPPartialCNode(PCNode* node); + + size_t findEndOfFullBlock(PCNode* node, PCNode* pred, PCNode* curr, PCNode*& fullEnd, + PCNode*& emptyEnd) const; + + bool setApexCandidate(PCNode* ac, bool fix = false); + + // update + + void replaceTPNeigh(PCNode* central, PCNode* oldTPNeigh, PCNode* newTPNeigh, + PCNode* newFullNeigh, PCNode* otherEndOfFullBlock); + + PCNode* splitOffFullPNode(PCNode* node, bool skip_parent); + + //! @} + +public: + /** + * @name Intersect + */ + //! @{ + /** + * Intersect the restrictions represented by this tree with those represented by \p other, + * given a bijection \p mapping from the leaves of \p other to this trees' leaves. + * @return \c true if the intersection is non-empty and this now represented by this tree. + * Otherwise, the intersection is empty and the state of this tree is undefined. + */ + bool intersect(PCTree& other, PCTreeNodeArray& mapping); + +private: + bool findNodeRestrictions(PCTree& applyTo, PCTreeNodeArray& mapping, + PCTreeNodeArray>& blockNodes, + PCTreeNodeArray>& subtreeNodes, + PCTreeNodeArray& leafPartner, PCTreeNodeArray& isFront); + + void restoreSubtrees(PCTreeNodeArray>& blockNodes, + PCTreeNodeArray>& subtreeNodes, + PCTreeNodeArray& leafPartner, PCTreeNodeArray& isFront); + + //! @} + +public: + /** + * @name Getters + */ + //! @{ + operator const PCTreeRegistry&() const { return m_forest->m_nodeArrayRegistry; } + + //! The PCTreeForest this PCTree belongs to, or nullptr. + [[nodiscard]] PCTreeForest* getForest() const { return m_forest; } + + //! Whether this PCTree allows all orders (consists of a single P-node). + [[nodiscard]] bool isTrivial() const; + + [[nodiscard]] const IntrusiveList& getLeaves() const { return m_leaves; } + + [[nodiscard]] size_t getLeafCount() const { return m_leaves.size(); }; + + [[nodiscard]] size_t getPNodeCount() const { return m_pNodeCount; } + + [[nodiscard]] size_t getCNodeCount() const { return m_cNodeCount; } + + [[nodiscard]] PCNode* getRootNode() const { return m_rootNode; } + + /** + * @return the length of the terminal path in the last update operation + */ + int getTerminalPathLength() const { return m_terminalPathLength; } + + //! An iterable through all nodes of this PCTree. + FilteringPCTreeDFS allNodes() const { return FilteringPCTreeDFS(*this, m_rootNode); } + + //! An iterable through all inner (non-leaf) nodes of this PCTree. + FilteringPCTreeDFS innerNodes() const { + return FilteringPCTreeDFS(*this, m_rootNode, [](PCNode* node) { return !node->isLeaf(); }); + } + + //! Store the order of leaves currently represented by this tree in \p container. + template + void currentLeafOrder(Container& container) const { + for (PCNode* leaf : FilteringPCTreeDFS(*this, m_rootNode)) { + if (leaf->isLeaf()) { + container.push_back(leaf); + } + } + OGDF_ASSERT(container.size() == m_leaves.size()); + } + + std::vector currentLeafOrder() const { + std::vector container; + currentLeafOrder(container); + return container; + } + + //! Validity check for debugging assertions. + bool checkValid(bool allow_small_deg = true) const; + + //! Check whether the order \p order is represented by this tree. + bool isValidOrder(const std::vector& order) const; + + //! Get a graphical representation of this tree as Graph. + // void getTree(ogdf::Graph& tree, ogdf::GraphAttributes* g_a, + // PCTreeNodeArray& pc_repr, ogdf::NodeArray* g_repr = nullptr, + // bool mark_full = false, bool show_sibs = false) const; + + /** + * Get a list of all cyclic restrictions used to generate this tree. + * If a \p fixedLeaf was given, the restrictions will linear with none of them containing \p fixedLeaf. + */ + void getRestrictions(std::vector>& restrictions, + PCNode* fixedLeaf = nullptr) const; + + //! Calculate the number of cyclic orders represented by this tree. + template + R possibleOrders() const { + R orders(1); + for (PCNode* node : innerNodes()) { + if (node->getNodeType() == PCNodeType::CNode) { + orders *= 2; + } else { + int children(node->getChildCount()); + if (node == m_rootNode) { + children -= 1; // don't count circular shifts + } + orders *= factorial(children); + } + } + return orders; + } + + /** + * Print a deterministic and unique representation of this PCTree to \p os. + * Unique node IDs and a deterministic order of nodes' children is generated using \p printNode and \p compareNodes, respectively. + * @sa pc_tree::uid_utils + */ + std::ostream& uniqueID(std::ostream& os, + const std::function& printNode = uid_utils::nodeToID, + const std::function& compareNodes = uid_utils::compareNodesByID); + + std::string uniqueID( + const std::function& printNode = uid_utils::nodeToID, + [[maybe_unused]] const std::function& compareNodes = + uid_utils::compareNodesByID) { + std::stringstream sb; + uniqueID(sb, printNode, compareNodes); + return sb.str(); + } + + //! @} + +public: + /** + * @name Observers + */ + //! @{ + template + struct NextFullLeaf { + It m_it; + It m_end; + + NextFullLeaf(It it, It an_end) : m_it(it), m_end(an_end) { } + + PCNode* operator()() { + if (m_it == m_end) { + return nullptr; + } + PCNode* n = *m_it; + ++m_it; + return n; + } + }; + + using FullLeafIter = std::function()>; + + //! Interface for Observers that can be notified of all changes made to the tree during an update. + struct Observer { + enum class Stage { Trivial, NoPartials, InvalidTP, SingletonTP, Done }; + + virtual void onNodeCreate([[maybe_unused]] PCNode* node) {}; + + virtual void makeConsecutiveCalled([[maybe_unused]] PCTree& tree, + [[maybe_unused]] FullLeafIter consecutiveLeaves) {}; + + virtual void labelsAssigned([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* firstPartial, [[maybe_unused]] PCNode* lastPartial, + [[maybe_unused]] int partialCount) {}; + + virtual void terminalPathFound([[maybe_unused]] PCTree& tree, [[maybe_unused]] PCNode* apex, + [[maybe_unused]] PCNode* apexTPPred2, [[maybe_unused]] int terminalPathLength) {}; + + virtual void centralCreated([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* central) {}; + + virtual void beforeMerge([[maybe_unused]] PCTree& tree, [[maybe_unused]] int count, + [[maybe_unused]] PCNode* tpNeigh) {}; + + virtual void afterMerge([[maybe_unused]] PCTree& tree, [[maybe_unused]] PCNode* successor, + [[maybe_unused]] PCNode* mergedNode) {}; + + virtual void whenPNodeMerged([[maybe_unused]] PCTree& tree, [[maybe_unused]] PCNode* tpNeigh, + [[maybe_unused]] PCNode* tpPred, [[maybe_unused]] PCNode* fullNeigh) {}; + + virtual void whenCNodeMerged([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* tpNeigh, [[maybe_unused]] bool tpNeighSiblingsFlipped, + [[maybe_unused]] PCNode* fullNeigh, [[maybe_unused]] PCNode* fullOuterChild) {}; + + virtual void fullNodeSplit([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* fullNode) {}; + + virtual void makeConsecutiveDone([[maybe_unused]] PCTree& tree, + [[maybe_unused]] Stage stage, [[maybe_unused]] bool success) {}; + + virtual void onApexMoved([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* apexCandidate, [[maybe_unused]] PCNode* central, + [[maybe_unused]] PCNode* parent) {}; + + virtual void nodeDeleted([[maybe_unused]] PCTree& tree, + [[maybe_unused]] PCNode* toBeDeleted) {}; + virtual void nodeReplaced([[maybe_unused]] PCTree& tree, [[maybe_unused]] PCNode* replaced, + [[maybe_unused]] PCNode* replacement) {}; + }; + + struct LoggingObserver : public Observer { + void makeConsecutiveCalled(PCTree& tree, FullLeafIter consecutiveLeaves) override; + + void labelsAssigned(PCTree& tree, PCNode* firstPartial, PCNode* lastPartial, + int partialCount) override; + + void terminalPathFound(PCTree& tree, PCNode* apex, PCNode* apexTPPred2, + int terminalPathLength) override; + + void centralCreated(PCTree& tree, PCNode* central) override; + + void beforeMerge(PCTree& tree, int count, PCNode* tpNeigh) override; + + void makeConsecutiveDone(PCTree& tree, Stage stage, bool success) override; + }; + + std::list::const_iterator addObserver(Observer* observer) { + m_observers.push_back(observer); + return --m_observers.end(); + } + + void removeObserver(std::list::const_iterator it) { m_observers.erase(it); } + + void removeObserver(Observer* observer) { m_observers.remove(observer); } + + //! @} +}; + +int PCTreeRegistry::keyToIndex(PCNode* key) { return key->index(); } + +} diff --git a/src/pctree/PCTreeForest.cpp b/src/pctree/PCTreeForest.cpp new file mode 100644 index 0000000..9758d9d --- /dev/null +++ b/src/pctree/PCTreeForest.cpp @@ -0,0 +1,84 @@ +/** \file + * \brief Implementation for pc_tree::PCTreeForest + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCTree.h" +#include "PCTreeForest.h" + +using namespace pc_tree; + +PCTreeForest::~PCTreeForest() { + clear(); +#ifdef OGDF_PCTREE_REUSE_NODES + while (m_reusableNodes) { + PCNode* tmp = m_reusableNodes; + m_reusableNodes = m_reusableNodes->m_parentPNode; + delete tmp; + } +#endif +} + +// forest auto deletes allocated trees when destructed +PCTree* PCTreeForest::makeTree() { + PCTree* tree = new PCTree(this); + m_trees.push_back(tree); + + return tree; +} + +void PCTreeForest::clear() { + if (m_autodelete) { + for (auto* k : m_trees) { + delete k; + } + } + + m_trees.clear(); + m_trees.shrink_to_fit(); + m_cNodes.clear(); + m_cNodes.shrink_to_fit(); + m_parents.init(); + m_nextNodeId = 0; + m_timestamp = 0; +} + +bool PCTreeRegistry::isKeyAssociated(PCNode* key) const { +#ifdef OGDF_DEBUG + return key && key->getForest() == m_pForest; +#else + return key; +#endif +} + +int PCTreeRegistry::calculateArraySize(int add) const { + return calculateTableSize(m_pForest->m_nextNodeId + add); +} + +int PCTreeRegistry::maxKeyIndex() const { return m_pForest->m_nextNodeId - 1; } diff --git a/src/pctree/PCTreeForest.h b/src/pctree/PCTreeForest.h new file mode 100644 index 0000000..c47de7a --- /dev/null +++ b/src/pctree/PCTreeForest.h @@ -0,0 +1,90 @@ +/** \file + * \brief PCTreeForests contain multiple PCTrees that can be merged with each other. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "PCEnum.h" +#include "PCRegistry.h" +#include "util/DisjointSets.h" + +#include +#include +#include + +#define OGDF_PCTREE_REUSE_NODES + +namespace pc_tree { +using UnionFindIndex = std::size_t; + +const UnionFindIndex UNIONFINDINDEX_EMPTY = std::numeric_limits::max(); + +/** + * Multiple PCTrees can be created within the same PCTreeForest, which allows merging the trees later on by making one + * a child of another. This is extensively used during planarity testing. + * @sa PCTree:insertTree() + */ +class OGDF_EXPORT PCTreeForest { + friend class PCNode; + friend class PCTree; + friend class PCTreeRegistry; + +private: + std::vector m_trees; + std::vector m_cNodes; + DisjointSets<> m_parents {1 << 8}; + int m_nextNodeId = 0; + size_t m_timestamp = 0; + PCTreeRegistry m_nodeArrayRegistry; + bool m_autodelete; + +#ifdef OGDF_PCTREE_REUSE_NODES + PCNode* m_reusableNodes = nullptr; +#endif + +public: + /** + * @param autodelete whether the trees created by makeTree() should be deleted automatically + * on destruction of this forrest. Note that this does not affect PCTrees directly created by + * calling PCTree::PCTree(PCTreeForest*). + */ + PCTreeForest(bool autodelete = true) : m_nodeArrayRegistry(this), m_autodelete(autodelete) {}; + + virtual ~PCTreeForest(); + + //! Create a new tree that may be automatically deleted when this forest is deleted. + PCTree* makeTree(void); + + //! Delete all trees created by makeTree(). + void clear(void); + + operator const PCTreeRegistry&() const { return m_nodeArrayRegistry; } +}; +} diff --git a/src/pctree/PCTreeIterators.h b/src/pctree/PCTreeIterators.h new file mode 100644 index 0000000..6f27d9a --- /dev/null +++ b/src/pctree/PCTreeIterators.h @@ -0,0 +1,218 @@ +/** \file + * \brief Utils for PCTree::allNodes(), PCTree::innerNodes(), PCNode::children() and PCNode::neighbors(). + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "PCEnum.h" +#include "PCNode.h" + +#include +#include +#include +#include + +namespace pc_tree { +class OGDF_EXPORT PCNodeIterator { + friend struct PCNodeChildrenIterable; + friend struct PCNodeNeighborsIterable; + + PCNode* m_node = nullptr; + PCNode* m_pred = nullptr; + PCNode* m_curr = nullptr; + + PCNodeIterator(PCNode* node, PCNode* pred, PCNode* curr) + : m_node(node), m_pred(pred), m_curr(curr) { } + +public: + using iterator_category = std::forward_iterator_tag; + using value_type = PCNode; + using pointer = PCNode*; + using reference = PCNode&; + using difference_type = std::ptrdiff_t; + + PCNodeIterator() = default; + + PCNode& operator->() const { return *m_curr; } + + PCNode* operator*() const { return m_curr; } + + //! Increment operator (prefix, returns result). + PCNodeIterator& operator++(); + + //! Increment operator (postfix, returns previous value). + PCNodeIterator operator++(int); + + bool operator==(const PCNodeIterator& rhs) const { + return m_node == rhs.m_node && m_pred == rhs.m_pred && m_curr == rhs.m_curr; + } + + bool operator!=(const PCNodeIterator& rhs) const { return !(rhs == *this); } + + PCNode* nodeOf() const { return m_node; } + + bool isParent(); +}; + +struct OGDF_EXPORT PCNodeChildrenIterable { + PCNode* const m_node; + + explicit PCNodeChildrenIterable(PCNode* node) : m_node(node) { } + + PCNodeIterator begin() const noexcept; + + PCNodeIterator end() const noexcept; + + unsigned long count() const; +}; + +struct OGDF_EXPORT PCNodeNeighborsIterable { + PCNode* const m_node; + PCNode* const m_first; + + explicit PCNodeNeighborsIterable(PCNode* node, PCNode* first = nullptr) + : m_node(node) + , m_first(first != nullptr + ? first + : (node->m_child1 != nullptr ? node->m_child1 : node->getParent())) { + if (this->m_first == nullptr) { + OGDF_ASSERT(this->m_node->getDegree() == 0); + } else { + OGDF_ASSERT(this->m_node->isParentOf(this->m_first) + || this->m_first->isParentOf(this->m_node)); + } + } + + PCNodeIterator begin() const noexcept; + + PCNodeIterator end() const noexcept; + + unsigned long count() const; +}; + +/** + * A DFS or BFS through a PCTree. + * @sa FilteringBFS + */ +template +class FilteringPCTreeWalk { + using container_type = + typename std::conditional, std::deque>::type; + + container_type m_pending; + std::function m_visit; + std::function m_descend; + +public: + // iterator traits + using iterator_category = std::input_iterator_tag; + using value_type = PCNode*; + using difference_type = std::ptrdiff_t; + using pointer = PCNode**; + using reference = PCNode*&; + + static bool return_true([[maybe_unused]] PCNode* n) { return true; } + + explicit FilteringPCTreeWalk() = default; + + explicit FilteringPCTreeWalk([[maybe_unused]] const PCTree& T, PCNode* start, + std::function visit = return_true, + std::function descend_from = return_true) + : m_pending({start}), m_visit(std::move(visit)), m_descend(std::move(descend_from)) { + if (!m_pending.empty() && !m_visit(top())) { + next(); + } + } + + bool operator==(const FilteringPCTreeWalk& rhs) const { return m_pending == rhs.m_pending; } + + bool operator!=(const FilteringPCTreeWalk& rhs) const { return m_pending != rhs.m_pending; } + + FilteringPCTreeWalk& begin() { return *this; } + + FilteringPCTreeWalk end() const { return FilteringPCTreeWalk(); } + + PCNode* top() { + OGDF_ASSERT(!m_pending.empty()); + if constexpr (dfs) { + return m_pending.back(); + } else { + return m_pending.front(); + } + } + + PCNode* operator*() { return top(); } + + //! Increment operator (prefix, returns result). + FilteringPCTreeWalk& operator++() { + next(); + return *this; + } + + //! Increment operator (postfix, returns previous value). + OGDF_DEPRECATED("Calling FilteringPCTreeWalk++ will copy the array of pending nodes") + + FilteringPCTreeWalk operator++(int) { + FilteringPCTreeWalk before = *this; + next(); + return before; + } + + void next() { + do { + OGDF_ASSERT(!m_pending.empty()); + PCNode* node = top(); + if constexpr (dfs) { + m_pending.pop_back(); + } else { + m_pending.pop_front(); + } + if (m_descend(node)) { + std::copy(node->children().begin(), node->children().end(), + std::back_inserter(m_pending)); + if constexpr (reverse) { + std::reverse(m_pending.end() - node->getChildCount(), m_pending.end()); + } + } + } while (!m_pending.empty() && !m_visit(top())); + } + + explicit operator bool() const { return valid(); } + + bool valid() const { return !m_pending.empty(); } + + void append(PCNode* a) { m_pending.push(a); } + + int pendingCount() const { return m_pending.size(); } +}; + +using FilteringPCTreeDFS = FilteringPCTreeWalk; +using FilteringPCTreeBFS = FilteringPCTreeWalk; +} diff --git a/src/pctree/PCTree_basic.cpp b/src/pctree/PCTree_basic.cpp new file mode 100644 index 0000000..01d45d4 --- /dev/null +++ b/src/pctree/PCTree_basic.cpp @@ -0,0 +1,753 @@ +/** \file + * \brief Implementation for pc_tree::PCTree basic methods + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCNode.h" +#include "PCTree.h" + +#include +#include +#include + +namespace pc_tree { + +bool PCTree::isTrivial() const { + if (m_leaves.empty()) { + OGDF_ASSERT(m_rootNode == nullptr); + return true; + } + return m_rootNode->getNodeType() == PCNodeType::PNode + && m_rootNode->m_childCount == m_leaves.size(); +} + +// void PCTree::getTree(Graph& tree, GraphAttributes* g_a, PCTreeNodeArray& pc_repr, +// ogdf::NodeArray* g_repr, bool mark_full, bool show_sibs) const { +// tree.clear(); +// +// if (m_leaves.empty()) { +// return; +// } +// +// bool nodeGraphics = false, nodeLabel = false, edgeStyle = false, edgeLabel = false; +// if (g_a != nullptr) { +// nodeGraphics = g_a->has(GraphAttributes::nodeGraphics); +// nodeLabel = g_a->has(GraphAttributes::nodeLabel); +// edgeStyle = g_a->has(GraphAttributes::edgeStyle); +// edgeLabel = g_a->has(GraphAttributes::edgeLabel); +// } +// +// for (PCNode* pc_node : allNodes()) { +// ogdf::node g_node = pc_repr[pc_node] = tree.newNode(); +// if (g_repr != nullptr) { +// (*g_repr)[g_node] = pc_node; +// } +// +// if (nodeGraphics) { +// if (pc_node->m_nodeType == PCNodeType::CNode) { +// g_a->shape(g_node) = Shape::Rhomb; +// } else if (pc_node->m_nodeType == PCNodeType::PNode) { +// g_a->shape(g_node) = Shape::Ellipse; +// } else { +// OGDF_ASSERT(pc_node->isLeaf()); +// g_a->shape(g_node) = Shape::Triangle; +// } +// if (mark_full) { +// NodeLabel label = pc_node->getLabel(); +// if (label == NodeLabel::Full) { +// g_a->fillColor(g_node) = (Color(Color::Name::Darkblue)); +// } else if (label == NodeLabel::Partial) { +// g_a->fillColor(g_node) = (Color(Color::Name::Lightblue)); +// } +// } +// } +// if (nodeLabel) { +// g_a->label(g_node) = std::to_string(pc_node->index()); +// } +// PCNode* parent = pc_node->getParent(); +// if (parent != nullptr) { +// ogdf::edge e = tree.newEdge(pc_repr[parent], g_node); +// if (edgeStyle) { +// if (pc_node->m_parentPNode != nullptr) { +// g_a->strokeType(e) = ogdf::StrokeType::Solid; +// } else if (pc_node->m_parentCNodeId != UNIONFINDINDEX_EMPTY) { +// g_a->strokeType(e) = ogdf::StrokeType::Dot; +// } else { +// g_a->strokeType(e) = ogdf::StrokeType::None; +// } +// +// if (parent->getChild1() == pc_node) { +// g_a->strokeColor(e) = (Color(Color::Name::Red)); +// } +// if (parent->getChild2() == pc_node) { +// g_a->strokeColor(e) = (Color(Color::Name::Green)); +// } +// } +// if (edgeLabel) { +// if (parent->getChild1() == pc_node) { +// g_a->label(e) = "c1"; +// } +// if (parent->getChild2() == pc_node) { +// g_a->label(e) = "c2"; +// } +// } +// } +// } +// if (!show_sibs) { +// return; +// } +// for (PCNode* pc_node : allNodes()) { +// ogdf::node g_node = pc_repr[pc_node]; +// // PCNode* parent = pc_node->getParent(); +// if (pc_node->getSibling1() != nullptr) { +// ogdf::edge e = tree.newEdge(g_node, pc_repr[pc_node->getSibling1()]); +// if (edgeStyle) { +// g_a->strokeType(e) = ogdf::StrokeType::Dash; +// g_a->strokeColor(e) = Color(Color::Name::Red); +// } +// if (edgeLabel) { +// g_a->label(e) = "s1"; +// } +// } +// if (pc_node->getSibling2() != nullptr) { +// ogdf::edge e = tree.newEdge(g_node, pc_repr[pc_node->getSibling2()]); +// if (edgeStyle) { +// g_a->strokeType(e) = ogdf::StrokeType::Dash; +// g_a->strokeColor(e) = Color(Color::Name::Green); +// } +// if (edgeLabel) { +// g_a->label(e) = "s2"; +// } +// } +// } +// } + +std::ostream& operator<<(std::ostream& os, const PCTree& tree) { return os << &tree; } + +std::ostream& operator<<(std::ostream& os, const PCTree* tree) { + std::stack> stack; + stack.push(tree->m_rootNode); + if (tree->m_rootNode == nullptr) { + return os << "empty"; + } + + while (!stack.empty()) { + auto next = stack.top(); + stack.pop(); + + // Next stack element is either a string we need to append or an arc pointing to a + // subtree we still need to process. + if (std::holds_alternative(next)) { + os << std::get(next); + continue; + } + + PCNode* base = std::get(next); + if (base->m_nodeType == PCNodeType::CNode) { + os << base->m_id << ":["; + stack.push("]"); + } else if (base->m_nodeType == PCNodeType::PNode) { + os << base->m_id << ":("; + stack.push(")"); + } else { + OGDF_ASSERT(base->m_nodeType == PCNodeType::Leaf); + if (base != tree->m_rootNode) { + os << base->m_id; + continue; + } else { + os << base->m_id << ":{"; + stack.push("}"); + } + } + + bool space = false; + for (PCNode* node : base->children()) { + if (space) { + stack.push(", "); + } + OGDF_ASSERT(node != nullptr); + stack.emplace(node); + space = true; + } + } + + return os; +} + +void uid_utils::nodeToID(std::ostream& os, PCNode* n, int pos) { + os << n->index(); + if (!n->isLeaf()) { + os << ":"; + } +} + +void uid_utils::nodeToPosition(std::ostream& os, PCNode* n, int pos) { + os << pos; + if (!n->isLeaf()) { + os << ":"; + } +} + +void uid_utils::leafToID(std::ostream& os, PCNode* n, int pos) { + if (n->isLeaf()) { + os << n->index(); + } +} + +void uid_utils::leafToPosition(std::ostream& os, PCNode* n, int pos) { + if (n->isLeaf()) { + os << pos; + } +} + +bool uid_utils::compareNodesByID(PCNode* a, PCNode* b) { return a->index() < b->index(); } + +std::ostream& PCTree::uniqueID(std::ostream& os, + const std::function& printNode, + const std::function& compareNodes) { + if (m_rootNode == nullptr) { + return os << "empty"; + } + std::vector sortedLeaves(m_leaves.begin(), m_leaves.end()); + std::sort(sortedLeaves.begin(), sortedLeaves.end(), compareNodes); + + PCTreeNodeArray order(*this, -1); + int i = 0; + for (PCNode* leaf : sortedLeaves) { + order[leaf] = i++; + } + + PCNode* lastLeaf = sortedLeaves.back(); + sortedLeaves.resize(m_leaves.size() - 1); + std::vector fullNodeOrder; + { + resetTempData(); + markFull(sortedLeaves.begin(), sortedLeaves.end(), &fullNodeOrder); + } + for (PCNode* node : fullNodeOrder) { + order[node] = i++; + } + + std::stack> stack; + stack.push(fullNodeOrder.back()); + while (!stack.empty()) { + auto next = stack.top(); + stack.pop(); + + if (std::holds_alternative(next)) { + os << std::get(next); + continue; + } + + PCNode* node = std::get(next); + std::list children; + if (node->m_nodeType == PCNodeType::CNode) { + printNode(os, node, order[node]); + os << "["; + stack.push("]"); + if (node == fullNodeOrder.back()) { + children.assign(node->neighbors().begin(), node->neighbors().end()); + PCNode* minChild = *std::min_element(children.begin(), children.end(), + [&order](PCNode* elem, PCNode* min) { return order[elem] < order[min]; }); + while (children.front() != minChild) { + children.push_back(children.front()); + children.pop_front(); + } + PCNode* second = *(++children.begin()); + if (order[second] > order[children.back()]) { + children.push_back(children.front()); + children.pop_front(); + children.reverse(); + } + second = *(++children.begin()); + OGDF_ASSERT(children.front() == minChild); + OGDF_ASSERT(order[second] < order[children.back()]); + } else { + PCNode* informedNeighbor = nullptr; + for (PCNode* neigh : node->neighbors()) { + if (order[neigh] > order[node]) { + OGDF_ASSERT(informedNeighbor == nullptr); + informedNeighbor = neigh; + } + } + OGDF_ASSERT(informedNeighbor != nullptr); + PCNode* neigh1 = node->getNextNeighbor(nullptr, informedNeighbor); + PCNode* neigh2 = node->getNextNeighbor(neigh1, informedNeighbor); + if (order[neigh2] < order[neigh1]) { + std::swap(neigh1, neigh2); + } + for (PCNode *pred = informedNeighbor, *curr = neigh1; curr != informedNeighbor; + node->proceedToNextNeighbor(pred, curr)) { + children.push_back(curr); + } + } + } else if (node->m_nodeType == PCNodeType::PNode) { + printNode(os, node, order[node]); + if (node->getDegree() <= 3) { + os << "["; + stack.push("]"); + } else { + os << "("; + stack.push(")"); + } + std::vector& fullNeighbors = node->tempInfo().fullNeighbors; + children.assign(fullNeighbors.begin(), fullNeighbors.end()); + if (node == fullNodeOrder.back()) { + children.push_back(lastLeaf); + } + children.sort([&order](PCNode* a, PCNode* b) { return order[a] < order[b]; }); + } else { + OGDF_ASSERT(node->m_nodeType == PCNodeType::Leaf); + printNode(os, node, order[node]); + continue; + } + if (node == fullNodeOrder.back()) { + OGDF_ASSERT(children.size() == node->tempInfo().fullNeighbors.size() + 1); + } else { + OGDF_ASSERT(children.size() == node->tempInfo().fullNeighbors.size()); + } + OGDF_ASSERT(order[children.front()] < order[children.back()]); + + bool space = false; + for (PCNode* child : children) { + if (space) { + stack.push(", "); + } + OGDF_ASSERT(child != nullptr); + stack.push(child); + space = true; + } + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const PCNodeType t) { + switch (t) { + case PCNodeType::Leaf: + return os << "Leaf"; + case PCNodeType::PNode: + return os << "PNode"; + case PCNodeType::CNode: + return os << "CNode"; + default: + OGDF_ASSERT(false); + return os << "PCNodeType???"; + } +} + +std::ostream& operator<<(std::ostream& os, const NodeLabel l) { + switch (l) { + case NodeLabel::Unknown: + return os << "Empty/Unknown"; + case NodeLabel::Partial: + return os << "Partial"; + case NodeLabel::Full: + return os << "Full"; + default: + OGDF_ASSERT(false); + return os << "NodeLabel???"; + } +} + +bool PCTree::isValidOrder(const std::vector& order) const { + OGDF_ASSERT(order.size() == m_leaves.size()); + PCTreeNodeArray leafMapping(*this); + PCTree copy(*this, leafMapping); + PCNode* previous = nullptr; + for (PCNode* node : order) { + OGDF_ASSERT(node->m_forest == m_forest); + if (previous == nullptr) { + previous = node; + continue; + } + if (!copy.makeConsecutive({leafMapping[previous], leafMapping[node]})) { + return false; + } + previous = node; + } +#ifdef OGDF_DEBUG + OGDF_ASSERT(copy.possibleOrders() == 2); + OGDF_ASSERT(copy.makeConsecutive({leafMapping[order.front()], leafMapping[order.back()]})); + std::list res_order; + copy.currentLeafOrder(res_order); + OGDF_ASSERT(res_order.size() == order.size()); + for (size_t i = 0; res_order.front() != leafMapping[order.front()]; i++) { + res_order.push_back(res_order.front()); + res_order.pop_front(); + OGDF_ASSERT(i < order.size()); + } + if (res_order.back() != leafMapping[order.back()]) { + std::reverse(res_order.begin(), res_order.end()); + res_order.push_front(res_order.back()); + res_order.pop_back(); + } + OGDF_ASSERT(res_order.size() == order.size()); + int i = 0; + for (PCNode* n : res_order) { + OGDF_ASSERT(n == leafMapping[order.at(i)]); + ++i; + } +#endif + + return true; +} + +int PCTREE_DEBUG_CHECK_FREQ = 10; +int PCTREE_DEBUG_CHECK_CNT = 0; + +bool PCTree::checkValid(bool allow_small_deg) const { +#ifdef OGDF_DEBUG + if (PCTREE_DEBUG_CHECK_FREQ == 0) { + return true; + } + ++PCTREE_DEBUG_CHECK_CNT; + if (PCTREE_DEBUG_CHECK_CNT % PCTREE_DEBUG_CHECK_FREQ != 0) { + return true; + } + + if (m_rootNode == nullptr) { + OGDF_ASSERT(m_leaves.size() == 0); + OGDF_ASSERT(m_pNodeCount == 0); + OGDF_ASSERT(m_cNodeCount == 0); + return true; + } + + OGDF_ASSERT(m_rootNode->getParent() == nullptr); + for (PCNode* leaf : m_leaves) { + OGDF_ASSERT(leaf->isLeaf()); + if (leaf == m_rootNode) { + OGDF_ASSERT(leaf->getChildCount() <= 1); + } else { + OGDF_ASSERT(leaf->getChildCount() == 0); + } + } + + if (m_leaves.size() == 0) { + OGDF_ASSERT(m_rootNode->getDegree() == 0); + OGDF_ASSERT(!m_rootNode->isLeaf()); + OGDF_ASSERT(m_pNodeCount + m_cNodeCount == 1); + return true; + } else if (m_leaves.size() == 1) { + if (m_rootNode->isLeaf()) { + OGDF_ASSERT(m_rootNode == m_leaves.front()); + if (m_rootNode->getDegree() == 1) { + OGDF_ASSERT(m_pNodeCount + m_cNodeCount == 1); + OGDF_ASSERT(m_rootNode->getOnlyChild()->isValidNode(m_forest)); + OGDF_ASSERT(m_rootNode->getOnlyChild()->getChildCount() == 0); + OGDF_ASSERT(!m_rootNode->getOnlyChild()->isLeaf()); + } else { + OGDF_ASSERT(m_pNodeCount + m_cNodeCount == 0); + } + } else { + OGDF_ASSERT(m_rootNode->getOnlyChild()->isValidNode(m_forest)); + OGDF_ASSERT(m_rootNode->getOnlyChild() == m_leaves.front()); + OGDF_ASSERT(m_rootNode->getOnlyChild()->isLeaf()); + } + return true; + } else if (m_leaves.size() == 2) { + OGDF_ASSERT(m_pNodeCount + m_cNodeCount == 1); + if (m_rootNode->isLeaf()) { + OGDF_ASSERT(!m_rootNode->getOnlyChild()->isLeaf()); + if (m_rootNode == m_leaves.front()) { + OGDF_ASSERT(m_rootNode->getOnlyChild()->getOnlyChild() == m_leaves.back()); + } else { + OGDF_ASSERT(m_rootNode == m_leaves.back()); + OGDF_ASSERT(m_rootNode->getOnlyChild()->getOnlyChild() == m_leaves.front()); + } + } else { + if (m_rootNode->getChild1() == m_leaves.front()) { + OGDF_ASSERT(m_rootNode->getChild2() == m_leaves.back()); + } else { + OGDF_ASSERT(m_rootNode->getChild2() == m_leaves.front()); + OGDF_ASSERT(m_rootNode->getChild1() == m_leaves.back()); + } + } + return true; + } + OGDF_ASSERT(m_leaves.size() > 2); + + // bottom-up + std::queue todo; + for (PCNode* leaf : m_leaves) { + if (leaf != m_rootNode) { + todo.push(leaf); + } + } + if (m_rootNode->isLeaf()) { + OGDF_ASSERT(todo.size() == m_leaves.size() - 1); + } else { + OGDF_ASSERT(todo.size() == m_leaves.size()); + } + PCNode* lastLeaf = todo.back(); + bool leaves_done = false, root_found = false; + size_t leaves_found = 0, p_nodes_found = 0, c_nodes_found = 0; + std::vector id_seen(m_forest->m_nextNodeId, nullptr); + while (!todo.empty()) { + PCNode* node = todo.front(); + todo.pop(); + + if (id_seen.at(node->m_id) == node) { + OGDF_ASSERT(leaves_done); + continue; + } + OGDF_ASSERT(node->isValidNode(m_forest)); + OGDF_ASSERT(id_seen[node->m_id] == nullptr); + id_seen[node->m_id] = node; + PCNode* parent = node->getParent(); + OGDF_ASSERT((node == m_rootNode) == (parent == nullptr)); + if (node == m_rootNode) { + OGDF_ASSERT(leaves_done); + root_found = true; + } else { + OGDF_ASSERT(leaves_done == (node->m_nodeType != PCNodeType::Leaf)); + todo.push(parent); + } + + if (node->m_nodeType == PCNodeType::PNode) { + p_nodes_found++; + } else if (node->m_nodeType == PCNodeType::CNode) { + c_nodes_found++; + } else { + OGDF_ASSERT(node->isLeaf()); + leaves_found++; + if (node == m_rootNode) { + OGDF_ASSERT(node->getChildCount() == 1); + } else { + OGDF_ASSERT(node->getChildCount() == 0); + } + } + + if (node == lastLeaf) { + leaves_done = true; + } + } + OGDF_ASSERT(leaves_done); + OGDF_ASSERT(root_found); + OGDF_ASSERT(leaves_found == m_leaves.size()); + OGDF_ASSERT(p_nodes_found == m_pNodeCount); + OGDF_ASSERT(c_nodes_found == m_cNodeCount); + + // top-down + leaves_found = p_nodes_found = c_nodes_found = 0; + OGDF_ASSERT(todo.empty()); + todo.push(m_rootNode); + while (!todo.empty()) { + PCNode* node = todo.front(); + todo.pop(); + OGDF_ASSERT(id_seen[node->m_id] == node); + + if (node->m_nodeType == PCNodeType::PNode) { + p_nodes_found++; + } else if (node->m_nodeType == PCNodeType::CNode) { + c_nodes_found++; + } else { + OGDF_ASSERT(node->isLeaf()); + leaves_found++; + } + + OGDF_ASSERT(node->getDegree() >= 1); + if (node->m_nodeType != PCNodeType::Leaf && !allow_small_deg) { + OGDF_ASSERT(node->getDegree() >= 3); + } + + // also check that all my children know me and that my degree is right + PCNode* pred = nullptr; + PCNode* curr = node->m_child1; + size_t children = 0; + while (curr != nullptr) { + todo.push(curr); + OGDF_ASSERT(curr->getParent() == node); + if (node->getNodeType() == PCNodeType::CNode) { + OGDF_ASSERT(curr->m_parentPNode == nullptr); + OGDF_ASSERT(curr->m_parentCNodeId + == node->m_nodeListIndex); // getParent() updates parentCNodeId + } else { + OGDF_ASSERT(curr->m_parentPNode == node); + OGDF_ASSERT(curr->m_parentCNodeId == UNIONFINDINDEX_EMPTY); + } + children++; + proceedToNextSibling(pred, curr); + } + OGDF_ASSERT(children == node->m_childCount); + OGDF_ASSERT(pred == node->m_child2); + } + OGDF_ASSERT(leaves_found == m_leaves.size()); + OGDF_ASSERT(p_nodes_found == m_pNodeCount); + OGDF_ASSERT(c_nodes_found == m_cNodeCount); + + OGDF_ASSERT(m_forest->m_cNodes.size() >= m_cNodeCount); +#endif + return true; +} + +void PCTree::getRestrictions(std::vector>& restrictions, + PCNode* fixedLeaf) const { + PCTreeNodeArray readyChildren(*this, 0); + PCTreeNodeArray> subtreeLeaves(*this); + std::queue todo; + for (PCNode* leaf : m_leaves) { + if (leaf == fixedLeaf) { + continue; + } + subtreeLeaves[leaf].push_back(leaf); + PCNode* next = leaf == m_rootNode ? leaf->m_child1 : leaf->getParent(); + if ((readyChildren[next] += 1) == next->getDegree() - 1) { + todo.push(next); + } + } +#ifdef OGDF_DEBUG + PCNode* central = nullptr; +#endif + while (!todo.empty()) { + PCNode* node = todo.front(); + todo.pop(); + OGDF_ASSERT(node != fixedLeaf); + + PCNode* next = nullptr; + PCNode* parent = node->getParent(); + if (parent != nullptr && subtreeLeaves[parent].empty() + && !(parent == m_rootNode && parent->isLeaf())) { + next = parent; + } +#ifndef OGDF_DEBUG + else +#endif + { + for (PCNode* neigh : node->neighbors()) { + if (subtreeLeaves[neigh].empty()) { + OGDF_ASSERT(next == nullptr || (next == parent && neigh == parent)); + next = neigh; +#ifndef OGDF_DEBUG + break; +#endif + } + } + } +#ifdef OGDF_DEBUG + if (next == nullptr) { + OGDF_ASSERT(fixedLeaf == nullptr); + OGDF_ASSERT(central == nullptr); + OGDF_ASSERT(todo.empty()); + central = node; + } +#endif + + PCNode* pred = nullptr; + for (PCNode* curr : node->neighbors(next)) { + if (curr == next) { + continue; + } + OGDF_ASSERT(!subtreeLeaves[curr].empty()); + if (node->m_nodeType == PCNodeType::CNode && pred != nullptr) { + unsigned long size = subtreeLeaves[pred].size() + subtreeLeaves[curr].size(); + if (!isTrivialRestriction(size)) { + std::vector& back = restrictions.emplace_back(); + back.reserve(size); + back.insert(back.end(), subtreeLeaves[pred].begin(), subtreeLeaves[pred].end()); + back.insert(back.end(), subtreeLeaves[curr].begin(), subtreeLeaves[curr].end()); + } + } + if (pred != nullptr) { + subtreeLeaves[node].splice(subtreeLeaves[node].end(), subtreeLeaves[pred]); + } + pred = curr; + } + if (pred != next) { + subtreeLeaves[node].splice(subtreeLeaves[node].end(), subtreeLeaves[pred]); + } + + if (node->m_nodeType == PCNodeType::PNode + && !isTrivialRestriction(subtreeLeaves[node].size())) { + restrictions.emplace_back(subtreeLeaves[node].begin(), subtreeLeaves[node].end()); + } + + if (next != nullptr && (readyChildren[next] += 1) == next->getDegree() - 1 + && next != fixedLeaf) { + todo.push(next); + } + } +#ifdef OGDF_DEBUG + if (fixedLeaf != nullptr) { + OGDF_ASSERT(central == nullptr); + central = fixedLeaf == m_rootNode ? fixedLeaf->m_child1 : fixedLeaf->getParent(); + OGDF_ASSERT(readyChildren[central] == central->getDegree() - 1); + OGDF_ASSERT(subtreeLeaves[central].size() == getLeafCount() - 1); + } else { + OGDF_ASSERT(central != nullptr); + OGDF_ASSERT(readyChildren[central] == central->getDegree()); + OGDF_ASSERT(subtreeLeaves[central].size() == getLeafCount()); + } +#endif +} + +PCNode* PCTree::setRoot(PCNode* newRoot) { + OGDF_ASSERT(newRoot != nullptr && newRoot->isValidNode(m_forest)); + OGDF_ASSERT(newRoot->isDetached()); + PCNode* oldRoot = m_rootNode; + m_rootNode = newRoot; + OGDF_ASSERT(checkValid()); + return oldRoot; +} + +PCNode* PCTree::changeRoot(PCNode* newRoot) { + OGDF_ASSERT(newRoot != nullptr && newRoot->isValidNode(m_forest)); + OGDF_HEAVY_ASSERT(checkValid()); + std::stack path; + for (PCNode* node = newRoot; node != nullptr; node = node->getParent()) { + OGDF_ASSERT(node != nullptr); + OGDF_ASSERT(node->m_forest == m_forest); + path.push(node); + } + while (path.size() > 1) { + PCNode* old_parent = path.top(); + path.pop(); + PCNode* new_parent = path.top(); + + if (!old_parent->isLeaf()) { + PCNode* sib1 = old_parent->getNextNeighbor(nullptr, new_parent); + PCNode* sib2 = old_parent->getNextNeighbor(sib1, new_parent); + new_parent->detach(); + old_parent->m_child1->replaceSibling(nullptr, old_parent->m_child2); + old_parent->m_child2->replaceSibling(nullptr, old_parent->m_child1); + old_parent->m_child1 = sib1; + old_parent->m_child2 = sib2; + sib1->replaceSibling(sib2, nullptr); + sib2->replaceSibling(sib1, nullptr); + } else { + new_parent->detach(); + } + new_parent->appendChild(old_parent); + } + OGDF_ASSERT(path.size() == 1); + OGDF_ASSERT(path.top() == newRoot); + return setRoot(newRoot); +} + +} diff --git a/src/pctree/PCTree_construction.cpp b/src/pctree/PCTree_construction.cpp new file mode 100644 index 0000000..2b1c5a0 --- /dev/null +++ b/src/pctree/PCTree_construction.cpp @@ -0,0 +1,488 @@ +/** \file + * \brief Implementation for pc_tree::PCTree construction methods + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCNode.h" +#include "PCTree.h" + +#include +#include + +using namespace pc_tree; + +PCTree::PCTree(int leafNum, std::vector* added, PCTreeForest* forest) : PCTree(forest) { + OGDF_ASSERT(leafNum > 2); + m_rootNode = newNode(PCNodeType::PNode); + insertLeaves(leafNum, m_rootNode, added); +} + +PCTree::PCTree(const std::string& str, bool keep_ids, PCTreeForest* forest) : PCTree(forest) { + std::string s = std::regex_replace(str, std::regex("\\s+"), ""); //remove whitespaces + + std::stringstream ss(s); + std::stack stack; + int nextIndex = 0; + bool indexUsed = true; + char previousChar = ' '; + + while (!ss.eof()) { + char nextChar = ss.peek(); + + if (isdigit(nextChar) || nextChar == '-') { + ss >> nextIndex; + if (keep_ids) { + m_forest->m_nextNodeId = std::max(nextIndex + 1, m_forest->m_nextNodeId); + } else { + nextIndex = m_forest->m_nextNodeId++; + } + indexUsed = false; + } else { + ss.ignore(); + + PCNode* parent = stack.empty() ? nullptr : stack.top(); + PCNode* created = nullptr; + + switch (nextChar) { + case ',': + if (previousChar != ']' && previousChar != ')') { + if (stack.empty()) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + created = newNode(PCNodeType::Leaf, parent, nextIndex); + } + break; + case '{': + if (stack.empty() && getLeafCount() > 0) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + OGDF_ASSERT(parent == nullptr); + created = newNode(PCNodeType::Leaf, parent, nextIndex); + stack.push(created); + break; + case '[': + if (stack.empty() && getLeafCount() > 0) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + created = newNode(PCNodeType::CNode, parent, nextIndex); + stack.push(created); + break; + case '(': + if (stack.empty() && getLeafCount() > 0) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + created = newNode(PCNodeType::PNode, parent, nextIndex); + stack.push(created); + break; + case ']': + if (stack.empty() || stack.top()->m_nodeType != PCNodeType::CNode) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + if (previousChar != ']' && previousChar != ')') { + created = newNode(PCNodeType::Leaf, parent, nextIndex); + } + stack.pop(); + break; + case ')': + if (stack.empty() || stack.top()->m_nodeType != PCNodeType::PNode) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + if (previousChar != ']' && previousChar != ')') { + created = newNode(PCNodeType::Leaf, parent, nextIndex); + } + stack.pop(); + break; + case '}': + if (stack.empty() || stack.top()->m_nodeType != PCNodeType::Leaf) { + throw std::invalid_argument("Invalid PC-Tree"); + } + + if (previousChar != ']' && previousChar != ')') { + created = newNode(PCNodeType::Leaf, parent, nextIndex); + } + stack.pop(); + OGDF_ASSERT(stack.empty()); + break; + default: + break; + } + + if (created) { + if (indexUsed) { + throw std::invalid_argument("Invalid PC-Tree"); + } + indexUsed = true; + } + + previousChar = nextChar; + } + } + if (!stack.empty()) { + throw std::invalid_argument("Invalid PC-Tree"); + } +} + +PCTree::PCTree(const PCTree& other, PCTreeNodeArray& nodeMapping, bool keep_ids, + PCTreeForest* forest) + : PCTree(forest) { + nodeMapping.init(other); + for (PCNode* other_node : other.allNodes()) { + PCNode* parent = other_node->getParent(); + int id = -1; + if (keep_ids) { + id = other_node->m_id; + m_forest->m_nextNodeId = std::max(id + 1, m_forest->m_nextNodeId); + } + OGDF_ASSERT((parent == nullptr) == (other.m_rootNode == other_node)); + if (parent == nullptr) { + m_rootNode = nodeMapping[other_node] = newNode(other_node->getNodeType(), nullptr, id); + } else { + nodeMapping[other_node] = newNode(other_node->getNodeType(), nodeMapping[parent], id); + } + } + OGDF_ASSERT(nodeMapping[other.m_rootNode] == m_rootNode); + OGDF_ASSERT(other.getLeafCount() == getLeafCount()); + OGDF_ASSERT(other.getPNodeCount() == getPNodeCount()); + OGDF_ASSERT(other.getCNodeCount() == getCNodeCount()); +} + +PCTree::~PCTree() { + OGDF_ASSERT(checkValid()); + // get rid of any degree <= 2 root, including a root leaf and possible degree 2 descendants + while (m_rootNode != nullptr && m_rootNode->m_childCount <= 2) { + PCNode* old_root = m_rootNode; + if (m_rootNode->m_childCount == 2) { + m_rootNode = old_root->getChild1(); + m_rootNode->detach(); + PCNode* child = old_root->getChild2(); + child->detach(); + m_rootNode->appendChild(child); + } else if (m_rootNode->m_childCount == 1) { + m_rootNode = old_root->getOnlyChild(); + m_rootNode->detach(); + } else { + m_rootNode = nullptr; + } + destroyNode(old_root); + } + OGDF_ASSERT(checkValid()); + + while (!m_leaves.empty()) { + PCNode* node = m_leaves.back(); + PCNode* parent = node->getParent(); + PCNodeType type = node->getNodeType(); + bool is_root = node == m_rootNode; + OGDF_ASSERT((parent == nullptr) == is_root); + if (is_root) { + OGDF_ASSERT(m_leaves.size() == 1); + m_rootNode = nullptr; + } + node->detach(); + destroyNode(node); + if (type != PCNodeType::Leaf) { + m_leaves.pop_back(); // destroyNode(leaf) automatically removes it from the list + } + if (parent != nullptr && parent->m_childCount == 0) { + m_leaves.push_back(parent); + } + if (is_root) { + OGDF_ASSERT(m_leaves.empty()); + OGDF_ASSERT(m_rootNode == nullptr); + } else { + OGDF_ASSERT(!m_leaves.empty()); + OGDF_ASSERT(m_rootNode != nullptr); + } + } + + if (!m_externalForest) { + delete m_forest; + } + + OGDF_ASSERT(m_rootNode == nullptr); + OGDF_ASSERT(m_pNodeCount == 0); + OGDF_ASSERT(m_cNodeCount == 0); +} + +void PCTree::registerNode(PCNode* node) { + if (node->m_nodeType == PCNodeType::Leaf) { + m_leaves.push_back(node); + } else if (node->m_nodeType == PCNodeType::PNode) { + m_pNodeCount++; + } else { + OGDF_ASSERT(node->m_nodeType == PCNodeType::CNode); + node->m_nodeListIndex = m_forest->m_parents.makeSet(); + OGDF_ASSERT(m_forest->m_cNodes.size() == node->m_nodeListIndex); + m_forest->m_cNodes.push_back(node); + m_cNodeCount++; + } +} + +void PCTree::unregisterNode(PCNode* node) { + if (node->m_nodeType == PCNodeType::Leaf) { + m_leaves.erase(node); + } else if (node->m_nodeType == PCNodeType::PNode) { + m_pNodeCount--; + } else { + OGDF_ASSERT(node->m_nodeType == PCNodeType::CNode); + OGDF_ASSERT(m_forest->m_cNodes.at(node->m_nodeListIndex) == node); + m_forest->m_cNodes[node->m_nodeListIndex] = nullptr; + m_cNodeCount--; + node->m_nodeListIndex = UNIONFINDINDEX_EMPTY; + } +} + +PCNode* PCTree::newNode(PCNodeType type, PCNode* parent, int id) { + PCNode* node; +#ifdef OGDF_PCTREE_REUSE_NODES + if (m_forest->m_reusableNodes) { + node = m_forest->m_reusableNodes; + m_forest->m_reusableNodes = m_forest->m_reusableNodes->m_parentPNode; + node->m_parentPNode = nullptr; + node->m_timestamp = 0; + if (id >= 0) { + node->m_id = id; + m_forest->m_nextNodeId = std::max(m_forest->m_nextNodeId, id + 1); + } // else we can't re-use the old ID after clear was called + else { + node->m_id = m_forest->m_nextNodeId++; + } + node->changeType(type); + } else +#endif + { + if (id < 0) { + id = m_forest->m_nextNodeId++; + } else { + m_forest->m_nextNodeId = std::max(m_forest->m_nextNodeId, id + 1); + } + node = new PCNode(m_forest, id, type); + } + registerNode(node); + if (parent != nullptr) { + parent->appendChild(node); + } else if (m_rootNode == nullptr) { + m_rootNode = node; + } + m_forest->m_nodeArrayRegistry.keyAdded(node); + + for (auto obs : m_observers) { + obs->onNodeCreate(node); + } + + return node; +} + +void PCTree::destroyNode(PCNode* const& node) { + OGDF_ASSERT(node->m_forest == m_forest); + OGDF_ASSERT(node->isDetached()); + OGDF_ASSERT(node->m_childCount == 0); + OGDF_ASSERT(node->m_child1 == nullptr); + OGDF_ASSERT(node->m_child2 == nullptr); + OGDF_ASSERT(node != m_rootNode); + unregisterNode(node); +#ifdef OGDF_PCTREE_REUSE_NODES + node->m_parentPNode = m_forest->m_reusableNodes; + m_forest->m_reusableNodes = node; +#else + delete node; +#endif +} + +PCNodeType PCTree::changeNodeType(PCNode* node, PCNodeType newType) { + PCNodeType oldType = node->m_nodeType; + if (oldType == newType) { + return oldType; + } + +#ifdef OGDF_DEBUG + UnionFindIndex oldIndex = node->m_nodeListIndex; +#endif + unregisterNode(node); + node->changeType(newType); + registerNode(node); + + if (oldType == PCNodeType::CNode || newType == PCNodeType::CNode) { + PCNode* pred = nullptr; + PCNode* curr = node->m_child1; +#ifdef OGDF_DEBUG + size_t children = 0; +#endif + while (curr != nullptr) { + if (oldType == PCNodeType::CNode) { + OGDF_ASSERT(curr->m_parentPNode == nullptr); + OGDF_ASSERT((size_t)m_forest->m_parents.find(curr->m_parentCNodeId) == oldIndex); + } else { + OGDF_ASSERT(curr->m_parentPNode == node); + OGDF_ASSERT(curr->m_parentCNodeId == UNIONFINDINDEX_EMPTY); + } + if (newType == PCNodeType::CNode) { + curr->m_parentPNode = nullptr; + curr->m_parentCNodeId = node->m_nodeListIndex; + } else { + curr->m_parentPNode = node; + curr->m_parentCNodeId = UNIONFINDINDEX_EMPTY; + } +#ifdef OGDF_DEBUG + children++; +#endif + proceedToNextSibling(pred, curr); + } + OGDF_ASSERT(children == node->m_childCount); + OGDF_ASSERT(pred == node->m_child2); + } + + return oldType; +} + +void PCTree::insertLeaves(int count, PCNode* parent, std::vector* added) { + OGDF_ASSERT(parent != nullptr); + OGDF_ASSERT(parent->m_forest == m_forest); + if (added) { + added->reserve(added->size() + count); + } + for (int i = 0; i < count; i++) { + PCNode* leaf = newNode(PCNodeType::Leaf); + parent->appendChild(leaf); + if (added) { + added->push_back(leaf); + } + } +} + +void PCTree::replaceLeaf(int leafCount, PCNode* leaf, std::vector* added) { + OGDF_ASSERT(leaf && leaf->m_forest == m_forest); + OGDF_ASSERT(leaf->isLeaf()); + OGDF_ASSERT(leafCount > 1); + if (getLeafCount() <= 2) { + changeNodeType(leaf->getParent(), PCNodeType::PNode); + insertLeaves(leafCount, leaf->getParent(), added); + leaf->detach(); + destroyNode(leaf); + } else { + changeNodeType(leaf, PCNodeType::PNode); + insertLeaves(leafCount, leaf, added); + } +} + +void PCTree::destroyLeaf(PCNode* leaf) { + OGDF_ASSERT(leaf->getNodeType() == PCNodeType::Leaf); + OGDF_ASSERT(leaf != m_rootNode); + + PCNode* parent = leaf->getParent(); + leaf->detach(); + destroyNode(leaf); + + /* assume the PC-tree is valid, so a childCount of 0 is impossible, since every inner node must + * have at least 2 children (except for child of root node) */ + + if (parent->m_childCount == 1) { + if (m_rootNode->getNodeType() == PCNodeType::Leaf) { + if (parent->getChild1()->getNodeType() != PCNodeType::Leaf + || m_rootNode->getChild1() != parent) { + PCNode* child = parent->getChild1(); + child->detach(); + parent->replaceWith(child); + destroyNode(parent); + } + } else { + PCNode* child = parent->getChild1(); + if (parent != m_rootNode) { + child->detach(); + parent->replaceWith(child); + destroyNode(parent); + } else if (child->getNodeType() != PCNodeType::Leaf) { + PCNode* root = m_rootNode; + root->detach(); + root->m_childCount = 0; + root->m_child1 = root->m_child2 = nullptr; + child->m_parentCNodeId = UNIONFINDINDEX_EMPTY; + child->m_parentPNode = nullptr; + setRoot(child); + destroyNode(root); + } + } + } +} + +void PCTree::insertTree(PCNode* at, PCTree* inserted) { + OGDF_HEAVY_ASSERT(checkValid()); + OGDF_HEAVY_ASSERT(inserted->checkValid()); + OGDF_ASSERT(at->isValidNode(getForest())); + OGDF_ASSERT(inserted->getForest() == getForest()); + + m_observers.splice(m_observers.end(), inserted->m_observers); + m_leaves.splice(m_leaves.end(), inserted->m_leaves); + m_pNodeCount += inserted->m_pNodeCount; + m_cNodeCount += inserted->m_cNodeCount; + + PCNode* root = inserted->m_rootNode; + inserted->m_rootNode = nullptr; + inserted->m_pNodeCount = inserted->m_cNodeCount = 0; + delete inserted; + inserted = nullptr; + + while (root->getChildCount() == 1) { + PCNode* new_root = root->getOnlyChild(); + new_root->detach(); + destroyNode(root); + root = new_root; + } + + if (at->isLeaf()) { + OGDF_ASSERT(!at->isDetached()); + PCNode* parent = at->getParent(); + if (!root->isLeaf() && parent->getDegree() == 2) { + // remove degree 2 node in a tree with two leaves if it got further leaves + at->detach(); + if (parent->isDetached()) { + // use the inserted root as root + m_rootNode = root; + PCNode* child = parent->getOnlyChild(); + child->detach(); + m_rootNode->appendChild(child); + } else { + // we can use the other leaf as root + parent->replaceWith(root); + } + destroyNode(parent); + } else { + at->replaceWith(root); + } + destroyNode(at); + } else { + at->appendChild(root); + } + OGDF_ASSERT(checkValid()); +} diff --git a/src/pctree/PCTree_intersect.cpp b/src/pctree/PCTree_intersect.cpp new file mode 100644 index 0000000..f1d51ee --- /dev/null +++ b/src/pctree/PCTree_intersect.cpp @@ -0,0 +1,273 @@ +/** \file + * \brief Implementation for pc_tree::PCTree intersection method + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCTree.h" + +#include + +using namespace pc_tree; + +bool PCTree::intersect(PCTree& other, PCTreeNodeArray& mapping) { + OGDF_HEAVY_ASSERT(checkValid() && other.checkValid()); + OGDF_ASSERT(m_leaves.size() == other.m_leaves.size()); + OGDF_ASSERT(mapping.registeredAt() == &other.m_forest->m_nodeArrayRegistry); + if (other.isTrivial()) { + return true; + } + +#ifdef OGDF_DEBUG + size_t oldLeaves = m_leaves.size(); +#endif + PCTreeNodeArray> blockNodes(*this); + PCTreeNodeArray> subtreeNodes(*this); + PCTreeNodeArray leafPartner(*this, nullptr); + PCTreeNodeArray isFront(*this, false); + bool possible = other.findNodeRestrictions(*this, mapping, blockNodes, subtreeNodes, + leafPartner, isFront); + restoreSubtrees(blockNodes, subtreeNodes, leafPartner, isFront); + OGDF_ASSERT(oldLeaves == m_leaves.size()); + + return possible; +} + +bool PCTree::findNodeRestrictions(PCTree& applyTo, PCTreeNodeArray& mapping, + PCTreeNodeArray>& blockNodes, + PCTreeNodeArray>& subtreeNodes, PCTreeNodeArray& leafPartner, + PCTreeNodeArray& isFront) { + std::vector fullNodeOrder; + resetTempData(); + markFull(m_leaves.begin(), IntrusiveList::iterator(m_leaves.back()), &fullNodeOrder); + changeRoot(m_leaves.back() == m_rootNode ? m_leaves.back()->m_child1 + : m_leaves.back()->getParent()); + applyTo.changeRoot(mapping[m_leaves.back()] == applyTo.m_rootNode + ? mapping[m_leaves.back()]->m_child1 + : mapping[m_leaves.back()]->getParent()); + + for (PCNode* node : fullNodeOrder) { + auto nonLeafNeighborIt = std::find_if(node->neighbors().begin(), node->neighbors().end(), + [](PCNode* n) { return !n->isLeaf(); }); + + PCNode* startWith = nonLeafNeighborIt == node->neighbors().end() ? node->neighbors().m_first + : *nonLeafNeighborIt; + + std::vector consecutiveOriginal; + std::vector consecutiveOther; + auto neighbors = node->neighbors(startWith); + for (auto it = std::next(neighbors.begin()); it != neighbors.end(); ++it) { + PCNode* current = *it; + + OGDF_ASSERT(current->isLeaf()); + consecutiveOther.push_back(current); + consecutiveOriginal.push_back(mapping[current]); + if (leafPartner[mapping[current]] != nullptr) { + consecutiveOriginal.push_back(leafPartner[mapping[current]]); + } + + if (node->getNodeType() == PCNodeType::CNode && consecutiveOther.size() >= 2) { + std::vector pair; + PCNode* n1 = consecutiveOther[consecutiveOther.size() - 2]; + PCNode* n2 = consecutiveOther.back(); + pair.push_back(mapping[n1]); + if (leafPartner[mapping[n1]] != nullptr) { + pair.push_back(leafPartner[mapping[n1]]); + } + pair.push_back(mapping[n2]); + if (leafPartner[mapping[n2]] != nullptr) { + pair.push_back(leafPartner[mapping[n2]]); + } + if (!applyTo.makeConsecutive(pair)) { + return false; + } + } + } + + if (node != fullNodeOrder.back()) { + PCNode* merged = mergeLeaves(consecutiveOther); + if (!applyTo.makeConsecutive(consecutiveOriginal)) { + return false; + } + std::vector nodeOrder; + applyTo.resetTempData(); + applyTo.markFull(consecutiveOriginal.begin(), consecutiveOriginal.end(), &nodeOrder); + PCNode* partialNode = applyTo.m_firstPartial; + OGDF_ASSERT(partialNode); + OGDF_ASSERT(partialNode == applyTo.m_lastPartial); + auto& fullNeighbors = partialNode->tempInfo().fullNeighbors; + PCNode* ebEnd1 = nullptr; + auto fbEnd1It = std::find_if(fullNeighbors.begin(), fullNeighbors.end(), [&](PCNode* n) { + PCNode* sib1 = partialNode->getNextNeighbor(nullptr, n); + if (!sib1->isFull()) { + ebEnd1 = sib1; + return true; + } + PCNode* sib2 = partialNode->getNextNeighbor(sib1, n); + if (!sib2->isFull()) { + ebEnd1 = sib2; + return true; + } + return false; + }); + OGDF_ASSERT(ebEnd1 != nullptr); + OGDF_ASSERT(fbEnd1It != fullNeighbors.end()); + PCNode* fbEnd1 = *fbEnd1It; + PCNode *fbEnd2, *ebEnd2; + size_t count = applyTo.findEndOfFullBlock(partialNode, ebEnd1, fbEnd1, fbEnd2, ebEnd2); + OGDF_ASSERT(partialNode->areNeighborsAdjacent(fbEnd1, ebEnd1)); + OGDF_ASSERT(partialNode->areNeighborsAdjacent(fbEnd2, ebEnd2)); + PCNode* newLeaf = applyTo.newNode(PCNodeType::Leaf); + PCNode* newLeaf2 = nullptr; + if (count >= 2) { + /* + * If the block connecting the subtree to the partial node has length >= 2, it is important that the + * orientation of the block remains the same when reinserted in restoreSubtrees(). Because a single leaf + * could be rotated during makeConsecutive() calls, we insert two leaves instead to ensure the + * orientation remains consistent. + */ + OGDF_ASSERT(partialNode->getNodeType() == PCNodeType::CNode); + newLeaf2 = applyTo.newNode(PCNodeType::Leaf); + leafPartner[newLeaf] = newLeaf2; + isFront[newLeaf] = true; + leafPartner[newLeaf2] = newLeaf; + } + + subtreeNodes[newLeaf].assign(nodeOrder.begin(), nodeOrder.end()); + subtreeNodes[newLeaf].insert(subtreeNodes[newLeaf].end(), consecutiveOriginal.begin(), + consecutiveOriginal.end()); + + PCNode* current = fbEnd1; + while (current != ebEnd2) { + OGDF_ASSERT(current->isFull()); + + blockNodes[newLeaf].push_back(current); + PCNode* next = partialNode->getNextNeighbor(ebEnd1, current); + current->detach(); + current = next; + } + OGDF_ASSERT(blockNodes[newLeaf].size() == count); + + newLeaf->insertBetween(ebEnd1, ebEnd2); + if (newLeaf2 != nullptr) { + newLeaf2->insertBetween(newLeaf, ebEnd2); + subtreeNodes[newLeaf2] = subtreeNodes[newLeaf]; + blockNodes[newLeaf2] = blockNodes[newLeaf]; + } + + for (PCNode* n : subtreeNodes[newLeaf]) { + int oldId = n->m_nodeListIndex; + applyTo.unregisterNode(n); + if (n->getNodeType() == PCNodeType::CNode) { + n->m_nodeListIndex = oldId; + } + } + + mapping[merged] = newLeaf; + OGDF_HEAVY_ASSERT(checkValid()); + } + } + return true; +} + +void PCTree::restoreSubtrees(PCTreeNodeArray>& blockNodes, + PCTreeNodeArray>& subtreeNodes, PCTreeNodeArray& leafPartner, + PCTreeNodeArray& isFront) { + PCTreeNodeArray visited(*this, false); + std::queue queue; + queue.push(m_leaves.front()->getParent()); + + while (!queue.empty()) { + PCNode* node = queue.front(); + queue.pop(); + + PCNode* previous = node->neighbors().m_first; + PCNode* current = node->getNextNeighbor(nullptr, previous); + if (leafPartner[current] != nullptr && leafPartner[current] == previous) { + previous = node->getNextNeighbor(previous, current); + } + int degree = node->neighbors().count(); + for (int i = 0; i < degree; i++) { + OGDF_ASSERT(current->isValidNode(m_forest)); + + if (!blockNodes[current].empty()) { + // Replace the merged leaf (or two leaves) with the subtree it represents. + visited[current] = true; + PCNode* neighbor1 = previous; + OGDF_ASSERT(node->areNeighborsAdjacent(current, previous)); + PCNode* neighbor2 = leafPartner[current] == nullptr + ? node->getNextNeighbor(previous, current) + : node->getNextNeighbor(current, leafPartner[current]); + OGDF_ASSERT(neighbor1 != neighbor2); + if (!isFront[current]) { + std::reverse(blockNodes[current].begin(), blockNodes[current].end()); + } + + for (PCNode* n : subtreeNodes[current]) { + if (n->getNodeType() == PCNodeType::CNode) { + m_cNodeCount++; + OGDF_ASSERT(getForest()->m_cNodes.at(n->m_nodeListIndex) == nullptr); + getForest()->m_cNodes[n->m_nodeListIndex] = n; + } else { + registerNode(n); + } + } + + current->detach(); + if (leafPartner[current] != nullptr) { + leafPartner[current]->detach(); + } + + for (PCNode* n : blockNodes[current]) { + n->insertBetween(neighbor1, neighbor2); + neighbor1 = n; + } + + degree += blockNodes[current].size(); + PCNode* tmp = current; + current = blockNodes[current].front(); + blockNodes[tmp].clear(); + if (leafPartner[tmp] != nullptr) { + destroyNode(leafPartner[tmp]); + } + destroyNode(tmp); + } else { + if (!current->isLeaf() && !visited[current]) { + queue.push(current); + } + visited[current] = true; + PCNode* tmp = current; + current = node->getNextNeighbor(previous, current); + previous = tmp; + OGDF_ASSERT(previous != current); + } + } + } + + OGDF_ASSERT(checkValid()); +} diff --git a/src/pctree/PCTree_restriction.cpp b/src/pctree/PCTree_restriction.cpp new file mode 100644 index 0000000..b5e2b44 --- /dev/null +++ b/src/pctree/PCTree_restriction.cpp @@ -0,0 +1,1128 @@ +/** \file + * \brief Implementation for pc_tree::PCTree update/restriction methods + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "PCNode.h" +#include "PCTree.h" + +#include +#include +#include +#include +#include + +#ifdef LIKWID_PERFMON + +# include + +# define PC_PROFILE_ENTER(level, msg) PC_PROFILE_ENTER_##level(msg) +# define PC_PROFILE_EXIT(level, msg) PC_PROFILE_EXIT_##level(msg) + +# ifdef PC_PROFILE_LEVEL_1 +# define PC_PROFILE_ENTER_1(msg) likwid_markerStartRegion(msg) +# define PC_PROFILE_EXIT_1(msg) likwid_markerStopRegion(msg) +# else +# define PC_PROFILE_ENTER_1(msg) +# define PC_PROFILE_EXIT_1(msg) +# endif + +# ifdef PC_PROFILE_LEVEL_2 +# define PC_PROFILE_ENTER_2(msg) likwid_markerStartRegion(msg) +# define PC_PROFILE_EXIT_2(msg) likwid_markerStopRegion(msg) +# else +# define PC_PROFILE_ENTER_2(msg) +# define PC_PROFILE_EXIT_2(msg) +# endif + +#else +# define PC_PROFILE_ENTER(level, msg) +# define PC_PROFILE_EXIT(level, msg) +#endif + +using namespace pc_tree; + +#ifdef UFPC_DEBUG +# define log \ + if (true) \ + std::cout + +void dump(PCTree& T, const std::string& name) { + // Graph G; + // GraphAttributes GA(G, GraphAttributes::all); + // PCTreeNodeArray pc_repr(T, nullptr); + // T.getTree(G, &GA, pc_repr, nullptr, true); + // CircularLayout cl; + // cl.call(GA); + // GraphIO::write(GA, name + "-cl.svg"); + // TreeLayout tl; + // G.reverseAllEdges(); + // tl.call(GA); + // GraphIO::write(GA, name + "-tl.svg"); +} + +#else +# define log \ + if (false) \ + std::cout + +void dump(PCTree& T, const std::string& name) { } + +#endif + + +namespace pc_tree { + +bool isTrivialRestriction(int restSize, int leafCount) { + return restSize <= 1 || restSize >= leafCount - 1; +} + +bool PCTree::isTrivialRestriction(int size) const { + return ::isTrivialRestriction(size, getLeafCount()); +} + +void PCTree::LoggingObserver::makeConsecutiveCalled(PCTree& tree, FullLeafIter consecutiveLeaves) { + log << "Tree " << tree << " with consecutive leaves ["; + auto it = consecutiveLeaves(); + for (auto l = it(); l != nullptr; l = it()) { + log << l->index() << ", "; + } + log << "]" << std::endl; + dump(tree, "before"); +} + +void PCTree::LoggingObserver::labelsAssigned(PCTree& tree, PCNode* firstPartial, + PCNode* lastPartial, int partialCount) { + log << "Found " << partialCount << " partial nodes: ["; +#ifdef OGDF_DEBUG + int found = 0; +#endif + for (PCNode* node = firstPartial; node != nullptr; node = node->tempInfo().nextPartial) { + log << node << ", "; +#ifdef OGDF_DEBUG + found++; +#endif + } + log << "]" << std::endl; + OGDF_ASSERT(partialCount == found); + dump(tree, "labelled"); +} + +void PCTree::LoggingObserver::terminalPathFound(PCTree& tree, PCNode* apex, PCNode* apexTPPred2, + int terminalPathLength) { + log << "Apex is " << apex->getLabel() << " " << apex << ", TP length is " << terminalPathLength + << ", first path is ["; + int length = 1; + for (PCNode* cur = apex->constTempInfo().tpPred; cur != nullptr; + cur = cur->constTempInfo().tpPred) { + log << cur << ", "; + length++; + } + log << "], second path is ["; + for (PCNode* cur = apexTPPred2; cur != nullptr; cur = cur->constTempInfo().tpPred) { + log << cur << ", "; + length++; + } + log << "] (effective length " << length << ")" << std::endl; + OGDF_ASSERT(terminalPathLength == length); +} + +void PCTree::LoggingObserver::centralCreated(PCTree& tree, PCNode* central) { + log << "Tree before merge into central " << central->index() << ": " << tree << std::endl; + dump(tree, "before-merge"); +} + +void PCTree::LoggingObserver::beforeMerge(PCTree& tree, int count, PCNode* tpNeigh) { + PCNode* central = tpNeigh->getParent(); + PCNode* fullNeigh = central->getFullNeighInsertionPoint(tpNeigh); + log << "Merging child #" << count << " " << tpNeigh->index() << " next to " + << fullNeigh->index() << " into parent " << central->index() << std::endl + << "\t" << tree << std::endl; + dump(tree, "merge-" + std::to_string(count) + "-" + std::to_string(tpNeigh->index())); +} + +void PCTree::LoggingObserver::makeConsecutiveDone(PCTree& tree, Stage stage, bool success) { + log << "Resulting tree after stage "; + switch (stage) { + case Stage::Trivial: + log << "Trivial"; + break; + case Stage::NoPartials: + log << "NoPartials"; + break; + case Stage::InvalidTP: + log << "InvalidTP"; + break; + case Stage::SingletonTP: + log << "SingletonTP"; + break; + case Stage::Done: + log << "Done"; + break; + default: + log << "???"; + break; + } + log << ", restriction " << (success ? "successful" : "invalid") << ": " << tree << std::endl; +} + +extern int PCTREE_DEBUG_CHECK_CNT; + +bool PCTree::makeFullNodesConsecutive() { + if (m_firstPartial == nullptr) { + OGDF_ASSERT(m_lastPartial == nullptr); + OGDF_ASSERT(m_partialCount == 0); + for (auto obs : m_observers) { + obs->makeConsecutiveDone(*this, Observer::Stage::NoPartials, true); + } + return true; + } + OGDF_ASSERT(m_lastPartial != nullptr); + OGDF_ASSERT(m_partialCount > 0); + for (auto obs : m_observers) { + obs->labelsAssigned(*this, m_firstPartial, m_lastPartial, m_partialCount); + } + + PC_PROFILE_ENTER(1, "find_tp"); + bool find_tp = findTerminalPath(); + PC_PROFILE_EXIT(1, "find_tp"); + if (!find_tp) { + for (auto obs : m_observers) { + obs->makeConsecutiveDone(*this, Observer::Stage::InvalidTP, false); + } + return false; + } + OGDF_ASSERT(m_apexCandidate != nullptr); + OGDF_ASSERT(m_apexCandidateIsFix == true); + for (auto obs : m_observers) { + obs->terminalPathFound(*this, m_apexCandidate, m_apexTPPred2, m_terminalPathLength); + } + + PC_PROFILE_ENTER(1, "update_tp"); + if (m_terminalPathLength == 1) { + OGDF_ASSERT(m_apexCandidate->tempInfo().tpPred == nullptr); + updateSingletonTerminalPath(); + PC_PROFILE_EXIT(1, "update_tp"); + for (auto obs : m_observers) { + obs->makeConsecutiveDone(*this, Observer::Stage::SingletonTP, true); + } + return true; + } + OGDF_ASSERT(m_apexCandidate->tempInfo().tpPred != nullptr); + PC_PROFILE_ENTER(2, "update_tp_central"); + PCNode* central = createCentralNode(); + PC_PROFILE_EXIT(2, "update_tp_central"); + for (auto obs : m_observers) { + obs->centralCreated(*this, central); + } + + PCNode::TempInfo& ctinfo = central->tempInfo(); +#ifdef OGDF_DEBUG + size_t merged = +#endif + updateTerminalPath(central, ctinfo.tpPred); + if (m_apexTPPred2 != nullptr) { +#ifdef OGDF_DEBUG + merged += +#endif + updateTerminalPath(central, m_apexTPPred2); + } + OGDF_ASSERT(merged == m_terminalPathLength - 1); + PC_PROFILE_EXIT(1, "update_tp"); + + for (auto obs : m_observers) { + obs->makeConsecutiveDone(*this, Observer::Stage::Done, true); + } +#ifdef OGDF_HEAVY_DEBUG + OGDF_HEAVY_ASSERT(checkValid()); + if (PCTREE_DEBUG_CHECK_FREQ != 0 && PCTREE_DEBUG_CHECK_CNT % PCTREE_DEBUG_CHECK_FREQ == 0) { + std::list order; + currentLeafOrder(order); + while (!order.front()->isFull()) { + order.push_back(order.front()); + order.pop_front(); + } + while (order.back()->isFull()) { + order.push_front(order.back()); + order.pop_back(); + } + OGDF_ASSERT(order.front()->isFull()); + OGDF_ASSERT(order.size() == m_leaves.size()); + while (order.front()->isFull()) { + order.pop_front(); + } + while (!order.empty()) { + OGDF_ASSERT(!order.front()->isFull()); + order.pop_front(); + } + } +#endif + return true; +} + +void PCTree::addPartialNode(PCNode* partial) { + OGDF_ASSERT(partial->tempInfo().predPartial == nullptr); + OGDF_ASSERT(partial->tempInfo().nextPartial == nullptr); + partial->tempInfo().predPartial = m_lastPartial; + if (m_firstPartial == nullptr) { + m_firstPartial = partial; + } else { + m_lastPartial->tempInfo().nextPartial = partial; + } + m_lastPartial = partial; + m_partialCount++; +} + +void PCTree::removePartialNode(PCNode* partial) { + PCNode::TempInfo& tinfo = partial->tempInfo(); + OGDF_ASSERT((tinfo.predPartial == nullptr) == (m_firstPartial == partial)); + if (tinfo.predPartial == nullptr) { + m_firstPartial = tinfo.nextPartial; + } else { + tinfo.predPartial->tempInfo().nextPartial = tinfo.nextPartial; + } + OGDF_ASSERT((tinfo.nextPartial == nullptr) == (m_lastPartial == partial)); + if (tinfo.nextPartial == nullptr) { + m_lastPartial = tinfo.predPartial; + } else { + tinfo.nextPartial->tempInfo().predPartial = tinfo.predPartial; + } + tinfo.predPartial = tinfo.nextPartial = nullptr; + m_partialCount--; +} + +PCNode* PCTree::markFull(PCNode* full_node, std::vector* fullNodeOrder) { + OGDF_HEAVY_ASSERT(full_node->isValidNode(m_forest)); + if (!full_node->isLeaf()) { + OGDF_ASSERT(full_node->tempInfo().fullNeighbors.size() == full_node->getDegree() - 1); + OGDF_ASSERT(full_node->getLabel() == NodeLabel::Partial); + removePartialNode(full_node); + } + full_node->setLabel(NodeLabel::Full); + + PCNode* partial_neigh = full_node->getParent(); + if (partial_neigh == nullptr || partial_neigh->isFull()) { + // if we are the root or our parent node got full before us, we need to find our one non-full neighbor + PC_PROFILE_ENTER(2, "label_process_neigh"); + PCNode* pred = nullptr; + partial_neigh = full_node->m_child1; + while (partial_neigh != nullptr && partial_neigh->isFull()) { + proceedToNextSibling(pred, partial_neigh); + } + PC_PROFILE_EXIT(2, "label_process_neigh"); + } + OGDF_ASSERT(partial_neigh != nullptr); + OGDF_ASSERT(!partial_neigh->isFull()); + if (partial_neigh->isLeaf()) { + // when a leaf becomes partial, all other leaves need to be full, which only happens during getRestriction + OGDF_ASSERT(fullNodeOrder != nullptr); + OGDF_ASSERT(fullNodeOrder->size() == getPNodeCount() + getCNodeCount()); + return nullptr; + } + + size_t fullNeighborCounter = partial_neigh->addFullNeighbor(full_node); + OGDF_ASSERT(fullNeighborCounter >= 1); + OGDF_ASSERT(fullNeighborCounter + 1 <= partial_neigh->getDegree()); + if (fullNeighborCounter == 1) { + OGDF_ASSERT(partial_neigh->getLabel() == NodeLabel::Unknown); + partial_neigh->setLabel(NodeLabel::Partial); + addPartialNode(partial_neigh); + } else { + OGDF_ASSERT(partial_neigh->getLabel() == NodeLabel::Partial); + } + if (fullNeighborCounter == partial_neigh->getDegree() - 1) { + if (fullNodeOrder != nullptr) { + fullNodeOrder->push_back(partial_neigh); + } + return partial_neigh; + } else { + return nullptr; + } +} + +bool PCTree::checkTPPartialCNode(PCNode* node) { + // check that C node's full neighbors are consecutive + PCNode::TempInfo& tinfo = node->tempInfo(); + if (tinfo.ebEnd1 == nullptr) { + PC_PROFILE_ENTER(2, "find_tp_cnode"); + PCNode* fullChild = tinfo.fullNeighbors.front(); + PCNode* sib1 = node->getNextNeighbor(nullptr, fullChild); + PCNode* sib2 = node->getNextNeighbor(sib1, fullChild); + size_t count = 1; + count += findEndOfFullBlock(node, fullChild, sib1, tinfo.fbEnd1, tinfo.ebEnd1); + count += findEndOfFullBlock(node, fullChild, sib2, tinfo.fbEnd2, tinfo.ebEnd2); + PC_PROFILE_EXIT(2, "find_tp_cnode"); + if (count != tinfo.fullNeighbors.size()) { + log << "C-node's full-block isn't consecutive, abort!" << std::endl; + return false; + } + } + OGDF_ASSERT(tinfo.ebEnd1 != nullptr); + OGDF_ASSERT(tinfo.ebEnd2 != nullptr); + OGDF_ASSERT(tinfo.fbEnd1 != nullptr); + OGDF_ASSERT(tinfo.fbEnd2 != nullptr); + if (tinfo.tpPred != nullptr) { + if (tinfo.tpPred != tinfo.ebEnd1 && tinfo.tpPred != tinfo.ebEnd2) { + log << "C-node's TP pred is not adjacent to its empty block, abort!" << std::endl; + return false; + } + } + if (node == m_apexCandidate && m_apexTPPred2 != nullptr) { + if (m_apexTPPred2 != tinfo.ebEnd1 && m_apexTPPred2 != tinfo.ebEnd2) { + log << "C-node's TP second pred is not adjacent to its empty block, abort!" << std::endl; + return false; + } + } + return true; +} + +bool PCTree::findTerminalPath() { + while (m_firstPartial != nullptr) { + OGDF_ASSERT(m_lastPartial != nullptr); + OGDF_ASSERT(m_partialCount > 0); + + PCNode* node = m_firstPartial; + removePartialNode(node); + PCNode* parent = node->getParent(); + PCNode::TempInfo& tinfo = node->tempInfo(); + OGDF_ASSERT(!node->isFull()); + log << "Processing " << node->getLabel() << " " << node; + if (parent != nullptr) { + log << " (" << parent->getLabel() << ")"; + } + log << ", current TP length is " << m_terminalPathLength << ": "; + + if (node->m_nodeType == PCNodeType::CNode && node->getLabelUnchecked() == NodeLabel::Partial + && !checkTPPartialCNode(node)) { + return false; + } + if (node->getLabelUnchecked() == NodeLabel::Partial) { + tinfo.tpPartialPred = node; + tinfo.tpPartialHeight = 0; + } + + OGDF_ASSERT((parent == nullptr) == (node == m_rootNode)); + if (tinfo.tpSucc != nullptr) { + // we never process a node twice, proceeding to the next entry if we detect this case + log << "dupe!" << std::endl; + continue; + } + + if (m_firstPartial == nullptr && m_apexCandidate == nullptr) { + // we can stop early if the queue size reached 1 right before we removed the current node, + // but we have not found an apex, marking the node the remaining arc points to as apex candidate + log << "early stop I-shaped!" << std::endl; +#ifdef OGDF_DEBUG + bool s = +#endif + setApexCandidate(node, false); + OGDF_ASSERT(s); + } else if (m_firstPartial == nullptr && m_apexCandidate != nullptr && m_apexTPPred2 != nullptr + && tinfo.tpPartialPred == m_apexCandidate->tempInfo().tpPartialPred) { + log << "early stop A-shaped!" << std::endl; + OGDF_ASSERT(m_apexCandidate->tempInfo().tpPartialHeight <= tinfo.tpPartialHeight); + OGDF_ASSERT(m_apexCandidateIsFix); +#ifdef OGDF_DEBUG + // setApexCandidate here should make no changes, but just re-do the checks from this branch and keep the old apex + PCNode* oldApex = m_apexCandidate; + bool s = setApexCandidate(node, false); + OGDF_ASSERT(s); + OGDF_ASSERT(m_apexCandidate == oldApex); +#endif + } else if (node == m_rootNode || parent->getLabel() == NodeLabel::Full) { + // we can't ascend from the root node or if our parent is full + log << "can't ascend from root / node with full parent!" << std::endl; + if (!setApexCandidate(node, false)) { + return false; + } + } else { + // check that we can ascend through a C-Node (P-Nodes always work) + if (node->m_nodeType == PCNodeType::CNode) { + if (node->getLabelUnchecked() == NodeLabel::Empty) { + if (!node->isChildOuter(tinfo.tpPred)) { + log << "can't ascend from empty C-Node where TP pred is not adjacent to parent!" + << std::endl; + if (!setApexCandidate(node, false)) { + return false; + } + continue; + } else { + tinfo.ebEnd1 = tinfo.fbEnd2 = tinfo.tpPred; + tinfo.ebEnd2 = tinfo.fbEnd1 = parent; + } + } else { + OGDF_ASSERT(node->getLabelUnchecked() == NodeLabel::Partial); + if (!node->isChildOuter(tinfo.fbEnd1) && !node->isChildOuter(tinfo.fbEnd2)) { + log << "can't ascend from partial C-Node where full block is not adjacent to parent!" + << std::endl; + if (!setApexCandidate(node, false)) { + return false; + } + continue; + } + } + } + + // ascend to parent + m_terminalPathLength++; + tinfo.tpSucc = parent; + PCNode::TempInfo& parent_tinfo = parent->tempInfo(); + if (parent_tinfo.tpPred == nullptr) { + // attach current path to parent + parent_tinfo.tpPred = node; + if (parent->getLabelUnchecked() != NodeLabel::Partial) { // parent might be empty or full + parent_tinfo.tpPartialPred = tinfo.tpPartialPred; + parent_tinfo.tpPartialHeight = tinfo.tpPartialHeight + 1; + OGDF_ASSERT(parent_tinfo.tpPartialPred != nullptr); + addPartialNode(parent); + log << "proceed to non-partial parent (whose partial height is " + << parent_tinfo.tpPartialHeight << ")" << std::endl; + } else { + OGDF_ASSERT(parent->getLabelUnchecked() == NodeLabel::Partial); + log << "partial parent is already queued" << std::endl; + } + } else if (parent_tinfo.tpPred != node) { + log << "parent is A-shaped apex!" << std::endl; + if (!setApexCandidate(parent, true)) { + return false; + } + if (m_apexTPPred2 != nullptr && m_apexTPPred2 != node) { + log << "Conflicting apexTPPred2!" << std::endl; + return false; + } + m_apexTPPred2 = node; + if (parent->m_nodeType == PCNodeType::CNode + && parent->getLabelUnchecked() == NodeLabel::Empty) { + if (!parent->areNeighborsAdjacent(parent_tinfo.tpPred, m_apexTPPred2)) { + log << "Apex is empty C-Node, but partial predecessors aren't adjacent!" + << std::endl; + return false; + } else { + parent_tinfo.ebEnd1 = parent_tinfo.fbEnd2 = parent_tinfo.tpPred; + parent_tinfo.ebEnd2 = parent_tinfo.fbEnd1 = m_apexTPPred2; + } + } + } + + // if the parent is a partial C-node, it might have been processed before we were added as its tpPred(2), so re-run the checks + if (parent->m_nodeType == PCNodeType::CNode + && parent->getLabelUnchecked() == NodeLabel::Partial) { + if (!checkTPPartialCNode(parent)) { + return false; + } + } + } + } + OGDF_ASSERT(m_lastPartial == nullptr); + OGDF_ASSERT(m_partialCount == 0); + OGDF_ASSERT(m_apexCandidate != nullptr); + if (!m_apexCandidateIsFix) { + if (m_apexCandidate->getLabel() != NodeLabel::Partial) { + log << "Backtracking from " << m_apexCandidate << " to " + << m_apexCandidate->tempInfo().tpPartialPred << ", TP length " + << m_terminalPathLength << "-" << m_apexCandidate->tempInfo().tpPartialHeight << "="; + m_terminalPathLength -= m_apexCandidate->tempInfo().tpPartialHeight; + m_apexCandidate = m_apexCandidate->tempInfo().tpPartialPred; + log << m_terminalPathLength << std::endl; + OGDF_ASSERT(m_apexCandidate->tempInfo().tpPartialHeight + <= m_terminalPathLength); // make sure we didn't ascent too far + } + m_apexCandidateIsFix = true; + } + if (m_apexCandidate->m_nodeType == PCNodeType::CNode + && m_apexCandidate->getLabel() == NodeLabel::Empty) { + OGDF_ASSERT(m_apexCandidate->tempInfo().tpPred != nullptr); + OGDF_ASSERT(m_apexTPPred2 != nullptr); + } + m_apexCandidate->tempInfo().tpSucc = nullptr; + return true; +} + +PCNode* PCTree::createCentralNode() { + PCNode::TempInfo& atinfo = m_apexCandidate->tempInfo(); + PCNode::TempInfo* ctinfo; + PCNode* parent = m_apexCandidate->getParent(); + PCNode* central; + PCNode* tpStubApex1 = atinfo.tpPred; + PCNode* tpStubApex2 = m_apexTPPred2; + OGDF_ASSERT(tpStubApex1 != nullptr); + OGDF_ASSERT(m_apexCandidate->getLabelUnchecked() != NodeLabel::Unknown || tpStubApex2 != nullptr); + + if (m_apexCandidate->m_nodeType == PCNodeType::PNode) { + // calculate the sizes of all sets + bool isParentFull = false; + if (parent != nullptr) { + OGDF_ASSERT(parent->getLabel() != NodeLabel::Partial); + isParentFull = parent->isFull(); + } + size_t fullNeighbors = atinfo.fullNeighbors.size(); + size_t partialNeighbors = 1; // tpStubApex1 + if (tpStubApex2 != nullptr) { + partialNeighbors = 2; + } + OGDF_ASSERT(m_apexCandidate->getDegree() >= fullNeighbors + partialNeighbors); + size_t emptyNeighbors = m_apexCandidate->getDegree() - fullNeighbors - partialNeighbors; + log << "Apex is " << m_apexCandidate->getLabel() << " " << m_apexCandidate + << " with neighbors: full=" << fullNeighbors << ", partial=" << partialNeighbors + << ", empty=" << emptyNeighbors << std::endl; + if (parent != nullptr) { + log << "Parent is " << parent->getLabel() << " " << parent << std::endl; + } + + // isolate the apex candidate + tpStubApex1->detach(); + if (tpStubApex2 != nullptr) { + tpStubApex2->detach(); + } + + // create the new central node + central = newNode(PCNodeType::CNode); + ctinfo = ¢ral->tempInfo(); + log << "New Central has index " << central->index(); + + // first step of assembly: tpStubApex1 == apexCandidate.tempInfo().tpPred + central->appendChild(tpStubApex1); + tpStubApex1->tempInfo().replaceNeighbor(m_apexCandidate, central); + + // second step of assembly: fullNode + PCNode* fullNode = nullptr; + if (fullNeighbors == 1 && isParentFull) { + m_apexCandidate->replaceWith(central); + for (auto obs : m_observers) { + obs->nodeReplaced(*this, m_apexCandidate, central); + } + fullNode = parent; + log << ", full parent is node " << parent->index(); + OGDF_ASSERT(atinfo.fullNeighbors.front() == parent); + } else if (fullNeighbors > 0) { + fullNode = splitOffFullPNode(m_apexCandidate, isParentFull); + if (isParentFull) { + OGDF_ASSERT(!m_apexCandidate->isDetached()); + m_apexCandidate->replaceWith(fullNode); + for (auto obs : m_observers) { + obs->nodeReplaced(*this, m_apexCandidate, fullNode); + } + fullNode->appendChild(central); + } else { + central->appendChild(fullNode); + } + log << ", full " << (fullNeighbors == 1 ? "neigh" : "node") << " is " << fullNode; + OGDF_ASSERT(fullNeighbors == 1 || fullNode->getDegree() == fullNeighbors + 1); + } + if (fullNode) { + central->setLabelUnchecked(NodeLabel::Partial); + ctinfo->fullNeighbors = {fullNode}; + } else { + central->setLabelUnchecked(NodeLabel::Empty); + } + + // third step of assembly: tpStubApex2 == apexTPPred2 + if (tpStubApex2 != nullptr) { + central->appendChild(tpStubApex2, isParentFull); + tpStubApex2->tempInfo().replaceNeighbor(m_apexCandidate, central); + } + + // fourth step of assembly: apexCandidate (the empty node) + PCNode* emptyNode = nullptr; + if (emptyNeighbors == 1) { + if (isParentFull || parent == nullptr) { + emptyNode = m_apexCandidate->m_child1; + emptyNode->detach(); + if (fullNode && tpStubApex2) { + emptyNode->insertBetween(tpStubApex2, tpStubApex1); + } else { + central->appendChild(emptyNode, isParentFull); + } + log << ", empty neigh is " << emptyNode; + } else { + emptyNode = parent; + m_apexCandidate->replaceWith(central); + for (auto obs : m_observers) { + obs->nodeReplaced(*this, m_apexCandidate, central); + } + log << ", empty parent is node " << parent->index(); + } + } else if (emptyNeighbors > 1) { + emptyNode = m_apexCandidate; + if (isParentFull) { + OGDF_ASSERT(fullNode); + if (tpStubApex2) { + m_apexCandidate->insertBetween(tpStubApex2, tpStubApex1); + } else { + central->appendChild(m_apexCandidate, true); + } + } else { + m_apexCandidate->appendChild(central); + } + log << ", empty node is " << m_apexCandidate; + OGDF_ASSERT(m_apexCandidate->getDegree() == emptyNeighbors + 1); + } + + for (auto obs : m_observers) { + obs->onApexMoved(*this, m_apexCandidate, central, parent); + } + + if (emptyNeighbors <= 1) { + if (m_apexCandidate == m_rootNode) { + log << ", replaced root by central"; + m_rootNode = central; + } + destroyNode(m_apexCandidate); + } else { + OGDF_ASSERT(m_apexCandidate->getDegree() > 2); + OGDF_ASSERT(m_apexCandidate->isDetached() == (m_rootNode == m_apexCandidate)); + } + log << std::endl; + OGDF_ASSERT(central->isDetached() == (m_rootNode == central)); + + // assembly done, verify +#ifdef OGDF_DEBUG + log << "Central " << central << " and neighbors ["; + std::vector cn {tpStubApex1}; + log << "TP1 " << tpStubApex1->index() << ", "; + if (fullNode) { + log << "F " << fullNode->index() << ", "; + cn.push_back(fullNode); + } + if (tpStubApex2) { + log << "TP2 " << tpStubApex2->index() << ", "; + cn.push_back(tpStubApex2); + } + if (emptyNode) { + log << "E " << emptyNode->index(); + cn.push_back(emptyNode); + } + log << "]" << std::endl; + OGDF_ASSERT(central->getDegree() == cn.size()); + OGDF_ASSERT(central->getDegree() + == partialNeighbors + (fullNeighbors > 0 ? 1 : 0) + (emptyNeighbors > 0 ? 1 : 0)); + OGDF_ASSERT(central->getDegree() >= 3); + std::list actual_neighbors { + central->neighbors().begin(), central->neighbors().end()}; + OGDF_ASSERT(actual_neighbors.size() == cn.size()); + for (size_t i = 0; actual_neighbors.front() != cn.front(); i++) { + actual_neighbors.push_back(actual_neighbors.front()); + actual_neighbors.pop_front(); + OGDF_ASSERT(i < cn.size()); + } + if (!std::equal(actual_neighbors.begin(), actual_neighbors.end(), cn.begin(), cn.end())) { + actual_neighbors.push_back(actual_neighbors.front()); + actual_neighbors.pop_front(); + OGDF_ASSERT(std::equal(actual_neighbors.rbegin(), actual_neighbors.rend(), cn.begin(), + cn.end())); + } +#endif + + // update temporary pointers around central + ctinfo->tpPred = tpStubApex1; + if (fullNode) { + ctinfo->ebEnd1 = tpStubApex1; + ctinfo->fbEnd1 = ctinfo->fbEnd2 = fullNode; + if (tpStubApex2) { + ctinfo->ebEnd2 = tpStubApex2; + } else { + ctinfo->ebEnd2 = emptyNode; + } + OGDF_ASSERT(ctinfo->ebEnd2 != nullptr); + } else { + OGDF_ASSERT(tpStubApex2 != nullptr); + ctinfo->ebEnd1 = ctinfo->fbEnd2 = tpStubApex1; + ctinfo->ebEnd2 = ctinfo->fbEnd1 = tpStubApex2; + } + } else { + central = m_apexCandidate; + ctinfo = &atinfo; + if (m_apexCandidate->getLabelUnchecked() == NodeLabel::Partial) { + if (atinfo.ebEnd2 == tpStubApex1) { + OGDF_ASSERT(atinfo.ebEnd1 == tpStubApex2 || tpStubApex2 == nullptr); + std::swap(atinfo.ebEnd1, atinfo.ebEnd2); + std::swap(atinfo.fbEnd1, atinfo.fbEnd2); + } + } else { + OGDF_ASSERT(m_apexCandidate->getLabelUnchecked() == NodeLabel::Empty); + OGDF_ASSERT(tpStubApex2 != nullptr); + OGDF_ASSERT(central->areNeighborsAdjacent(tpStubApex1, tpStubApex2)); + atinfo.ebEnd1 = tpStubApex1; + atinfo.fbEnd1 = tpStubApex2; + atinfo.fbEnd2 = tpStubApex1; + atinfo.ebEnd2 = tpStubApex2; + } + } + + OGDF_ASSERT(ctinfo->tpPred == tpStubApex1); + OGDF_ASSERT(ctinfo->ebEnd1 == tpStubApex1); + OGDF_ASSERT(central->getFullNeighInsertionPoint(tpStubApex1) == ctinfo->fbEnd1); + if (m_apexTPPred2 != nullptr) { + OGDF_ASSERT(ctinfo->ebEnd2 == tpStubApex2); + OGDF_ASSERT(central->getFullNeighInsertionPoint(tpStubApex2) == ctinfo->fbEnd2); + } + return central; +} + +int PCTree::updateTerminalPath(PCNode* central, PCNode* tpNeigh) { + int count = 0; + PCNode*& fullNeigh = central->getFullNeighInsertionPoint(tpNeigh); + while (tpNeigh != nullptr) { + PCNode::TempInfo& tinfo = tpNeigh->tempInfo(); + OGDF_ASSERT(central->areNeighborsAdjacent(tpNeigh, fullNeigh)); + OGDF_ASSERT(tinfo.tpSucc != nullptr); + OGDF_ASSERT(tpNeigh->getLabelUnchecked() != NodeLabel::Full); + OGDF_ASSERT(tpNeigh->getLabelUnchecked() != NodeLabel::Unknown || tinfo.tpPred != nullptr); + for (auto obs : m_observers) { + obs->beforeMerge(*this, count, tpNeigh); + } + PCNode* nextTPNeigh = tinfo.tpPred; + PCNode* otherEndOfFullBlock; + if (tpNeigh->m_nodeType == PCNodeType::PNode) { + PC_PROFILE_ENTER(2, "update_tp_pnode"); + if (tpNeigh->getLabelUnchecked() == NodeLabel::Partial) { + PCNode* fullNode = splitOffFullPNode(tpNeigh, false); + fullNode->insertBetween(tpNeigh, fullNeigh); + otherEndOfFullBlock = fullNeigh = fullNode; + log << "\tFull child is " << fullNode << std::endl; + } else { + otherEndOfFullBlock = nullptr; + } + if (tinfo.tpPred != nullptr) { + tinfo.tpPred->detach(); + log << "\ttpPred child is " << tinfo.tpPred << std::endl; + tinfo.tpPred->insertBetween(fullNeigh, tpNeigh); + } + + log << "\tThere are " << tpNeigh->m_childCount << " children left" << std::endl; + + for (auto obs : m_observers) { + obs->whenPNodeMerged(*this, tpNeigh, tinfo.tpPred, otherEndOfFullBlock); + } + + if (tpNeigh->m_childCount == 0) { + tpNeigh->detach(); + for (auto obs : m_observers) { + obs->nodeDeleted(*this, tpNeigh); + } + destroyNode(std::as_const(tpNeigh)); + } else if (tpNeigh->m_childCount == 1) { + PCNode* child = tpNeigh->m_child1; + child->detach(); + tpNeigh->replaceWith(child); + for (auto obs : m_observers) { + obs->nodeReplaced(*this, tpNeigh, child); + } + destroyNode(std::as_const(tpNeigh)); + } + + PC_PROFILE_EXIT(2, "update_tp_pnode"); + } else { + PC_PROFILE_ENTER(2, "update_tp_cnode"); + + auto print_tp_neigh = [](PCNode* node, int n) { + // cout << n << " node: " << node << endl << "children: " << endl; + // for (auto child : node->children()) { + // cout << child; + // if (child->isLeaf()) { + // cout << " edge: " + // << reinterpret_cast(child->leafUserData()[0]); + // } + // cout << endl; + // } + }; + print_tp_neigh(tpNeigh, 1); + + OGDF_ASSERT(tpNeigh->m_nodeType == PCNodeType::CNode); + PCNode* otherNeigh = central->getNextNeighbor(fullNeigh, tpNeigh); + bool tpNeighSiblingsFlipped = false; + if (tpNeigh->m_sibling1 == fullNeigh || tpNeigh->m_sibling2 == otherNeigh) { + tpNeighSiblingsFlipped = true; + std::swap(tpNeigh->m_sibling1, tpNeigh->m_sibling2); + } + OGDF_ASSERT(tpNeigh->m_sibling1 == otherNeigh + || (tpNeigh->m_sibling1 == nullptr && central->getParent() == otherNeigh) + || (tpNeigh->m_sibling1 == nullptr && central->getParent() == nullptr + && central->getOtherOuterChild(tpNeigh) == otherNeigh)); + OGDF_ASSERT(tpNeigh->m_sibling2 == fullNeigh + || (tpNeigh->m_sibling2 == nullptr && central->getParent() == fullNeigh) + || (tpNeigh->m_sibling2 == nullptr && central->getParent() == nullptr + && central->getOtherOuterChild(tpNeigh) == fullNeigh)); + + print_tp_neigh(tpNeigh, 2); + + if (tpNeigh->m_child1 == tinfo.fbEnd1 || tpNeigh->m_child1 == tinfo.fbEnd2) { + tpNeigh->flip(); + } +#ifdef OGDF_DEBUG + PCNode* emptyOuterChild = tpNeigh->m_child1; +#endif + PCNode* fullOuterChild = tpNeigh->m_child2; + + print_tp_neigh(tpNeigh, 3); + + if (fullOuterChild == tinfo.fbEnd2) { + std::swap(tinfo.ebEnd1, tinfo.ebEnd2); + std::swap(tinfo.fbEnd1, tinfo.fbEnd2); + } + OGDF_ASSERT(fullOuterChild == tinfo.fbEnd1); + OGDF_ASSERT(tpNeigh->getParent() == tinfo.ebEnd1); + OGDF_ASSERT(tinfo.tpPred == nullptr || tinfo.tpPred == tinfo.ebEnd2); + + print_tp_neigh(tpNeigh, 4); + + for (auto obs : m_observers) { + obs->whenCNodeMerged(*this, tpNeigh, tpNeighSiblingsFlipped, fullNeigh, + fullOuterChild); + } + + auto parent = tpNeigh->getParent(); + + tpNeigh->mergeIntoParent(); + + print_tp_neigh(parent, 6); + + OGDF_ASSERT(central->areNeighborsAdjacent(fullNeigh, fullOuterChild)); + OGDF_ASSERT(central->areNeighborsAdjacent(otherNeigh, emptyOuterChild)); + + if (tpNeigh->getLabelUnchecked() == NodeLabel::Partial) { + OGDF_ASSERT(fullOuterChild->isFull()); + fullNeigh = tinfo.fbEnd2; + otherEndOfFullBlock = tinfo.fbEnd1; + } else { + OGDF_ASSERT(tpNeigh->getLabelUnchecked() == NodeLabel::Empty); + OGDF_ASSERT(fullOuterChild == tinfo.tpPred); + otherEndOfFullBlock = nullptr; + } + OGDF_ASSERT(tinfo.tpPred == tinfo.ebEnd2 || tinfo.tpPred == nullptr); + destroyNode(std::as_const(tpNeigh)); + PC_PROFILE_EXIT(2, "update_tp_cnode"); + } + + replaceTPNeigh(central, tpNeigh, nextTPNeigh, fullNeigh, otherEndOfFullBlock); + if (nextTPNeigh != nullptr) { + nextTPNeigh->tempInfo().replaceNeighbor(tpNeigh, central); + } + auto mergedNode = tpNeigh; + tpNeigh = nextTPNeigh; + count++; + for (auto obs : m_observers) { + obs->afterMerge(*this, tpNeigh, mergedNode); + } + } + return count; +} + +void PCTree::replaceTPNeigh(PCNode* central, PCNode* oldTPNeigh, PCNode* newTPNeigh, + PCNode* newFullNeigh, PCNode* otherEndOfFullBlock) { + PCNode::TempInfo& cinfo = central->tempInfo(); + OGDF_ASSERT(oldTPNeigh != nullptr); + OGDF_ASSERT(cinfo.tpPred == cinfo.ebEnd1); + OGDF_ASSERT(m_apexTPPred2 == nullptr || m_apexTPPred2 == cinfo.ebEnd2); + if (oldTPNeigh == cinfo.tpPred) { + cinfo.tpPred = cinfo.ebEnd1 = newTPNeigh; + cinfo.fbEnd1 = newFullNeigh; + if (cinfo.fbEnd2 == oldTPNeigh) { + if (otherEndOfFullBlock != nullptr) { + cinfo.fbEnd2 = otherEndOfFullBlock; + } else { + cinfo.fbEnd2 = newTPNeigh; + } + } + } else { + OGDF_ASSERT(oldTPNeigh == m_apexTPPred2); + m_apexTPPred2 = cinfo.ebEnd2 = newTPNeigh; + cinfo.fbEnd2 = newFullNeigh; + if (cinfo.fbEnd1 == oldTPNeigh) { + if (otherEndOfFullBlock != nullptr) { + cinfo.fbEnd1 = otherEndOfFullBlock; + } else { + cinfo.fbEnd1 = newFullNeigh; + } + } + } + OGDF_ASSERT(m_apexTPPred2 != oldTPNeigh); + OGDF_ASSERT(cinfo.ebEnd1 != oldTPNeigh); + OGDF_ASSERT(cinfo.ebEnd2 != oldTPNeigh); + OGDF_ASSERT(cinfo.fbEnd1 != oldTPNeigh); + OGDF_ASSERT(cinfo.fbEnd2 != oldTPNeigh); + if (cinfo.ebEnd1 != nullptr) { + OGDF_ASSERT(central->areNeighborsAdjacent(cinfo.ebEnd1, cinfo.fbEnd1)); + } + if (cinfo.ebEnd2 != nullptr) { + OGDF_ASSERT(m_apexTPPred2 == nullptr || m_apexTPPred2 == cinfo.ebEnd2); + OGDF_ASSERT(central->areNeighborsAdjacent(cinfo.ebEnd2, cinfo.fbEnd2)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// findTerminalPath utils + +size_t PCTree::findEndOfFullBlock(PCNode* node, PCNode* pred, PCNode* curr, PCNode*& fullEnd, + PCNode*& emptyEnd) const { +#ifdef OGDF_DEBUG + PCNode* start = pred; +#endif + fullEnd = pred; + size_t count = 0; + while (curr->getLabel() == NodeLabel::Full) { + PCNode* next = node->getNextNeighbor(pred, curr); + OGDF_ASSERT(next != start); + fullEnd = pred = curr; + curr = next; + count++; + } + emptyEnd = curr; + return count; +} + +bool PCTree::setApexCandidate(PCNode* ac, bool fix) { + if (ac->tempInfo().tpSucc == nullptr) { + m_terminalPathLength++; + } else if (ac->tempInfo().tpSucc != ac) { + log << " Note: already ascended from new " << (m_apexCandidateIsFix ? "" : "non-") + << "fix apex (" << ac << ") to (" << ac->tempInfo().tpSucc << ")" << std::endl; + } + ac->tempInfo().tpSucc = ac; // mark ac as processed + if (m_apexCandidate == nullptr) { + m_apexCandidate = ac; + m_apexCandidateIsFix = fix; + return true; + } else if (m_apexCandidate == ac) { + if (!m_apexCandidateIsFix && fix) { + m_apexCandidateIsFix = true; + } + return true; + } else { + // we reached a node from which we can't ascend (nonFix), but also found the actual apex + // (fix) check whether we ascended too far, i.e. the nonFix node is a parent of the fix apex + if ((!fix && m_apexCandidateIsFix) || (fix && !m_apexCandidateIsFix)) { + PCNode *fixAC, *nonFixAC; + if (!fix && m_apexCandidateIsFix) { + fixAC = m_apexCandidate; + nonFixAC = ac; + } else { + OGDF_ASSERT(fix && !m_apexCandidateIsFix); + fixAC = ac; + nonFixAC = m_apexCandidate; + } + PCNode::TempInfo& fixTI = fixAC->tempInfo(); + PCNode::TempInfo& nonFixTI = nonFixAC->tempInfo(); + if (nonFixAC->getLabelUnchecked() == NodeLabel::Empty) { + if (fixAC->getLabelUnchecked() == NodeLabel::Partial) { + // if the fix apex is partial, it must be the tpPartialPred of the ascended-too-far empty/nonFix node + if (nonFixTI.tpPartialPred == fixAC) { + m_terminalPathLength -= nonFixTI.tpPartialHeight; + m_apexCandidate = fixAC; + m_apexCandidateIsFix = true; + return true; + } + } else { + OGDF_ASSERT(fixAC->getLabelUnchecked() == NodeLabel::Empty); + // otherwise the fix apex is also empty, but they must have the same tpPartialPred + if (nonFixTI.tpPartialPred == fixTI.tpPartialPred) { + m_terminalPathLength -= (nonFixTI.tpPartialHeight - fixTI.tpPartialHeight); + m_apexCandidate = fixAC; + m_apexCandidateIsFix = true; + return true; + } + } + } + } + log << "Conflicting " << (m_apexCandidateIsFix ? "" : "non-") << "fix apex candidates (" + << m_apexCandidate << ") and (" << ac << ")" << std::endl; + return false; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// updateTerminalPath utils + +void PCTree::updateSingletonTerminalPath() { + PCNode::TempInfo& atinfo = m_apexCandidate->tempInfo(); + OGDF_ASSERT(atinfo.tpPred == nullptr); + OGDF_ASSERT(m_apexTPPred2 == nullptr); + int fullNeighbors = atinfo.fullNeighbors.size(); + int emptyNeighbors = m_apexCandidate->getDegree() - fullNeighbors; + if (m_apexCandidate->m_nodeType == PCNodeType::PNode && fullNeighbors > 1 && emptyNeighbors > 1) { + // parent is handled and degree is always greater than 1 + PCNode* fullNode = splitOffFullPNode(m_apexCandidate, true); + PCNode* parent = m_apexCandidate->getParent(); + if (parent != nullptr && parent->getLabel() == NodeLabel::Full) { + m_apexCandidate->replaceWith(fullNode); + for (auto obs : m_observers) { + obs->nodeReplaced(*this, m_apexCandidate, fullNode); + } + fullNode->appendChild(m_apexCandidate); + for (auto obs : m_observers) { + obs->onApexMoved(*this, m_apexCandidate, fullNode, parent); + } + } else { + m_apexCandidate->appendChild(fullNode); + } + } +} + +PCNode* PCTree::splitOffFullPNode(PCNode* node, bool skip_parent) { + auto& tinfo = node->tempInfo(); + auto* parent = node->getParent(); + if (tinfo.fullNeighbors.size() == 1) { + PCNode* fullNode = tinfo.fullNeighbors.front(); + OGDF_ASSERT(fullNode != parent); + OGDF_ASSERT(node->isParentOf(fullNode)); + fullNode->detach(); + for (auto obs : m_observers) { + obs->fullNodeSplit(*this, fullNode); + } + return fullNode; + } + PCNode* fullNode = newNode(PCNodeType::PNode); + fullNode->setLabel(NodeLabel::Full); + for (PCNode* fullChild : tinfo.fullNeighbors) { + if (skip_parent) { + if (fullChild == parent) { + continue; + } + } else { + OGDF_ASSERT(fullChild != parent); + } + OGDF_ASSERT(node->isParentOf(fullChild)); + fullChild->detach(); + fullNode->appendChild(fullChild); + fullNode->tempInfo().fullNeighbors.push_back(fullChild); + } + if (skip_parent && parent != nullptr && parent->getLabel() == NodeLabel::Full) { + OGDF_ASSERT(fullNode->getDegree() >= 1); + } else { + OGDF_ASSERT(fullNode->getDegree() >= 2); + } + for (auto obs : m_observers) { + obs->fullNodeSplit(*this, fullNode); + } + return fullNode; +} + +} diff --git a/src/pctree/pctree.cpp b/src/pctree/pctree.cpp new file mode 100644 index 0000000..5415d9c --- /dev/null +++ b/src/pctree/pctree.cpp @@ -0,0 +1,96 @@ +#include "PCTree.h" + +#include + +using namespace pc_tree; + +int main() { + // create a tree with a given number of leaves + std::vector leaves; + PCTree tree(7, &leaves); + + // the tree allows any order of its leaves + OGDF_ASSERT(tree.isTrivial()); + OGDF_ASSERT(tree.getLeafCount() == 7); + OGDF_ASSERT(tree.getPNodeCount() == 1); + OGDF_ASSERT(tree.getCNodeCount() == 0); + OGDF_ASSERT(tree.possibleOrders() == factorial(6)); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "7:(6, 5, 4, 3, 2, 1, 0)"); + + // now we can force some leaves to be consecutive in all represented orders + tree.makeConsecutive({leaves.at(3), leaves.at(4), leaves.at(5)}); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "8:(7:(5, 4, 3), 6, 2, 1, 0)"); + OGDF_ASSERT(tree.getPNodeCount() == 2); + OGDF_ASSERT(tree.possibleOrders() == factorial(3) * factorial(4)); + + // further updates further change the tree + tree.makeConsecutive({leaves.at(4), leaves.at(5)}); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "9:(8:[7:[5, 4], 3], 6, 2, 1, 0)"); + + tree.makeConsecutive({leaves.at(0), leaves.at(1)}); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "10:(9:[8:[5, 4], 3], 7:[1, 0], 6, 2)"); + + tree.makeConsecutive({leaves.at(1), leaves.at(2)}); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "10:[9:[8:[5, 4], 3], 7:[2, 1, 0], 6]"); + + tree.makeConsecutive({leaves.at(2), leaves.at(3), leaves.at(4), leaves.at(5)}); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "9:[6, 8:[7:[5, 4], 3], 2, 1, 0]"); + + // if some leaves cannot be made consecutive, the update returns false and leaves the tree unchanged + OGDF_ASSERT(tree.makeConsecutive({leaves.at(2), leaves.at(0)}) == false); + OGDF_ASSERT(tree.uniqueID(uid_utils::nodeToPosition) == "9:[6, 8:[7:[5, 4], 3], 2, 1, 0]"); + OGDF_ASSERT(tree.possibleOrders() == 8); + + // we can query the (arbitrary) current order of leaves and check whether any order is represented by the tree + std::vector currentLeafOrder = + tree.currentLeafOrder(); // same entries as `leaves`, different order + OGDF_ASSERT(tree.isValidOrder(currentLeafOrder)); + // use tree.getLeaves() to get the leaves in creation order + + // iterator for all inner nodes + auto innerNode_it = tree.innerNodes(); + std::vector innerNodes {innerNode_it.begin(), innerNode_it.end()}; + OGDF_ASSERT(innerNodes.size() == tree.getPNodeCount() + tree.getCNodeCount()); + OGDF_ASSERT(innerNodes.front() == tree.getRootNode()); + + // we can also manually walk the tree + PCNode* root = tree.getRootNode(); + OGDF_ASSERT(root->getChildCount() == 5); // 6, 8:[...], 2, 1, 0 + OGDF_ASSERT(root->getChild1()->isLeaf()); + for (PCNode* n : root->children()) { + std::cout << n->index() << " "; + } + std::cout << std::endl; + + // the OGDF-based version can also visualize the PCTree + // ogdf::Graph G; + // ogdf::GraphAttributes GA(G); + // PCTreeNodeArray pc_repr(tree); + // tree.getTree(G, &GA, pc_repr); + // RadialTreeLayout l; + // l.rootSelection(RadialTreeLayout::RootSelectionType::Source); + // l.call(GA); + // GraphIO::write(GA, "pctree.svg"); + + // PCTrees can also be reconstructed from strings + PCTree from_string {"9:[6, 8:[7:[5, 4], 3], 2, 1, 0]", true}; + OGDF_ASSERT(from_string.getLeafCount() == tree.getLeafCount()); + OGDF_ASSERT(from_string.possibleOrders() == tree.possibleOrders()); + OGDF_ASSERT(from_string.uniqueID(uid_utils::nodeToPosition) == "9:[6, 8:[7:[5, 4], 3], 2, 1, 0]"); + // tree.getRestrictions() yields a list of updated via which the tree can also be reconstructed + + // alternatively, they can also be constructed manually + PCTree manual; + auto nr = manual.newNode(pc_tree::PCNodeType::CNode); + auto n1 = manual.newNode(pc_tree::PCNodeType::PNode, nr); + manual.insertLeaves(3, nr); + auto n2 = manual.newNode(pc_tree::PCNodeType::PNode, nr); + manual.insertLeaves(3, n2); + manual.insertLeaves(3, nr); + manual.insertLeaves(3, n1); + std::stringstream ss; + ss << manual; + OGDF_ASSERT(ss.str() == "0:[11, 10, 9, 5:(8, 7, 6), 4, 3, 2, 1:(14, 13, 12)]"); + + return 0; +} diff --git a/src/pctree/util/DisjointSets.h b/src/pctree/util/DisjointSets.h new file mode 100644 index 0000000..bc36af0 --- /dev/null +++ b/src/pctree/util/DisjointSets.h @@ -0,0 +1,630 @@ +/** \file + * \brief Implementation of disjoint sets data structures (union-find functionality). + * + * \author Andrej Dudenhefner + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "defines.h" + +#include +#include + +namespace pc_tree { + +#define OGDF_DISJOINT_SETS_INTERMEDIATE_PARENT_CHECK + +//! Defines options for linking two sets. +enum class LinkOptions { + Naive = 0, //!< Naive Link + Index = 1, //!< Link by index (default) + Size = 2, //!< Link by size + Rank = 3 //!< Link by rank +}; + +//! Defines options for compression search paths. +enum class CompressionOptions { + PathCompression = 0, //!< Path Compression + PathSplitting = 1, //!< Path Splitting (default) + PathHalving = 2, //!< Path Halving + Type1Reversal = 4, //!< Reversal of type 1 + Collapsing = 5, //!< Collapsing + Disabled = 6 //!< No Compression +}; + +//! Defines options for interleaving find/link operations in quickUnion. +enum class InterleavingOptions { + Disabled = 0, //!< No Interleaving (default) + Rem = 1, //!< Rem's Algorithm (only compatible with LinkOptions::Index) + Tarjan = 2, //!< Tarjan's and van Leeuwen's Algorithm (only compatible with LinkOptions::Rank) + Type0Reversal = 3, //!< Interleaved Reversal of Type 0 (only compatible with LinkOptions::Naive) + SplittingCompression = + 4 //!< Interleaved Path Splitting Path Compression (only compatible with LinkOptions::Index) +}; + +namespace disjoint_sets { + +struct AnyOption { }; + +template +struct LinkOption : AnyOption { }; + +template +struct CompressionOption : AnyOption { }; + +template +struct InterleavingOption : AnyOption { }; + +} + +//! A Union/Find data structure for maintaining disjoint sets. +template +class DisjointSets { + static_assert(interleavingOption != InterleavingOptions::Rem || linkOption == LinkOptions::Index, + "Rem's Algorithm requires linking by index."); + static_assert(interleavingOption != InterleavingOptions::Tarjan || linkOption == LinkOptions::Rank, + "Tarjan and van Leeuwen's Algorithm requires linking by rank."); + static_assert(interleavingOption != InterleavingOptions::Type0Reversal + || linkOption == LinkOptions::Naive, + "Interleaved Reversal Type 0 requires naïve linking."); + static_assert(interleavingOption != InterleavingOptions::SplittingCompression + || linkOption == LinkOptions::Index, + "Interleaved Path Splitting Path Compression requires linking by index."); + +private: + int m_numberOfSets; //!< Current number of disjoint sets. + int m_numberOfElements; //!< Current number of elements. + int m_maxNumberOfElements; //!< Maximum number of elements (array size) adjusted dynamically. + + // Arrays parents, elements, parameters, siblings map a set id to its properties. + + int* m_parents; //!< Maps set id to parent set id. + int* m_parameters; //!< Maps set id to rank/size. + int* m_siblings; //!< Maps set id to sibling set id. + + //find + int find(disjoint_sets::CompressionOption, int set); + int find(disjoint_sets::CompressionOption, int set); + int find(disjoint_sets::CompressionOption, int set); + int find(disjoint_sets::CompressionOption, int set); + int find(disjoint_sets::CompressionOption, int set); + int find(disjoint_sets::CompressionOption, int set); + + //link + int link(disjoint_sets::LinkOption, int set1, int set2); + int link(disjoint_sets::LinkOption, int set1, int set2); + int link(disjoint_sets::LinkOption, int set1, int set2); + int link(disjoint_sets::LinkOption, int set1, int set2); + + //quickUnion + bool quickUnion(disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, int set2); + bool quickUnion(disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, + int set2); + bool quickUnion(disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, int set2); + bool quickUnion(disjoint_sets::AnyOption, + disjoint_sets::InterleavingOption, int set1, int set2); + bool quickUnion(disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, + int set2); + +public: + //! Creates an empty DisjointSets structure. + /** + * \param maxNumberOfElements Expected number of Elements. + */ + explicit DisjointSets(int maxNumberOfElements = (1 << 15)) + : m_parents(nullptr), m_parameters(nullptr), m_siblings(nullptr) { + init(maxNumberOfElements); + } + + ~DisjointSets() { + delete[] this->m_parents; + delete[] this->m_parameters; + delete[] this->m_siblings; + } + + OGDF_COPY_CONSTR(DisjointSets) : DisjointSets(copy.m_maxNumberOfElements) { + m_numberOfSets = copy.m_numberOfSets; + m_numberOfElements = copy.m_numberOfElements; + if (copy.m_parents) { + std::copy_n(copy.m_parents, m_maxNumberOfElements, m_parents); + } + if (copy.m_parameters) { + std::copy_n(copy.m_parameters, m_maxNumberOfElements, m_parameters); + } + if (copy.m_siblings) { + std::copy_n(copy.m_siblings, m_maxNumberOfElements, m_siblings); + } + } + + OGDF_SWAP_OP(DisjointSets) { + std::swap(first.m_numberOfSets, second.m_numberOfSets); + std::swap(first.m_numberOfElements, second.m_numberOfElements); + std::swap(first.m_maxNumberOfElements, second.m_maxNumberOfElements); + std::swap(first.m_parents, second.m_parents); + std::swap(first.m_parameters, second.m_parameters); + std::swap(first.m_siblings, second.m_siblings); + }; + + OGDF_COPY_MOVE_BY_SWAP(DisjointSets) + + //! Resets the DisjointSets structure to be empty, also changing the expected number of elements. + void init(int maxNumberOfElements) { + this->m_maxNumberOfElements = maxNumberOfElements; + init(); + } + + //! Resets the DisjointSets structure to be empty, preserving the previous value of maxNumberOfElements. + void init() { + delete[] this->m_parents; + delete[] this->m_parameters; + delete[] this->m_siblings; + this->m_numberOfSets = 0; + this->m_numberOfElements = 0; + this->m_parents = new int[this->m_maxNumberOfElements]; + this->m_parameters = (linkOption == LinkOptions::Rank || linkOption == LinkOptions::Size) + ? new int[this->m_maxNumberOfElements] + : nullptr; + this->m_siblings = (compressionOption == CompressionOptions::Collapsing) + ? new int[this->m_maxNumberOfElements] + : nullptr; + } + + //! Returns the id of the largest superset of \p set and compresses the path according to ::CompressionOptions. + /** + * \param set Set. + * \return Superset id + * \pre \p set is a non negative properly initialized id. + */ + int find(int set) { + OGDF_ASSERT(set >= 0); + OGDF_ASSERT(set < m_numberOfElements); + return find(disjoint_sets::CompressionOption(), set); + } + + //! Returns the id of the largest superset of \p set. + /** + * \param set Set. + * \return Superset id + * \pre \p set is a non negative properly initialized id. + */ + int getRepresentative(int set) const { + OGDF_ASSERT(set >= 0); + OGDF_ASSERT(set < m_numberOfElements); + while (set != m_parents[set]) { + set = m_parents[set]; + } + return set; + } + + //! Initializes a singleton set. + /** + * \return Set id of the initialized singleton set. + */ + int makeSet() { + if (this->m_numberOfElements == this->m_maxNumberOfElements) { + int* parents = this->m_parents; + this->m_parents = new int[this->m_maxNumberOfElements * 2]; + memcpy(this->m_parents, parents, sizeof(int) * this->m_maxNumberOfElements); + delete[] parents; + + if (this->m_parameters != nullptr) { + int* parameters = this->m_parameters; + this->m_parameters = new int[this->m_maxNumberOfElements * 2]; + memcpy(this->m_parameters, parameters, sizeof(int) * this->m_maxNumberOfElements); + delete[] parameters; + } + + if (this->m_siblings != nullptr) { + int* siblings = this->m_siblings; + this->m_siblings = new int[this->m_maxNumberOfElements * 2]; + memcpy(this->m_siblings, siblings, sizeof(int) * this->m_maxNumberOfElements); + delete[] siblings; + } + this->m_maxNumberOfElements *= 2; + } + this->m_numberOfSets++; + int id = this->m_numberOfElements++; + this->m_parents[id] = id; + //Initialize size/ rank/ sibling. + if (linkOption == LinkOptions::Size) { + this->m_parameters[id] = 1; + } else if (linkOption == LinkOptions::Rank) { + this->m_parameters[id] = 0; + } + if (compressionOption == CompressionOptions::Collapsing) { + this->m_siblings[id] = -1; + } + return id; + } + + //! Unions \p set1 and \p set2. + /** + * \pre \p set1 and \p set2 are maximal disjoint sets. + * \return Set id of the union. + */ + int link(int set1, int set2) { + OGDF_ASSERT(set1 == getRepresentative(set1)); + OGDF_ASSERT(set2 == getRepresentative(set2)); + if (set1 == set2) { + return -1; + } + this->m_numberOfSets--; + return linkPure(set1, set2); + } + + //! Unions the maximal disjoint sets containing \p set1 and \p set2. + /** + * \return True, if the maximal sets containing \p set1 and \p set2 were disjoint und joined correctly. False otherwise. + */ + bool quickUnion(int set1, int set2) { + if (set1 == set2) { + return false; + } + bool result = quickUnion(disjoint_sets::LinkOption(), + disjoint_sets::InterleavingOption(), set1, set2); + m_numberOfSets -= result; + return result; + } + + //! Returns the current number of disjoint sets. + int getNumberOfSets() { return m_numberOfSets; } + + //! Returns the current number of elements. + int getNumberOfElements() { return m_numberOfElements; } + +private: + //! Unions \p set1 and \p set2 w/o decreasing the \a numberOfSets + /** + * \pre \p set1 and \p set2 are maximal disjoint sets. + * \return Set id of the union + */ + int linkPure(int set1, int set2) { + int superset = link(disjoint_sets::LinkOption(), set1, set2); + //Collapse subset tree. + if (compressionOption == CompressionOptions::Collapsing) { + int subset = set1 == superset ? set2 : set1; + int id = subset; + while (this->m_siblings[id] != -1) { + id = this->m_siblings[id]; + this->m_parents[id] = superset; + } + this->m_siblings[id] = this->m_siblings[superset]; + this->m_siblings[superset] = subset; + } + return superset; + } +}; + +//find +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + int parent = m_parents[set]; + if (set == parent) { + return set; + } else { + parent = find(disjoint_sets::CompressionOption(), + parent); + m_parents[set] = parent; + return parent; + } +} + +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + while (set != m_parents[set]) { + int parent = m_parents[set]; + int grandParent = m_parents[parent]; + m_parents[set] = grandParent; + set = grandParent; + } + return set; +} + +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + int parent = m_parents[set]; + int grandParent = m_parents[parent]; + while (parent != grandParent) { + m_parents[set] = grandParent; + set = parent; + parent = grandParent; + grandParent = m_parents[grandParent]; + } + return parent; +} + +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + int root = set; + set = m_parents[root]; + + while (set != m_parents[set]) { + int parent = m_parents[set]; + m_parents[set] = root; + set = parent; + } + m_parents[root] = set; + return set; +} + +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + while (set != m_parents[set]) { + set = m_parents[set]; + } + return set; +} + +template +int DisjointSets::find( + disjoint_sets::CompressionOption, int set) { + return m_parents[set]; +} + +//quickUnion +template +bool DisjointSets::quickUnion( + disjoint_sets::AnyOption, disjoint_sets::InterleavingOption, + int set1, int set2) { +#ifdef OGDF_DISJOINT_SETS_INTERMEDIATE_PARENT_CHECK + if (m_parents[set1] == m_parents[set2]) { + return false; + } +#endif + set1 = find(set1); + set2 = find(set2); + if (set1 != set2) { + linkPure(set1, set2); + return true; + } + return false; +} + +template +bool DisjointSets::quickUnion( + disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, int set2) { +#ifdef OGDF_DISJOINT_SETS_INTERMEDIATE_PARENT_CHECK + if (m_parents[set1] == m_parents[set2]) { + return false; + } +#endif + int root = set2; + int set = set2; + int parent = m_parents[set]; + m_parents[set] = root; + while (set != parent) { + if (parent == set1) { + m_parents[root] = set1; + return false; + } + set = parent; + parent = m_parents[set]; + m_parents[set] = root; + } + + set = set1; + parent = m_parents[set]; + while (true) { + if (parent == root) { + return false; + } + m_parents[set] = root; + if (parent == set) { + return true; + } + set = parent; + parent = m_parents[set]; + } +} + +template +bool DisjointSets::quickUnion( + disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, int set2) { + int r_x = set1; + int r_y = set2; + int p_r_x = m_parents[r_x]; + int p_r_y = m_parents[r_y]; + while (p_r_x != p_r_y) { + if (p_r_x < p_r_y) { + if (r_x == p_r_x) { + m_parents[r_x] = p_r_y; + return true; + } + m_parents[r_x] = p_r_y; + r_x = p_r_x; + p_r_x = m_parents[r_x]; + } else { + if (r_y == p_r_y) { + m_parents[r_y] = p_r_x; + return true; + } + m_parents[r_y] = p_r_x; + r_y = p_r_y; + p_r_y = m_parents[r_y]; + } + } + return false; +} + +template +bool DisjointSets::quickUnion( + disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, + int set2) { +#ifdef OGDF_DISJOINT_SETS_INTERMEDIATE_PARENT_CHECK + if (m_parents[set1] == m_parents[set2]) { + return false; + } +#endif + int set = set1; + + if (set1 < set2) { + set = set2; + set2 = set1; + set1 = set; + } + + //!Use path splitting to compress the path of set1 and get the root + set = m_parents[set]; + int parent = m_parents[set]; + int grandParent = m_parents[parent]; + while (parent != grandParent) { + m_parents[set] = grandParent; + set = parent; + parent = grandParent; + grandParent = m_parents[grandParent]; + } + m_parents[set1] = parent; + int root = parent; + + //!Redirect all nodes with smaller indices on the path of set2 to the root + set = set2; + parent = m_parents[set]; + while (true) { + if (parent < root) { + m_parents[set] = root; + if (set == parent) { + return true; + } + set = parent; + parent = m_parents[set]; + } else if (parent > root) { + m_parents[root] = parent; + m_parents[set1] = parent; + m_parents[set2] = parent; + return true; + } else { + return false; + } + } +} + +template +bool DisjointSets::quickUnion( + disjoint_sets::LinkOption, + disjoint_sets::InterleavingOption, int set1, int set2) { + int r_x = set1; + int r_y = set2; + int p_r_x = m_parents[r_x]; + int p_r_y = m_parents[r_y]; + while (p_r_x != p_r_y) { + if (m_parameters[p_r_x] <= m_parameters[p_r_y]) { + if (r_x == p_r_x) { + if (m_parameters[p_r_x] == m_parameters[p_r_y] && p_r_y == m_parents[p_r_y]) { + m_parameters[p_r_y]++; + } + m_parents[r_x] = m_parents[p_r_y]; + return true; + } + m_parents[r_x] = p_r_y; + r_x = p_r_x; + p_r_x = m_parents[r_x]; + } else { + if (r_y == p_r_y) { + m_parents[r_y] = m_parents[p_r_x]; + return true; + } + m_parents[r_y] = p_r_x; + r_y = p_r_y; + p_r_y = m_parents[r_y]; + } + } + return false; +} + +//link +template +int DisjointSets::link( + disjoint_sets::LinkOption, int set1, int set2) { + if (set1 < set2) { + m_parents[set1] = set2; + return set2; + } else { + m_parents[set2] = set1; + return set1; + } +} + +template +int DisjointSets::link( + disjoint_sets::LinkOption, int set1, int set2) { + int parameter1 = m_parameters[set1]; + int parameter2 = m_parameters[set2]; + + if (parameter1 < parameter2) { + m_parents[set1] = set2; + return set2; + } else if (parameter1 > parameter2) { + m_parents[set2] = set1; + return set1; + } else { + m_parents[set1] = set2; + m_parameters[set2]++; + return set2; + } +} + +template +int DisjointSets::link( + disjoint_sets::LinkOption, int set1, int set2) { + int parameter1 = m_parameters[set1]; + int parameter2 = m_parameters[set2]; + + if (parameter1 < parameter2) { + m_parents[set1] = set2; + m_parameters[set2] += parameter1; + return set2; + } else { + m_parents[set2] = set1; + m_parameters[set1] += parameter2; + return set1; + } +} + +template +int DisjointSets::link( + disjoint_sets::LinkOption, int set1, int set2) { + m_parents[set1] = set2; + return set2; +} + +} diff --git a/src/pctree/util/IntrusiveList.h b/src/pctree/util/IntrusiveList.h new file mode 100644 index 0000000..8616dfe --- /dev/null +++ b/src/pctree/util/IntrusiveList.h @@ -0,0 +1,218 @@ +/** \file + * \brief An intrusive list for the leaves of a PCTree. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "defines.h" + +#include + +namespace pc_tree { +template +class IntrusiveList { + T* m_first = nullptr; + T* m_last = nullptr; + size_t m_count = 0; + +public: + class iterator { + T* m_node; + + public: + // iterator traits + using iterator_category = std::input_iterator_tag; + using value_type = T*; + using difference_type = std::ptrdiff_t; + using pointer = const T*; + using reference = T*; + + explicit iterator(T* node) : m_node(node) { } + + iterator& operator++() { + m_node = m_node->m_next; + return *this; + } + + iterator operator++(int) { + iterator other = *this; + ++(*this); + return other; + } + + bool operator==(iterator other) const { return m_node == other.m_node; } + + bool operator!=(iterator other) const { return m_node != other.m_node; } + + T* operator*() const { return m_node; } + }; + + class node { + friend class IntrusiveList; + + private: + T* m_next; + T* m_prev; + }; + + iterator begin() const { return iterator(m_first); } + + iterator end() const { return iterator(nullptr); } + + void clear() { + m_first = nullptr; + m_last = nullptr; + m_count = 0; + } + + [[nodiscard]] bool empty() const { return m_count == 0; } + + [[nodiscard]] size_t size() const { return m_count; } + + T* front() const { + OGDF_ASSERT(m_first != nullptr); + return m_first; + } + + T* back() const { + OGDF_ASSERT(m_last != nullptr); + return m_last; + } + + void push_front(T* obj) { + OGDF_ASSERT(obj != nullptr); + check(); + + if (m_first == nullptr) { + obj->m_next = nullptr; + m_last = obj; + } else { + obj->m_next = m_first; + m_first->m_prev = obj; + } + + obj->m_prev = nullptr; + m_first = obj; + m_count++; + check(); + } + + void push_back(T* obj) { + OGDF_ASSERT(obj != nullptr); + check(); + + if (m_last == nullptr) { + obj->m_prev = nullptr; + m_first = obj; + } else { + obj->m_prev = m_last; + m_last->m_next = obj; + } + + obj->m_next = nullptr; + m_last = obj; + m_count++; + check(); + } + + void pop_front() { erase(front()); } + + void pop_back() { erase(back()); } + + void erase(T* obj) { + OGDF_ASSERT(obj != nullptr); + OGDF_ASSERT(m_count > 0); + check(); + + if (obj == m_first) { + m_first = obj->m_next; + } + + if (obj == m_last) { + m_last = obj->m_prev; + } + + if (obj->m_prev) { + obj->m_prev->m_next = obj->m_next; + } + + if (obj->m_next) { + obj->m_next->m_prev = obj->m_prev; + } + + m_count--; + check(); + } + + void splice(iterator at, IntrusiveList& other) { + OGDF_ASSERT(at == begin() || at == end()); + check(); + other.check(); + + if (at == end()) { + if (m_last != nullptr) { + m_last->m_next = other.m_first; + other.m_first->m_prev = m_last; + m_last = other.m_last; + } else { + m_first = other.m_first; + m_last = other.m_last; + } + } else if (at == begin()) { + if (m_first != nullptr) { + m_first->m_prev = other.m_last; + other.m_last->m_next = m_first; + m_first = other.m_first; + } else { + m_first = other.m_first; + m_last = other.m_last; + } + } + + m_count += other.m_count; + other.m_count = 0; + other.m_first = nullptr; + other.m_last = nullptr; + check(); + other.check(); + } + +private: + void check() { +#ifdef OGDF_DEBUG + size_t counter = 0; + for ([[maybe_unused]] T* _ : *this) { + counter++; + } + OGDF_ASSERT(counter == m_count); +#endif + } +}; +} diff --git a/src/pctree/util/RegisteredArray.h b/src/pctree/util/RegisteredArray.h new file mode 100644 index 0000000..822445d --- /dev/null +++ b/src/pctree/util/RegisteredArray.h @@ -0,0 +1,972 @@ +/** \file + * \brief Declaration and implementation of RegisteredArray class. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "defines.h" + +#include +#include +#include + +namespace pc_tree { +namespace internal { +template +class RegisteredArrayBase; + +template +inline typename std::enable_if::type nextPower2(T x) { + return x; +} + +//! Efficiently computes the next power of 2 without branching. +//! See "Hacker's Delight" 2nd Edition, by Henry S. Warren, Fig. 3.3 +template +inline typename std::enable_if::type nextPower2(T x) { + x = nextPower2(x); + return x | (x >> size / 2); +} +} + +//! Returns the smallest power of 2 that is no less than the given (integral) argument. +template +inline T nextPower2(T x) { + return internal::nextPower2(x - 1) + 1; +} + +//! Returns the smallest power of 2 that is no less than the given (integral) arguments. +template +inline static T nextPower2(T arg1, T arg2, Args... args) { + return nextPower2(std::max(arg1, arg2, args...)); +} + +//! The default minimum table size for registered arrays. +static constexpr int MIN_TABLE_SIZE = (1 << 4); + +//! The default growth function for registered arrays. +/** + * @return The smallest power of 2 that is no less than \p actualCount and #MIN_TABLE_SIZE. + */ +inline int calculateTableSize(int actualCount) { return nextPower2(MIN_TABLE_SIZE, actualCount); } + +//! Abstract base class for registries. +/** + * Defines the interface for event handling regarding the indexed keys. A registry manages one key type and stores all + * registered arrays associated with that key. It determines the new size of all registered arrays when + * keys are added or removed. + * + * The following methods must be implemented by all subclasses as they are used via the + * CRTP: + * \code{.cpp} + * //! Returns the index of \p key. + * static inline int keyToIndex(Key key); + * + * //! Returns whether \p key is associated with this registry. + * bool isKeyAssociated(Key key) const; + * + * //! Returns the maximum index of all keys managed by this registry. + * int maxKeyIndex() const; + * + * //! Returns the array size currently requested by this registry. + * int calculateArraySize(int add) const; + * \endcode + * + * \remark To avoid frequent costly resize operations, the array size returned by calculateArraySize + * should grow in larger steps (e.g. powers of 2) + * + * @tparam Key The key type the registry manages. + * @tparam Registry The class that implements the interface defined in RegistryBase. + * @tparam Iterator An iterator for all managed keys. Can be \c void if iterating the keys through the registered array + * is not required. To allow iterating over all keys, define \c begin() and \c end() methods. + * + * \sa RegisteredArray + */ +template +class RegistryBase { +public: + using registered_array_type = internal::RegisteredArrayBase; + using key_type = Key; + using registry_type = Registry; + using iterator_type = Iterator; + using registration_list_type = std::list; + using registration_iterator_type = typename registration_list_type::iterator; + +private: + mutable registration_list_type m_registeredArrays; + bool m_autoShrink = false; + int m_size = 0; + +protected: + RegistryBase() = default; + +public: + //! Destructor. Unregisters all associated arrays. + virtual ~RegistryBase() noexcept { unregisterArrays(); } + + RegistryBase(const RegistryBase& copy) = delete; + + RegistryBase(RegistryBase&& move) noexcept = delete; + + RegistryBase& operator=(const RegistryBase& other) = delete; + + RegistryBase& operator=(RegistryBase&& other) noexcept = delete; + + //! Registers a new array with this registry. + /** + * @param pArray A pointer to the registered array. + * @return An iterator pointing to the entry for the registered array in the list of registered arrays. + * This iterator is required for unregistering the array again. + */ + OGDF_NODISCARD registration_iterator_type registerArray(registered_array_type* pArray) const { + return m_registeredArrays.emplace(m_registeredArrays.end(), pArray); + } + + //! Unregisters an array associated with this registry. + /** + * @param it An iterator pointing to the entry of the array in the list of all registered arrays. + */ + void unregisterArray(registration_iterator_type it) const noexcept { + m_registeredArrays.erase(it); + } + + //! Stores array \p pArray at position \p it in the list of registered arrays. + void moveRegisterArray(registration_iterator_type it, registered_array_type* pArray) const { + *it = pArray; + } + + //! Records the addition of a new key and resizes all registered arrays if necessary. + void keyAdded(Key key) { + if (static_cast(this)->keyToIndex(key) >= m_size) { + resizeArrays(); + } + } + + //! Records the deletion of a key and resizes all registered arrays if auto shrink is enabled. + void keyRemoved(Key key) { + if (m_autoShrink) { + resizeArrays(); + } + } + + //! Records that all keys have been cleared. If auto shrink is enabled, all arrays are cleared and resized to 0. + void keysCleared() { resizeArrays(0); } + + //! Resizes all arrays to the size requested by calculateArraySize(). Only shrinks the arrays if auto shrink is + //! enabled + void resizeArrays() { resizeArrays(static_cast(this)->calculateArraySize(0)); } + + //! Resizes all arrays to \p size. Only shrinks the arrays if auto shrink is enabled + void resizeArrays(int size) { resizeArrays(size, m_autoShrink); } + + //! Resizes all arrays to \p size. If \p shrink is \c true, the arrays may also shrink. + void resizeArrays(int size, bool shrink) { + if (size == m_size) { + return; + } + m_size = size = std::max(size, 0); + for (registered_array_type* ab : m_registeredArrays) { + ab->resize(size, shrink); + } + } + + //! Resizes all arrays to make space of \p new_keys new keys. + void reserveSpace(int new_keys) { + resizeArrays(static_cast(this)->calculateArraySize(new_keys)); + } + + //! Swaps the entries at \p index1 and \p index2 in all registered arrays. + void swapArrayEntries(int index1, int index2) { + for (registered_array_type* ab : m_registeredArrays) { + ab->swapEntries(index1, index2); + } + } + + //! Copies the entry from \p fromIndex to \p toIndex in all registered arrays. + void copyArrayEntries(int toIndex, int fromIndex) { + for (registered_array_type* ab : m_registeredArrays) { + ab->copyEntry(toIndex, fromIndex); + } + } + + //! Unregister all associated arrays. + void unregisterArrays() noexcept { + while (!m_registeredArrays.empty()) { +#ifdef OGDF_DEBUG + auto size = m_registeredArrays.size(); +#endif + m_registeredArrays.front()->unregister(); + OGDF_ASSERT(m_registeredArrays.size() < size); + } + } + + //! Returns a reference to the list of all registered arrays. + const registration_list_type& getRegisteredArrays() const { return m_registeredArrays; } + + //! Returns whether the registry allows arrays to shrink when keys are removed. + bool isAutoShrink() const { return m_autoShrink; } + + //! Specifies whether the registry allows arrays to shrink when keys are removed. + void setAutoShrink(bool mAutoShrink) { m_autoShrink = mAutoShrink; } + + //! Returns the current size of all registered arrays. + int getArraySize() const { return m_size; } +}; + +namespace internal { +//! Abstract base class for registered arrays. +/** + * Defines the interface for event handling used by the registry. + * + * @tparam Registry The class which manages the registered keys. Must provide the functions defined in + * class RegistryBase. + */ +template +class RegisteredArrayBase { + using registry_type = Registry; + using registration_iterator_type = typename Registry::registration_iterator_type; + + registration_iterator_type m_registration; + const Registry* m_pRegistry = nullptr; + +public: + //! Creates a registered array associated with no registry. + RegisteredArrayBase() = default; + + //! Creates a registered array associated with the same registry as \p copy. + RegisteredArrayBase(const RegisteredArrayBase& copy) { reregister(copy.m_pRegistry); } + + //! Moves the registration of \p move_from to this registered array. + RegisteredArrayBase(RegisteredArrayBase&& move_from) noexcept { + moveRegister(move_from); + } + + //! Assignment operator. + RegisteredArrayBase& operator=(const RegisteredArrayBase& copy) { + reregister(copy.m_pRegistry); + return *this; + } + + //! Assignment operator (move semantics). + RegisteredArrayBase& operator=(RegisteredArrayBase&& move_from) noexcept { + moveRegister(move_from); + return *this; + } + + //! Destructor. + virtual ~RegisteredArrayBase() noexcept { + if (m_pRegistry) { + m_pRegistry->unregisterArray(m_registration); + } + } + + //! Resizes the registered array to \p size. The array will only shrink if \p shrink is \c true. + virtual void resize(int size, bool shrink) = 0; + + //! Swaps the entries stored at \p index1 and \p index2. + virtual void swapEntries(int index1, int index2) = 0; + + //! Copies the entry stored at \p oldIndex to \p newIndex. + virtual void copyEntry(int newIndex, int oldIndex) = 0; + + //! Clears the array and associates it with no registry. + void unregister() noexcept { + resize(0, true); + reregister(nullptr); + } + +protected: + //! Associates the array with a new registry. + void reregister(const Registry* registry) { + if (m_pRegistry) { + m_pRegistry->unregisterArray(m_registration); + } + m_pRegistry = registry; + if (m_pRegistry != nullptr) { + m_registration = m_pRegistry->registerArray(this); + } else { + m_registration = registration_iterator_type(); + } + } + + //! Moves array registration from \p move_from to this array. + void moveRegister(RegisteredArrayBase& move_from) { + if (m_pRegistry) { + m_pRegistry->unregisterArray(m_registration); + } + m_pRegistry = move_from.m_pRegistry; + m_registration = move_from.m_registration; + move_from.m_pRegistry = nullptr; + move_from.m_registration = registration_iterator_type(); + if (m_pRegistry != nullptr) { + m_pRegistry->moveRegisterArray(m_registration, this); + } + } + +public: + //! Returns a pointer to the associated registry. + const Registry* registeredAt() const { return m_pRegistry; } +}; +} + +//! Iterator for registered arrays. +/** + * Provides an iterator for the key-value pairs stored in registered arrays. + * + * @tparam ArrayType The type of registered array. + * @tparam KeyIterator An iterator for the keys in the registry. Determines the order of the key-value pairs. + * @tparam isConst Whether the iterator allows modifying the data or not. + */ +template +class RegisteredArrayIterator { +public: + using registry_type = typename ArrayType::registry_type; + using key_type = typename ArrayType::key_type; + using value_type = typename std::conditional::type; + using array_pointer_type = typename std::conditional::type; + +private: + KeyIterator m_it; + array_pointer_type m_array; + +public: + //! Creates a new iterator associated with no array. + RegisteredArrayIterator() : m_it(), m_array(nullptr) { } + + //! Creates a new iterator. + /** + * @param mIt An iterator pointing to the current key. + * @param mArray A pointer to the registered array containing the desired values. + */ + RegisteredArrayIterator(KeyIterator mIt, array_pointer_type mArray) + : m_it(mIt), m_array(mArray) { } + + //! Returns the current key. + key_type key() const { return *m_it; } + + //! Returns the value of key() in the registered array. + value_type& value() const { return (*m_array)[*m_it]; } + + //! Returns the value of key() in the registered array. + value_type& operator*() const { return (*m_array)[*m_it]; } + + //! Equality operator. + bool operator==(const RegisteredArrayIterator& iter) const { + return m_it == iter.m_it && m_array == iter.m_array; + } + + //! Inequality operator. + bool operator!=(const RegisteredArrayIterator& iter) const { + return !operator==(iter); + } + + //! Increment operator (prefix). + RegisteredArrayIterator& operator++() { + ++m_it; + return *this; + } + + //! Increment operator (postfix). + RegisteredArrayIterator operator++(int) { + RegisteredArrayIterator iter = *this; + ++m_it; + return iter; + } + + //! Decrement operator (prefix). + RegisteredArrayIterator& operator--() { + --m_it; + return *this; + } + + //! Decrement operator (postfix). + RegisteredArrayIterator operator--(int) { + RegisteredArrayIterator iter = *this; + --m_it; + return iter; + } +}; + +namespace internal { +//! Registered arrays without default values or by-index access to values. +/** + * Registered arrays provide an efficient, constant-time mapping from indexed keys of a \a Registry to elements of + * type \a Value. The storage automatically grows and shrinks when keys are added to or removed from the registry. + * New values are initialized using the default constructor of \a Value. + * + * @tparam Registry The class which manages the registered keys. Must provide the functions defined in + * class RegistryBase. + * @tparam Value The type of the stored data. + * + * \sa RegistryBase, RegisteredArrayWithoutDefaultWithIndexAccess, RegisteredArrayWithoutDefault, RegisteredArrayWithDefault, RegisteredArray + */ +template +class RegisteredArrayWithoutDefaultOrIndexAccess : protected RegisteredArrayBase { +protected: + using key_iterator = typename Registry::iterator_type; + using registered_array = RegisteredArrayWithoutDefaultOrIndexAccess; + using registered_array_base = RegisteredArrayBase; + +public: + using registry_type = Registry; + using key_type = typename Registry::key_type; + using vector_type = std::vector; + using value_type = typename vector_type::value_type; + using value_ref_type = typename vector_type::reference; + using value_const_ref_type = typename vector_type::const_reference; + + using iterator = RegisteredArrayIterator; + using const_iterator = RegisteredArrayIterator; + +protected: + vector_type m_data; + +public: + //! Creates a new registered array associated with no registry. + RegisteredArrayWithoutDefaultOrIndexAccess() = default; + + //! Creates a new registered array associated with \p registry. + explicit RegisteredArrayWithoutDefaultOrIndexAccess(const Registry* registry) { + // during base class initialization, no part of the derived class exists, so this will always call our base init + // so base classes should call their own init themselves + registered_array::init(registry); + } + + //! Associates the array with \p registry. All entries are initialized using the default constructor of \a Value. + void init(const Registry* registry = nullptr) { + if (registry == nullptr) { + resize(0, true); + } else { + OGDF_ASSERT(registry->maxKeyIndex() < registry->getArraySize()); + resize(0, false); + resize(registry->getArraySize(), true); + } + registered_array::reregister(registry); + } + + //! Fills all entries with value \p x. + void fill(value_const_ref_type x) { m_data.assign(m_data.size(), x); } + + //! Returns a const reference to the element associated with \p key. + value_const_ref_type operator[](key_type key) const { + OGDF_ASSERT(getRegistry().isKeyAssociated(key)); +#ifdef OGDF_DEBUG + return m_data.at(registeredAt()->keyToIndex(key)); +#else + return m_data[registeredAt()->keyToIndex(key)]; +#endif + } + + //! Returns a reference to the element associated with \p key. + value_ref_type operator[](key_type key) { + OGDF_ASSERT(getRegistry().isKeyAssociated(key)); +#ifdef OGDF_DEBUG + return m_data.at(registeredAt()->keyToIndex(key)); +#else + return m_data[registeredAt()->keyToIndex(key)]; +#endif + } + + //! Returns a const reference to the element associated with \p key. + value_const_ref_type operator()(key_type key) const { + OGDF_ASSERT(getRegistry().isKeyAssociated(key)); +#ifdef OGDF_DEBUG + return m_data.at(registeredAt()->keyToIndex(key)); +#else + return m_data[registeredAt()->keyToIndex(key)]; +#endif + } + + //! Returns a reference to the element associated with \p key. + value_ref_type operator()(key_type key) { + OGDF_ASSERT(getRegistry().isKeyAssociated(key)); +#ifdef OGDF_DEBUG + return m_data.at(registeredAt()->keyToIndex(key)); +#else + return m_data[registeredAt()->keyToIndex(key)]; +#endif + } + + //! Returns an iterator to the first key-value pair in the array. + iterator begin() { + using std::begin; + return iterator(begin(getRegistry()), this); + } + + //! Returns a const iterator to the first key-value pair in the array. + const_iterator begin() const { + using std::begin; + return const_iterator(begin(getRegistry()), this); + } + + //! Returns a const iterator to the first key-value pair in the array. + const_iterator cbegin() const { + using std::begin; + return const_iterator(begin(getRegistry()), this); + } + + //! Returns the past-the-end iterator of the array. + iterator end() { + using std::end; + return iterator(end(getRegistry()), this); + } + + //! Returns the const past-the-end iterator of the array. + const_iterator end() const { + using std::end; + return const_iterator(end(getRegistry()), this); + } + + //! Returns the const past-the-end iterator of the array. + const_iterator cend() const { + using std::end; + return const_iterator(end(getRegistry()), this); + } + + using registered_array_base::registeredAt; + + //! Returns true iff the array is associated with a registry. + bool valid() const { + OGDF_ASSERT(registeredAt() == nullptr || registeredAt()->maxKeyIndex() < 0 + || ((size_t)registeredAt()->maxKeyIndex()) < m_data.size()); + return registeredAt(); + } + +protected: + //! Returns a reference to the associated registry. + inline const Registry& getRegistry() const { + OGDF_ASSERT(registeredAt()); + OGDF_ASSERT(valid()); + return *registeredAt(); + } + + void resize(int size, bool shrink) override { + m_data.resize(size); + if (shrink) { + m_data.shrink_to_fit(); + } + } + + void swapEntries(int index1, int index2) override { + std::swap(m_data.at(index1), m_data.at(index2)); + } + + //! This operation is not supported for registered arrays without default. + void copyEntry(int toIndex, int fromIndex) override { + // silently ignored + } +}; + +//! RegisteredArrayWithoutDefaultOrIndexAccess that also allows accessing its values directly by their index. +template +class RegisteredArrayWithoutDefaultWithIndexAccess + : public RegisteredArrayWithoutDefaultOrIndexAccess { + using RA = RegisteredArrayWithoutDefaultOrIndexAccess; + +public: + RegisteredArrayWithoutDefaultWithIndexAccess() = default; + + explicit RegisteredArrayWithoutDefaultWithIndexAccess(const Registry* registry) + : RA(registry) { } + + using RA::operator[]; + + //! Returns a const reference to the element with index \p idx. + typename RA::value_const_ref_type operator[](int idx) const { +#ifdef OGDF_DEBUG + return RA::m_data.at(idx); +#else + return RA::m_data[idx]; +#endif + } + + //! Returns a reference to the element with index \p idx. + typename RA::value_ref_type operator[](int idx) { +#ifdef OGDF_DEBUG + return RA::m_data.at(idx); +#else + return RA::m_data[idx]; +#endif + } +}; + +//! Registered arrays without default values that automatically allows by-index access to values if Registry::key_type is not already integral. +template +using RegisteredArrayWithoutDefault = + typename std::conditional_t, + RegisteredArrayWithoutDefaultOrIndexAccess, + RegisteredArrayWithoutDefaultWithIndexAccess>; + +//! Registered arrays with default values. +/** + * Extends the functionality of RegisteredArrayWithoutDefault by adding the possibility to set a specific default value + * for new keys added to the registry. + * + * \pre Type \a Value must be copy-constructible. + * + * @tparam Registry The class which manages the registered keys. Must provide the functions defined in + * class RegistryBase. + * @tparam Value The type of the stored data. + * + * \sa RegistryBase, RegisteredArrayWithoutDefault, RegisteredArrayWithoutDefaultOrIndexAccess, RegisteredArray + */ +template +class RegisteredArrayWithDefault : public RegisteredArrayWithoutDefault { + using RA = RegisteredArrayWithoutDefault; + Value m_default; + + static_assert(std::is_copy_constructible_v, + "This RegisteredArrayWithDefault instantiation (e.g. NodeArray) is " + "invalid because Value is not copy-constructible! " + "Use, e.g., NodeArrayP or NodeArray, false> instead."); + +public: + //! Creates a new registered array associated with no registry and a default-constructed default value. + explicit RegisteredArrayWithDefault() : RA(), m_default() {}; + + //! Creates a new registered array associated with \p registry and a default-constructed default value. + explicit RegisteredArrayWithDefault(const Registry* registry) : RA(), m_default() { + // call init from here, as our virtual override of init is not available during initialization of the base class + RA::init(registry); + }; + + //! Creates a new registered array associated with no registry and default value \p def. + explicit RegisteredArrayWithDefault(const Value& def) : RA(), m_default(def) {}; + + //! Creates a new registered array associated with \p registry and default value \p def. + explicit RegisteredArrayWithDefault(const Registry* registry, const Value& def) + : RA(), m_default(def) { + // call init from here, as our virtual override of init is not available during initialization of the base class + RA::init(registry); + }; + + //! Creates a new registered array associated with no registry and default value \p def. + explicit RegisteredArrayWithDefault(Value&& def) : RA(), m_default(std::forward(def)) {}; + + //! Creates a new registered array associated with \p registry and default value \p def. + explicit RegisteredArrayWithDefault(const Registry* registry, Value&& def) + : RA(), m_default(std::forward(def)) { + // call init from here, as our virtual override of init is not available during initialization of the base class + RA::init(registry); + }; + + //! Sets a new default value for new keys. + void setDefault(Value&& def) { m_default = std::forward(def); } + + //! Sets a new default value for new keys. + void setDefault(const Value& def) { m_default = def; } + + //! Returns the current default value for new keys. + const Value& getDefault() const { return m_default; } + + //! Returns the current default value for new keys. + Value& getDefault() { return m_default; } + + //! Overwrites all values with the current default value. + void fillWithDefault() { RA::m_data.assign(RA::getRegistry().getArraySize(), m_default); } + +protected: + //! Copies the entry stored at \p oldIndex to \p newIndex. + void copyEntry(int toIndex, int fromIndex) override { + RA::m_data.at(toIndex) = RA::m_data.at(fromIndex); + } + + void resize(int size, bool shrink) override { + RA::m_data.resize(size, m_default); + if (shrink) { + RA::m_data.shrink_to_fit(); + } + } +}; +} + +//! Dynamic arrays indexed with arbitrary keys. +/** + * Registered arrays provide an efficient, constant-time mapping from indexed keys of a \a Registry to elements of + * type \a Value. The storage automatically grows and shrinks when keys are added to or removed from the registry. + * + * \warning When the array grows or shrinks, all pointers to its entries become invalid. + * + * @tparam Registry The class which manages the registered keys. Must provide the functions defined in + * class RegistryBase. + * @tparam Value The type of the stored data. + * @tparam WithDefault Determines whether the registered array inherits from RegisteredArrayWithDefault + * or RegisteredArrayWithoutDefault. With \a WithDefault \= \c true, the array can be initialized with specific default + * values, but this requires \a Value to be a copy-constructible type. With \a WithDefault \= \c false, the array uses + * the default constructor of \a Value to initialize new storage. + * @tparam Base The class that manages multiple related registries. \a Base must be convertible + * to \a Registry. If only one such registry exists, \a Base and \a Registry can be the + * same class (i.e. \a Base directly inherits from class RegistryBase) + * + * ### Class Interaction + * - **Key** + * + * Used to index the registered array. Every key must have a unique non-negative index. Must either provide a public + * method called \c index() or the template function keyToIndex() must offer a specialization for \a Key to give access + * to its index. + * + * - **Value** + * + * The type of the elements stored in the array. + * + * - **RegistryBase** + * + * Defines the interface for the \a Registry. + * + * - **Registry** + * + * Implements the abstract functionality defined in RegistryBase. Manages the objects of type \a Key and stores a list + * of associated registered arrays for \a Key. Determines the growth rate of the arrays. When keys are added or + * removed, the functions RegistryBase::keyAdded(), RegistryBase::keyRemoved(), and RegistryBase::keysCleared() + * should be called so that the size of all arrays will be adjusted accordingly. + * + * - **RegisteredArrayBase** + * + * Abstract base class for all registered arrays. The \a Registry communicates with its registered arrays using + * this interface. + * + * - **RegisteredArrayWithoutDefault** + * + * Provides the core functionality for accessing the values stored in the array. New entries are initialized using the + * default constructor of type \a Value. Additionally, there are variants providing by-index access if the Keys are not already ints. + * + * - **RegisteredArrayWithDefault** + * + * Extends the functionality of RegisteredArrayWithoutDefault by adding the possibility to set a specific default value + * for new keys added to the registry. This requires type \a Value to be copy-constructible. + * + * - **RegisteredArray** + * + * Used in user code. Inherits from RegisteredArrayWithoutDefault or RegisteredArrayWithDefault, depending on the + * template parameter \a WithDefault. + * + * + * ### Example Setup + * + * A simple registry that only allows addition of keys: + * \code + * class ExampleKey { + * int m_index; + * + * public: + * explicit ExampleKey(int index) : m_index(index) {} + * int index() const { return m_index; } + * }; + * + * class ExampleRegistry : public RegistryBase { + * std::list> m_keys; + * + * public: + * ExampleKey *newKey() { + * m_keys.push_back(std::unique_ptr(new ExampleKey(m_keys.size()))); + * keyAdded(m_keys.back().get()); + * return m_keys.back().get(); + * } + * + * bool isKeyAssociated(ExampleKey *key) const override { return true; } + * int maxKeyIndex() const override { return m_keys.size() - 1; } + * int calculateArraySize() const override { return calculateTableSize(m_keys.size()); } + * void begin() const override {} + * void end() const override {} + * }; + * \endcode + * With this setup, registering an array and modifying its values works as follows: + * \code + * ExampleRegistry G; + * RegisteredArray R(G); + * ExampleKey *key = G.newKey(); + * R[key] = 42; + * \endcode + * + * \sa RegisteredArrayWithoutDefaultOrIndexAccess, RegisteredArrayWithoutDefault, RegisteredArrayWithDefault, RegistryBase, NodeArray + */ +template +class RegisteredArray + : public std::conditional, + internal::RegisteredArrayWithoutDefault>::type { + using RA = + typename std::conditional, + internal::RegisteredArrayWithoutDefault>::type; + + static inline const Registry* cast(const Base* base) { + if (base != nullptr) { + // unpack the pointer to invoke the conversion operator + return &((const Registry&)*base); + } else { + return nullptr; + } + } + +public: + //! Creates a new registered array associated with no registry. + RegisteredArray() : RA() {}; + + //! Creates a new registered array associated with the matching registry of \p base. + explicit RegisteredArray(const Base& base) : RA(cast(&base)) {}; + + /** + * Creates a new registered array associated with the matching registry of \p base and initializes all values with \p def. + * + * \remarks This constructor is only available with \a WithDefault \= \c true. + */ + RegisteredArray(const Base& base, const Value& def) : RA(cast(&base), def) {}; + + //! Creates a new registered array associated with the matching registry of \p base. + explicit RegisteredArray(const Base* base) : RA(cast(base)) {}; + + /** + * Creates a new registered array associated with the matching registry of \p base and initializes all values with \p def. + * + * \remarks This constructor is only available with \a WithDefault \= \c true. + */ + RegisteredArray(const Base* base, const Value& def) : RA(cast(base), def) {}; + + //! Reinitializes the array. Associates the array with the matching registry of \p base. + void init(const Base* base = nullptr) { RA::init(cast(base)); } + + //! Reinitializes the array. Associates the array with the matching registry of \p base. + void init(const Base& base) { RA::init(cast(&base)); } + + /** + * Reinitializes the array with default value \p new_default. Associates the array with the matching registry of \p base. + * + * \remarks This method is only available with \a WithDefault \= \c true. + */ + void init(const Base& base, const Value& new_default) { + RA::setDefault(new_default); + RA::init(cast(&base)); + } + + void init(const Base* base, const Value& new_default) { + RA::setDefault(new_default); + RA::init(cast(base)); + } +}; + +//! Specialization to work around vector. +template +class RegisteredArray + : public RegisteredArray { + using RA = RegisteredArray; + using BRA = RegisteredArray; + +public: + using RA::RA; + + using key_type = typename RA::key_type; + using value_type = bool; + using value_const_ref_type = const bool&; + using value_ref_type = bool&; + using iterator = RegisteredArrayIterator; + using const_iterator = RegisteredArrayIterator; + + value_const_ref_type operator[](key_type key) const { + return reinterpret_cast(RA::operator[](key)); + } + + value_ref_type operator[](key_type key) { + return reinterpret_cast(RA::operator[](key)); + } + + value_const_ref_type operator()(key_type key) const { + return reinterpret_cast(RA::operator()(key)); + } + + value_ref_type operator()(key_type key) { + return reinterpret_cast(RA::operator()(key)); + } + + value_const_ref_type operator[](int idx) const { + return reinterpret_cast(RA::operator[](idx)); + } + + value_ref_type operator[](int idx) { + return reinterpret_cast(RA::operator[](idx)); + } + + value_ref_type getDefault() { return reinterpret_cast(RA::getDefault()); } + + value_const_ref_type getDefault() const { + return reinterpret_cast(RA::getDefault()); + } + + iterator begin() { + using std::begin; + return iterator(begin(RA::getRegistry()), this); + } + + const_iterator begin() const { + using std::begin; + return const_iterator(begin(RA::getRegistry()), this); + } + + const_iterator cbegin() const { + using std::begin; + return const_iterator(begin(RA::getRegistry()), this); + } + + iterator end() { + using std::end; + return iterator(end(RA::getRegistry()), this); + } + + const_iterator end() const { + using std::end; + return const_iterator(end(RA::getRegistry()), this); + } + + const_iterator cend() const { + using std::end; + return const_iterator(end(RA::getRegistry()), this); + } +}; +} + +template +//! Copy data from a ABCArray to an XYZArray +inline void invertRegisteredArray(const RA1& from, RA2& to) { + OGDF_ASSERT(from.registeredAt() != nullptr); + for (const auto& key : *from.registeredAt()) { + to[from[key]] = key; + } +} + +/* The following macro will be expanded in the docs, see doc/ogdf-doxygen.cfg:EXPAND_AS_DEFINED */ + +#define OGDF_DECL_REG_ARRAY(NAME) \ + template \ + using NAME = OGDF_DECL_REG_ARRAY_TYPE(Value, WithDefault); \ + /*! Shorthand for \ref NAME storing std::unique_ptr. \n + You may need to explicitly delete the copy constructor + of classes containing a member of this type for MSVC<=16 + (e.g. using OGDF_NO_COPY(MyClass)). */ \ + template \ + using NAME##P = NAME, false>; diff --git a/src/pctree/util/RegisteredSet.h b/src/pctree/util/RegisteredSet.h new file mode 100644 index 0000000..f77b856 --- /dev/null +++ b/src/pctree/util/RegisteredSet.h @@ -0,0 +1,182 @@ +/** \file + * \brief Declaration and implementation of ogdf::RegisteredSet. + * + * \author Simon D. Fink, Matthias Pfretzschner + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ +#pragma once + +#include "RegisteredArray.h" +#include "defines.h" + +namespace pc_tree { + +//! Constant-time set operations. +/** + * Maintains a subset of indexed keys managed by a registry. + * + * Provides efficient operations for testing membership, + * iteration, insertion, and deletion of elements, as well as clearing the set. + * + * @tparam Registry The class which manages the registered keys. Must provide the functions defined in + * class RegistryBase. + * @tparam SupportFastSizeQuery Whether this set supports querying its #size in + * constant instead of linear time (in the size). + * + * \sa RegisteredArray, NodeSet + */ +template +class RegisteredSet { +public: + using element_type = typename Registry::key_type; + using list_type = typename std::list; + using iter_type = typename list_type::iterator; + + //! Creates an empty set associated with registry \p R. + explicit RegisteredSet(const Registry& R) : m_it(&R) { } + + //! Creates an empty set associated with no registry. + explicit RegisteredSet() : m_it() { } + + //! Reinitializes the set. Associates the set with no registry. + void init() { + m_it.init(); + m_elements.clear(); + } + + //! Reinitializes the set. Associates the set with registry \p R. + void init(const Registry& R) { + m_it.init(&R); + m_elements.clear(); + } + + //! Inserts element \p v into this set. + /** + * This operation has constant runtime. + * If the element is already contained in this set, nothing happens. + * + * \pre \p v is an element in the associated registry. + */ + void insert(element_type v) { + iter_type& itV = m_it[v]; + if (!itV.valid()) { + itV = m_elements.pushBack(v); + } + } + + //! Removes element \p v from this set and return \p true iff \p v was previously present. + /** + * This operation has constant runtime. + * If the element is not contained in this set, nothing happens and \p false is returned. + * + * \pre \p v is an element in the associated registry. + */ + bool remove(element_type v) { + iter_type& itV = m_it[v]; + if (itV.valid()) { + m_elements.del(itV); + itV = iter_type(); + return true; + } else { + return false; + } + } + + //! Removes all elements from this set. + /** + * After this operation, this set is empty and still associated with the same registry. + * The runtime of this operation is linear in the #size(). Implementation Detail: + * If less than 10% of all elements are part of this set, they will be individually cleared. + * Otherwise, \c std::vector::assign(...) will be used to clear all values. + */ + void clear() { + if (!registeredAt()) { + return; + } + if (size() * 10 < registeredAt()->getArraySize()) { + while (!m_elements.empty()) { + remove(m_elements.front()); + } + } else { + m_it.fill(iter_type()); + m_elements.clear(); + } + } + + //! Returns \c true iff element \p v is contained in this set. + /** + * This operation has constant runtime. + * + * \pre \p v is an element in the associated registry. + */ + bool isMember(element_type v) const { return m_it[v].valid(); } + + //! Returns the same as isMember() to use an RegisteredSet instance as filter function. + bool operator()(element_type v) const { return isMember(v); } + + //! Returns a reference to the list of elements contained in this set. + const list_type& elements() const { return m_elements; } + + //! Returns the associated registry. + const Registry* registeredAt() const { return m_it.registeredAt(); } + + //! Returns the number of elements in this set. + /** + * This operation has either linear or constant runtime, depending on \a SupportFastSizeQuery. + */ + int size() const { return m_elements.size(); } + + typename list_type::const_iterator begin() const { return m_elements.begin(); } + + typename list_type::const_iterator end() const { return m_elements.end(); } + + //! Copy constructor. + template + explicit RegisteredSet(const RegisteredSet& other) { + this = other; + } + + //! Assignment operator. + template + RegisteredSet& operator=(const RegisteredSet& other) { + m_elements.clear(); + m_it.init(other.registeredAt()); + for (element_type v : other.elements()) { + insert(v); + } + } + +private: + //! #m_it[\a v] contains the list iterator pointing to \a v if \a v is contained in this set, + //! or an invalid list iterator otherwise. + RegisteredArray m_it; + + //! The list of elements contained in this set. + list_type m_elements; +}; + +} diff --git a/src/pctree/util/copy_move.h b/src/pctree/util/copy_move.h new file mode 100644 index 0000000..418ef35 --- /dev/null +++ b/src/pctree/util/copy_move.h @@ -0,0 +1,90 @@ +/** \file + * \brief Utility macros for declaring copy and move constructors and assignment operations. + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +/* This needs to be synchronized with doc/ogdf-doxygen.cfg:EXPAND_AS_DEFINED */ + +//! Explicitly disables (deletes) copy construction and assignment for class \p cls. +#define OGDF_NO_COPY(cls) \ + cls(const cls& copy) = delete; \ + cls& operator=(const cls& copy) = delete; + +//! Explicitly disables (deletes) move construction and assignment for class \p cls. +#define OGDF_NO_MOVE(cls) \ + cls(cls&& move) = delete; \ + cls& operator=(cls&& move) = delete; + +//! Explicitly provides default copy construction and assignment for class \p cls. +#define OGDF_DEFAULT_COPY(cls) \ + cls(const cls& copy) = default; \ + cls& operator=(const cls& copy) = default; + +//! Explicitly provides default move construction and assignment for class \p cls. +#define OGDF_DEFAULT_MOVE(cls) \ + cls(cls&& move) noexcept = default; \ + cls& operator=(cls&& move) noexcept = default; + +//! Declares the copy constructor for class \p cls. +#define OGDF_COPY_CONSTR(cls) cls(const cls& copy) +//! Declares the copy assignment operation for class \p cls. Don't forget to return \p *this; +#define OGDF_COPY_OP(cls) cls& operator=(const cls& copy) +//! Declares the move constructor for class \p cls. +#define OGDF_MOVE_CONSTR(cls) cls(cls&& move) noexcept : cls() +//! Declares the move assignment operation for class \p cls. +#define OGDF_MOVE_OP(cls) cls& operator=(cls&& move) noexcept +//! Declares the swap function for class \p cls. +#define OGDF_SWAP_OP(cls) friend void swap(cls& first, cls& second) noexcept + +//! Provide move construct/assign and copy assign given there is a copy constructor and swap. +/** + * Requires custom implementations of OGDF_COPY_CONSTR and OGDF_SWAP_OP (may not be the std::swap via move to temporary) + * and automatically provides OGDF_COPY_OP, OGDF_MOVE_CONSTR, and OGDF_MOVE_OP. + * + * See https://stackoverflow.com/a/3279550 for more details on this idiom. + * Note that your swap implementation is expected to be noexcept (as declared e.g. by OGDF_SWAP_OP) + * as all methods declared by this macro are also noexcept (see https://stackoverflow.com/a/7628345). + */ +#define OGDF_COPY_MOVE_BY_SWAP(cls) \ + /*! Assign this cls to be a copy of \p copy_by_value. + Internally, this will use the \ref cls ## (const cls&) "copy constructor" to create a temporary copy + of the argument \p copy_by_value (as it is passed by value) and then \ref swap(cls&, cls&) "swap" this object + instance with the temporary copy. + If the assigned-from object can be moved, the move constructor will be automatically used instead of copying. + Note that this method thereby covers both copy-assignment and move-assignment. + See https://stackoverflow.com/a/3279550 for more details on this idiom. + This method is noexcept as a potentially-throwing copy constructor call is made within the context + of the caller (see https://stackoverflow.com/a/7628345) and swap is expected to be noexcept. */ \ + cls& operator=(cls copy_by_value) noexcept { \ + swap(*this, copy_by_value); \ + return *this; \ + } \ + OGDF_MOVE_CONSTR(cls) { swap(*this, move); } diff --git a/src/pctree/util/defines.h b/src/pctree/util/defines.h new file mode 100644 index 0000000..9f2a1c6 --- /dev/null +++ b/src/pctree/util/defines.h @@ -0,0 +1,44 @@ +/** \file +* \brief Defines usually taken from the OGDF + * + * \author Simon D. Fink + * + * \par License: + * This file is part of the Open Graph Drawing Framework (OGDF). + * + * \par + * Copyright (C)
+ * See README.md in the OGDF root directory for details. + * + * \par + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 or 3 as published by the Free Software Foundation; + * see the file LICENSE.txt included in the packaging of this file + * for details. + * + * \par + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * \par + * You should have received a copy of the GNU General Public + * License along with this program; if not, see + * http://www.gnu.org/copyleft/gpl.html + */ + +#pragma once + +#include "copy_move.h" + +#include + +#define OGDF_EXPORT +#define OGDF_NODISCARD +#define OGDF_NEW_DELETE +#define OGDF_DEBUG +#define OGDF_ASSERT(x) assert(x) +#define OGDF_HEAVY_ASSERT(x) assert(x) +#define OGDF_DEPRECATED(x)