File indexing completed on 2024-05-19 05:42:16
0001 // ct_lvtqtc_alg_transitive_reduction.cpp -*-C++-*- 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <ct_lvtqtc_alg_transitive_reduction.h> 0021 0022 #include <ct_lvtqtc_edgecollection.h> 0023 #include <ct_lvtqtc_lakosentity.h> 0024 #include <ct_lvtqtc_lakosrelation.h> 0025 0026 #include <memory> 0027 0028 #include <QDebug> 0029 #include <QRunnable> 0030 0031 namespace { 0032 constexpr bool debugMode = false; 0033 // start to spit information on the command line. 0034 0035 QString S(const std::string& string) 0036 { 0037 return QString::fromStdString(string); 0038 } 0039 } // namespace 0040 0041 using namespace Codethink::lvtqtc; 0042 0043 class AlgorithmTransitiveReduction::RunnableThread : public QRunnable { 0044 // The ancient version of Qt in appimage (5.11) doesn't support creating a 0045 // QRunnable from a lambda so we have to write a lambda by hand here :( 0046 private: 0047 // PRIVATE DATA 0048 AlgorithmTransitiveReduction& d_parent; 0049 LakosEntity *d_entity; 0050 0051 public: 0052 RunnableThread(AlgorithmTransitiveReduction& parent, LakosEntity *entity): d_parent(parent), d_entity(entity) 0053 { 0054 setAutoDelete(true); 0055 } 0056 0057 ~RunnableThread() override = default; 0058 0059 // MUTATORS 0060 void run() override 0061 { 0062 d_parent.startLookingForRedundantEdgesOnNode(d_entity); 0063 } 0064 }; 0065 0066 void AlgorithmTransitiveReduction::setVertices(std::vector<LakosEntity *> entities) 0067 { 0068 d_entities = std::move(entities); 0069 } 0070 0071 bool AlgorithmTransitiveReduction::hasRun() const 0072 { 0073 return d_hasRun; 0074 } 0075 0076 bool AlgorithmTransitiveReduction::hasError() const 0077 { 0078 return d_hasError; 0079 } 0080 0081 QString AlgorithmTransitiveReduction::errorMessage() const 0082 { 0083 return d_errorMessage; 0084 } 0085 0086 std::unordered_map<LakosEntity *, std::vector<std::shared_ptr<EdgeCollection>>>& 0087 AlgorithmTransitiveReduction::redundantEdgesByNode() 0088 { 0089 return d_redundantEdgesByNode; 0090 } 0091 0092 long long AlgorithmTransitiveReduction::totalRunTime() const 0093 { 0094 return d_totalTime; 0095 } 0096 0097 void AlgorithmTransitiveReduction::reset() 0098 { 0099 d_hasRun = false; 0100 d_hasError = false; 0101 d_redundantEdgesByNode.clear(); 0102 d_redundantEdges.clear(); 0103 d_redundantEdgesCache.clear(); 0104 } 0105 0106 void AlgorithmTransitiveReduction::run() 0107 { 0108 if (d_entities.empty()) { 0109 return; 0110 } 0111 0112 reset(); 0113 0114 d_hasRun = true; 0115 d_totalTimer.start(); 0116 0117 auto cacheKey = std::uintptr_t{0}; 0118 for (auto *e : d_entities) { 0119 cacheKey += reinterpret_cast<std::uintptr_t>(e->internalNode()); 0120 } 0121 if (d_redundantEdgesCache.count(cacheKey) > 0) { 0122 for (auto const& entity : d_entities) { 0123 for (const std::shared_ptr<EdgeCollection>& maybeRedundant : entity->edgesCollection()) { 0124 auto& cache = d_redundantEdgesCache[cacheKey]; 0125 auto edge = 0126 std::tuple<lvtldr::LakosianNode *, lvtldr::LakosianNode *>{maybeRedundant->from()->internalNode(), 0127 maybeRedundant->to()->internalNode()}; 0128 auto it = std::find_if(cache.cbegin(), cache.cend(), [&edge](auto const& e) { 0129 return edge == e; 0130 }); 0131 if (it == cache.cend()) { 0132 continue; 0133 } 0134 0135 d_redundantEdgesByNode[entity].push_back(maybeRedundant); 0136 d_redundantEdges.insert(maybeRedundant); 0137 } 0138 } 0139 } else { 0140 for (LakosEntity *entity : d_entities) { 0141 // if the current entity has zero edges, it's impossible 0142 // that it has redundant edges. If it has just one edge, 0143 // the edge can't be redundant as there's no alternative 0144 // way to go to the node. 0145 const auto eSize = entity->edgesCollection().size(); 0146 if (eSize == 0 || eSize == 1) { 0147 if (debugMode) { 0148 qDebug() << "entity" << S(entity->name()) << "can't have redundant edges"; 0149 } 0150 continue; 0151 } 0152 0153 // if we have more than one edge, one of them could be a redundant 0154 // edge. so let's search. 0155 QRunnable *runnable = new RunnableThread(*this, entity); 0156 d_pool.start(runnable); 0157 } 0158 d_pool.waitForDone(); 0159 0160 // Save to cache 0161 for (auto const& e : d_redundantEdges) { 0162 d_redundantEdgesCache[cacheKey].push_back({e->from()->internalNode(), e->to()->internalNode()}); 0163 } 0164 } 0165 0166 for (const std::shared_ptr<EdgeCollection>& redundant : d_redundantEdges) { 0167 redundant->setRedundant(true); 0168 } 0169 0170 d_totalTime = d_totalTimer.elapsed(); 0171 if (debugMode) { 0172 qDebug() << "Transitive Reduction took" << d_totalTime; 0173 } 0174 } 0175 0176 void AlgorithmTransitiveReduction::startLookingForRedundantEdgesOnNode(LakosEntity *entity) 0177 { 0178 // here we iterate over all edges, twice. 0179 // the outer loop is where we will have the target entity, the 0180 // value we are looking for, the inner loop is the start of the 0181 // lookup. 0182 // the edgesCollection is necessarily the edges that are going from 0183 // the entity to other entities. 0184 if (debugMode) { 0185 qDebug() << "Starting to look for reduntant edges on node" << S(entity->name()); 0186 } 0187 0188 for (const std::shared_ptr<EdgeCollection>& maybeRedundant : entity->edgesCollection()) { 0189 if (debugMode) { 0190 qDebug() << "Testing to see if the edge " << S(entity->name()) << S(maybeRedundant->to()->name()) 0191 << "is redundant"; 0192 } 0193 for (const std::shared_ptr<EdgeCollection>& lookupStart : entity->edgesCollection()) { 0194 // ignore the lookupStart if it's the same edge as the `maybeRedundant`. 0195 if (maybeRedundant == lookupStart) { 0196 continue; 0197 } 0198 // This edge is already on the redundant set, ignore. 0199 { 0200 std::shared_lock<std::shared_mutex> guard(d_collectionSerialiser); 0201 if (d_redundantEdges.find(lookupStart) != std::end(d_redundantEdges)) { 0202 continue; 0203 } 0204 } 0205 0206 // We are at the start of the search of transitiveness for the edge 0207 // `maybeRedundant`. Setup the initial data here, and start to walk 0208 // the graph. If we find a cycle, abort. 0209 if (debugMode) { 0210 qDebug() << "Reseting visited"; 0211 } 0212 0213 std::unordered_set<LakosEntity *> visited; 0214 LakosEntity *maybeReduntantNode = maybeRedundant->to(); 0215 LakosEntity *currentToNode = lookupStart->to(); 0216 visited.insert(lookupStart->from()); 0217 const bool found = recursiveTransitiveSearch(maybeReduntantNode, currentToNode, visited); 0218 if (found) { 0219 if (debugMode) { 0220 qDebug() << "Found redundant edge between" << QString::fromStdString(entity->name()) << "and" 0221 << QString::fromStdString(maybeReduntantNode->name()); 0222 } 0223 0224 // An edge collection has 0 - N edges between the same two 0225 // nodes. It's usually 1 though. 0226 { 0227 std::unique_lock<std::shared_mutex> guard(d_collectionSerialiser); 0228 d_redundantEdgesByNode[entity].push_back(maybeRedundant); 0229 d_redundantEdges.insert(maybeRedundant); 0230 } 0231 } 0232 } 0233 } 0234 } 0235 0236 bool AlgorithmTransitiveReduction::recursiveTransitiveSearch(LakosEntity *target, 0237 LakosEntity *current, 0238 std::unordered_set<LakosEntity *>& visited) 0239 { 0240 if (target == current) { 0241 return true; 0242 } 0243 0244 // if it's already visited, we are on a loop, and we need to break away from it. 0245 if (visited.find(current) != std::end(visited)) { 0246 return false; 0247 } 0248 0249 if (debugMode) { 0250 qDebug() << "Adding" << S(current->name()) << "To visited"; 0251 } 0252 0253 visited.insert(current); 0254 for (const std::shared_ptr<EdgeCollection>& edge : current->edgesCollection()) { 0255 LakosEntity *nextNode = edge->to(); 0256 0257 // This edge is already on the redundant set, so just abort here. 0258 { 0259 std::shared_lock<std::shared_mutex> guard(d_collectionSerialiser); 0260 if (d_redundantEdges.find(edge) != std::end(d_redundantEdges)) { 0261 continue; 0262 } 0263 } 0264 0265 if (debugMode) { 0266 if (!nextNode) { 0267 qDebug() << "Entity" << S(current->name()) << " lacks a `to` node on the edge collection"; 0268 } else { 0269 qDebug() << "The next node from" << S(current->name()) << "is" << S(nextNode->name()); 0270 } 0271 } 0272 0273 const bool found = recursiveTransitiveSearch(target, nextNode, visited); 0274 if (found) { 0275 return true; 0276 } 0277 } 0278 0279 return false; 0280 }