From 026f8804d5ec152342fe31f7eda751357e46aadc Mon Sep 17 00:00:00 2001 From: Nolan Kramer Date: Tue, 23 May 2023 23:23:49 -0700 Subject: [PATCH 1/3] Add transitive reduction algorithm --- include/Graph/Graph.hpp | 81 +++++++++++++++++++++++++++++ test/TransitiveReductionTest.cpp | 87 ++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 test/TransitiveReductionTest.cpp diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index dd36feb5c..7ad198a2b 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -156,6 +156,16 @@ class Graph { * */ virtual void removeEdge(const unsigned long long edgeId); + /** + * \brief + * Finds the given edge defined by v1 and v2 within the graph. + * + * @param v1 The first vertex. + * @param v2 The second vertex. + * @param id The edge id if the edge is found. Otherwise set to 0. + * @return True if the edge exists in the graph. + */ + virtual bool findEdge(const Node* v1, const Node* v2, unsigned long long &id) const; /** * \brief * Function that return the Node Set of the Graph @@ -262,6 +272,15 @@ class Graph { */ virtual const BellmanFordResult bellmanford(const Node &source, const Node &target) const; + /** + * @brief This function computes the transitive reduction of the graph, returning a graph + * with the property of transitive closure satisfied. It removes the "short-circuit" + * paths from a graph, leaving only the longest paths. Commonly used to remove duplicate edges + * among nodes that do not pass through the entire graph. + * @return A copy of the current graph with the transitive closure property satisfied. + * + */ + virtual const Graph transitiveReduction() const; /** * @brief Function runs the floyd-warshall algorithm and returns the shortest * distance of all pair of nodes. It can also detect if a negative cycle @@ -642,6 +661,29 @@ void Graph::removeEdge(const unsigned long long edgeId) { } } +template +bool Graph::findEdge(const Node* v1, const Node* v2, unsigned long long &id) const { + + // This could be made faster by looking for the edge hash, assuming we hash based on + // node data, instead of a unique integer + for(const Edge* e : this->edgeSet) + { + if (e->getNodePair().first == v1 && e->getNodePair().second == v2) + { + id = e->getId(); + return true; + } + if (!e->isDirected() && (e->getNodePair().second == v1 && e->getNodePair().first == v2)) + { + id = e->getId(); + return true; + } + } + + id = 0; + return false; +} + template const std::set *> Graph::getNodeSet() const { std::set *> nodeSet; @@ -1356,6 +1398,45 @@ const BellmanFordResult Graph::bellmanford(const Node &source, return result; } +/* + * See Harry Hsu. "An algorithm for finding a minimal equivalent graph of a digraph.", + * Journal of the ACM, 22(1):11-16, January 1975 + * + * foreach x in graph.vertices + * foreach y in graph.vertices + * foreach z in graph.vertices + * delete edge xz if edges xy and yz exist +*/ +template +const Graph Graph::transitiveReduction() const +{ + Graph result(this->edgeSet); + + unsigned long long edgeId = 0; + std::set *> nodes = this->getNodeSet(); + for(const Node* x : nodes) + { + for(const Node* y : nodes) + { + if (this->findEdge(x, y, edgeId)) + { + for(const Node* z : nodes) + { + if (this->findEdge(y, z, edgeId)) + { + if (this->findEdge(x, z, edgeId)) + { + result.removeEdge(edgeId); + } + } + } + } + } + } + + return result; +} + template const FWResult Graph::floydWarshall() const { FWResult result; diff --git a/test/TransitiveReductionTest.cpp b/test/TransitiveReductionTest.cpp new file mode 100644 index 000000000..570d43acc --- /dev/null +++ b/test/TransitiveReductionTest.cpp @@ -0,0 +1,87 @@ +#include "CXXGraph.hpp" +#include "gtest/gtest.h" + +TEST(TransitiveReductionTest, reduceEmpty) { + CXXGraph::T_EdgeSet edgeSet; + CXXGraph::Graph graph(edgeSet); + CXXGraph::Graph result = graph.transitiveReduction(); + + ASSERT_EQ(result.getEdgeSet().size(), 0); +} + +TEST(TransitiveReductionTest, reduceReducedDAG) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + + CXXGraph::DirectedEdge edge1(1, node1, node2); + CXXGraph::DirectedEdge edge2(2, node1, node3); + CXXGraph::DirectedEdge edge3(3, node2, node4); + CXXGraph::DirectedEdge edge4(4, node3, node4); + CXXGraph::DirectedEdge edge5(5, node4, node5); + CXXGraph::DirectedEdge edge6(6, node4, node6); + CXXGraph::DirectedEdge edge7(7, node4, node7); + + CXXGraph::T_EdgeSet edgeSet; + edgeSet.insert(&edge1); + edgeSet.insert(&edge2); + edgeSet.insert(&edge3); + edgeSet.insert(&edge4); + edgeSet.insert(&edge5); + edgeSet.insert(&edge6); + edgeSet.insert(&edge7); + + CXXGraph::Graph graph(edgeSet); + CXXGraph::Graph result = graph.transitiveReduction(); + + ASSERT_EQ(result.getEdgeSet(), graph.getEdgeSet()); +} + +TEST(TransitiveReductionTest, reduceDAG) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + + CXXGraph::DirectedEdge edge1(1, node1, node2); + CXXGraph::DirectedEdge edge2(2, node1, node3); + CXXGraph::DirectedEdge edge3(3, node2, node4); + CXXGraph::DirectedEdge edge4(4, node3, node4); + CXXGraph::DirectedEdge edge5(5, node4, node5); + CXXGraph::DirectedEdge edge6(6, node4, node6); + CXXGraph::DirectedEdge edge7(7, node4, node7); + + CXXGraph::T_EdgeSet reducedEdgeSet; + reducedEdgeSet.insert(&edge1); + reducedEdgeSet.insert(&edge2); + reducedEdgeSet.insert(&edge3); + reducedEdgeSet.insert(&edge4); + reducedEdgeSet.insert(&edge5); + reducedEdgeSet.insert(&edge6); + reducedEdgeSet.insert(&edge7); + CXXGraph::Graph reducedGraph(reducedEdgeSet); + + // Add some more edges that can be reduced + CXXGraph::T_EdgeSet edgeSet(reducedEdgeSet); + CXXGraph::DirectedEdge edge8(8, node2, node5); + CXXGraph::DirectedEdge edge9(9, node1, node4); + CXXGraph::DirectedEdge edge10(10, node1, node7); + CXXGraph::DirectedEdge edge11(11, node3, node6); + edgeSet.insert(&edge8); + edgeSet.insert(&edge9); + edgeSet.insert(&edge10); + edgeSet.insert(&edge11); + CXXGraph::Graph graph(edgeSet); + + // Perform reduction + CXXGraph::Graph result = graph.transitiveReduction(); + + ASSERT_EQ(reducedGraph.getEdgeSet(), result.getEdgeSet()); +} \ No newline at end of file From 17e0817a7c0761ec41dba827d9c2c242f823b8b0 Mon Sep 17 00:00:00 2001 From: Nolan Kramer Date: Tue, 23 May 2023 23:30:17 -0700 Subject: [PATCH 2/3] clang formatting --- include/Graph/Graph.hpp | 93 +++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index 7ad198a2b..5b2e24a7a 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -159,13 +159,14 @@ class Graph { /** * \brief * Finds the given edge defined by v1 and v2 within the graph. - * + * * @param v1 The first vertex. * @param v2 The second vertex. * @param id The edge id if the edge is found. Otherwise set to 0. * @return True if the edge exists in the graph. - */ - virtual bool findEdge(const Node* v1, const Node* v2, unsigned long long &id) const; + */ + virtual bool findEdge(const Node *v1, const Node *v2, + unsigned long long &id) const; /** * \brief * Function that return the Node Set of the Graph @@ -273,14 +274,16 @@ class Graph { virtual const BellmanFordResult bellmanford(const Node &source, const Node &target) const; /** - * @brief This function computes the transitive reduction of the graph, returning a graph - * with the property of transitive closure satisfied. It removes the "short-circuit" - * paths from a graph, leaving only the longest paths. Commonly used to remove duplicate edges - * among nodes that do not pass through the entire graph. - * @return A copy of the current graph with the transitive closure property satisfied. - * - */ - virtual const Graph transitiveReduction() const; + * @brief This function computes the transitive reduction of the graph, + * returning a graph with the property of transitive closure satisfied. It + * removes the "short-circuit" paths from a graph, leaving only the longest + * paths. Commonly used to remove duplicate edges among nodes that do not pass + * through the entire graph. + * @return A copy of the current graph with the transitive closure property + * satisfied. + * + */ + virtual const Graph transitiveReduction() const; /** * @brief Function runs the floyd-warshall algorithm and returns the shortest * distance of all pair of nodes. It can also detect if a negative cycle @@ -662,19 +665,17 @@ void Graph::removeEdge(const unsigned long long edgeId) { } template -bool Graph::findEdge(const Node* v1, const Node* v2, unsigned long long &id) const { - - // This could be made faster by looking for the edge hash, assuming we hash based on - // node data, instead of a unique integer - for(const Edge* e : this->edgeSet) - { - if (e->getNodePair().first == v1 && e->getNodePair().second == v2) - { +bool Graph::findEdge(const Node *v1, const Node *v2, + unsigned long long &id) const { + // This could be made faster by looking for the edge hash, assuming we hash + // based on node data, instead of a unique integer + for (const Edge *e : this->edgeSet) { + if (e->getNodePair().first == v1 && e->getNodePair().second == v2) { id = e->getId(); return true; } - if (!e->isDirected() && (e->getNodePair().second == v1 && e->getNodePair().first == v2)) - { + if (!e->isDirected() && + (e->getNodePair().second == v1 && e->getNodePair().first == v2)) { id = e->getId(); return true; } @@ -713,9 +714,9 @@ const std::set *> Graph::getNodeSet() const { template void Graph::setNodeData(const std::string &nodeUserId, T data) { auto nodeset = this->nodeSet(); - auto nodeIt = std::find_if(nodeset.begin(), nodeset.end(), [&nodeUserId](auto node){ - return node->getUserId() == nodeUserId; - }); + auto nodeIt = std::find_if( + nodeset.begin(), nodeset.end(), + [&nodeUserId](auto node) { return node->getUserId() == nodeUserId; }); (*nodeIt)->setData(std::move(data)); } @@ -1399,33 +1400,26 @@ const BellmanFordResult Graph::bellmanford(const Node &source, } /* - * See Harry Hsu. "An algorithm for finding a minimal equivalent graph of a digraph.", - * Journal of the ACM, 22(1):11-16, January 1975 - * + * See Harry Hsu. "An algorithm for finding a minimal equivalent graph of a + * digraph.", Journal of the ACM, 22(1):11-16, January 1975 + * * foreach x in graph.vertices * foreach y in graph.vertices * foreach z in graph.vertices * delete edge xz if edges xy and yz exist -*/ + */ template -const Graph Graph::transitiveReduction() const -{ +const Graph Graph::transitiveReduction() const { Graph result(this->edgeSet); unsigned long long edgeId = 0; std::set *> nodes = this->getNodeSet(); - for(const Node* x : nodes) - { - for(const Node* y : nodes) - { - if (this->findEdge(x, y, edgeId)) - { - for(const Node* z : nodes) - { - if (this->findEdge(y, z, edgeId)) - { - if (this->findEdge(x, z, edgeId)) - { + for (const Node *x : nodes) { + for (const Node *y : nodes) { + if (this->findEdge(x, y, edgeId)) { + for (const Node *z : nodes) { + if (this->findEdge(y, z, edgeId)) { + if (this->findEdge(x, z, edgeId)) { result.removeEdge(edgeId); } } @@ -1433,7 +1427,7 @@ const Graph Graph::transitiveReduction() const } } } - + return result; } @@ -2991,9 +2985,9 @@ int Graph::writeToMTXFile(const std::string &workingDir, std::string header = "%%MatrixMarket graph"; // Check if the adjacency matrix is symmetric, i.e., if all the edges are // undirected - bool symmetric = !std::any_of(edgeSet.begin(), edgeSet.end(), [](auto edge){ - return (edge->isDirected().has_value() && edge->isDirected().value()); - }); + bool symmetric = !std::any_of(edgeSet.begin(), edgeSet.end(), [](auto edge) { + return (edge->isDirected().has_value() && edge->isDirected().value()); + }); // Write in the header whether the adj matrix is symmetric or not if (symmetric) { header += " symmetric\n"; @@ -3080,7 +3074,7 @@ int Graph::readFromMTXFile(const std::string &workingDir, // Since the matrix represents the adjacency matrix, it must be square if (n_rows != n_cols) { - return -1; + return -1; } // Read the content of each line @@ -3110,8 +3104,9 @@ int Graph::readFromMTXFile(const std::string &workingDir, } if (n_edges != edgeMap.size()) { - std::cout << "Error: The number of edges does not match the value provided in the size line.\n"; - return -1; + std::cout << "Error: The number of edges does not match the value provided " + "in the size line.\n"; + return -1; } iFile.close(); From 71011e38ec5ae85d4d3294bcb6afe30d163de7f6 Mon Sep 17 00:00:00 2001 From: Nolan Kramer Date: Wed, 24 May 2023 22:18:39 -0700 Subject: [PATCH 3/3] Fix a few violations --- include/Graph/Graph.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index 5b2e24a7a..61899aa03 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -670,12 +670,12 @@ bool Graph::findEdge(const Node *v1, const Node *v2, // This could be made faster by looking for the edge hash, assuming we hash // based on node data, instead of a unique integer for (const Edge *e : this->edgeSet) { - if (e->getNodePair().first == v1 && e->getNodePair().second == v2) { + if ((e->getNodePair().first == v1) && (e->getNodePair().second == v2)) { id = e->getId(); return true; } if (!e->isDirected() && - (e->getNodePair().second == v1 && e->getNodePair().first == v2)) { + ((e->getNodePair().second == v1) && (e->getNodePair().first == v2))) { id = e->getId(); return true; }