diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index dd36feb5c..61899aa03 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -156,6 +156,17 @@ 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 +273,17 @@ 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 +664,27 @@ 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; @@ -671,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)); } @@ -1356,6 +1399,38 @@ 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; @@ -2910,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"; @@ -2999,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 @@ -3029,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(); 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