From 91bb7ed0c51ba47f011199af7bc0a3a2964cf5be Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 10 Dec 2017 17:47:09 +0100 Subject: [PATCH] fixes #0003185: Recompute should work if only subgraph is cyclic --- src/App/Document.cpp | 112 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 737a168e68..34438f0238 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -180,6 +180,8 @@ struct DocumentP std::vector &all_paths, Path tmp); std::vector topologicalSort(const std::vector& objects) const; + std::vector + partialTopologicalSort(const std::vector& objects) const; }; } // namespace App @@ -2214,8 +2216,8 @@ int Document::recompute() vector topoSortedObjects = topologicalSort(); if (topoSortedObjects.size() != d->objectArray.size()){ - cerr << "App::Document::recompute(): topological sort fails, invalid DAG!" << endl; - return -1; + cerr << "App::Document::recompute(): cyclic dependency detected" << endl; + topoSortedObjects = d->partialTopologicalSort(d->objectArray); } for (auto objIt = topoSortedObjects.rbegin(); objIt != topoSortedObjects.rend(); ++objIt){ @@ -2247,6 +2249,112 @@ int Document::recompute() #endif // USE_OLD_DAG +/*! + Does almost the same as topologicalSort() until no object with an input degree of zero + can be found. It then searches for objects with an output degree of zero until neither + an object with input or output degree can be found. The remaining objects form one or + multiple cycles. + An alternative to this method might be: + https://en.wikipedia.org/wiki/Tarjan%E2%80%99s_strongly_connected_components_algorithm + */ +std::vector DocumentP::partialTopologicalSort(const std::vector& objects) const +{ + vector < App::DocumentObject* > ret; + ret.reserve(objects.size()); + // pairs of input and output degree + map < App::DocumentObject*, std::pair > countMap; + + for (auto objectIt : objects) { + //we need inlist with unique entries + auto in = objectIt->getInList(); + std::sort(in.begin(), in.end()); + in.erase(std::unique(in.begin(), in.end()), in.end()); + + //we need outlist with unique entries + auto out = objectIt->getOutList(); + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); + + countMap[objectIt] = std::make_pair(in.size(), out.size()); + } + + std::list degIn; + std::list degOut; + + bool removeVertex = true; + while (removeVertex) { + removeVertex = false; + + // try input degree + auto degInIt = find_if(countMap.begin(), countMap.end(), + [](pair< App::DocumentObject*, pair > vertex)->bool { + return vertex.second.first == 0; + }); + + if (degInIt != countMap.end()) { + removeVertex = true; + degIn.push_back(degInIt->first); + degInIt->second.first = degInIt->second.first - 1; + + //we need outlist with unique entries + auto out = degInIt->first->getOutList(); + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); + + for (auto outListIt : out) { + auto outListMapIt = countMap.find(outListIt); + outListMapIt->second.first = outListMapIt->second.first - 1; + } + } + } + + // make the output degree negative if input degree is negative + // to mark the vertex as processed + for (auto& countIt : countMap) { + if (countIt.second.first < 0) { + countIt.second.second = -1; + } + } + + removeVertex = degIn.size() != objects.size(); + while (removeVertex) { + removeVertex = false; + + auto degOutIt = find_if(countMap.begin(), countMap.end(), + [](pair< App::DocumentObject*, pair > vertex)->bool { + return vertex.second.second == 0; + }); + + if (degOutIt != countMap.end()) { + removeVertex = true; + degOut.push_front(degOutIt->first); + degOutIt->second.second = degOutIt->second.second - 1; + + //we need inlist with unique entries + auto in = degOutIt->first->getInList(); + std::sort(in.begin(), in.end()); + in.erase(std::unique(in.begin(), in.end()), in.end()); + + for (auto inListIt : in) { + auto inListMapIt = countMap.find(inListIt); + inListMapIt->second.second = inListMapIt->second.second - 1; + } + } + } + + // at this point we have no root object any more + for (auto countIt : countMap) { + if (countIt.second.first > 0 && countIt.second.second > 0) { + degIn.push_back(countIt.first); + } + } + + ret.insert(ret.end(), degIn.begin(), degIn.end()); + ret.insert(ret.end(), degOut.begin(), degOut.end()); + + return ret; +} + std::vector DocumentP::topologicalSort(const std::vector& objects) const { // topological sort algorithm described here: