Skip to content

Commit

Permalink
Add transitive reduction algorithm (#304)
Browse files Browse the repository at this point in the history
* Add transitive reduction algorithm

* clang formatting

* Fix a few violations
  • Loading branch information
nrkramer authored May 25, 2023
1 parent 922ff14 commit 09ec417
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 9 deletions.
94 changes: 85 additions & 9 deletions include/Graph/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> *v1, const Node<T> *v2,
unsigned long long &id) const;
/**
* \brief
* Function that return the Node Set of the Graph
Expand Down Expand Up @@ -262,6 +273,17 @@ class Graph {
*/
virtual const BellmanFordResult bellmanford(const Node<T> &source,
const Node<T> &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<T> 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
Expand Down Expand Up @@ -642,6 +664,27 @@ void Graph<T>::removeEdge(const unsigned long long edgeId) {
}
}

template <typename T>
bool Graph<T>::findEdge(const Node<T> *v1, const Node<T> *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<T> *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 <typename T>
const std::set<const Node<T> *> Graph<T>::getNodeSet() const {
std::set<const Node<T> *> nodeSet;
Expand Down Expand Up @@ -671,9 +714,9 @@ const std::set<const Node<T> *> Graph<T>::getNodeSet() const {
template <typename T>
void Graph<T>::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));
}

Expand Down Expand Up @@ -1356,6 +1399,38 @@ const BellmanFordResult Graph<T>::bellmanford(const Node<T> &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 <typename T>
const Graph<T> Graph<T>::transitiveReduction() const {
Graph<T> result(this->edgeSet);

unsigned long long edgeId = 0;
std::set<const Node<T> *> nodes = this->getNodeSet();
for (const Node<T> *x : nodes) {
for (const Node<T> *y : nodes) {
if (this->findEdge(x, y, edgeId)) {
for (const Node<T> *z : nodes) {
if (this->findEdge(y, z, edgeId)) {
if (this->findEdge(x, z, edgeId)) {
result.removeEdge(edgeId);
}
}
}
}
}
}

return result;
}

template <typename T>
const FWResult Graph<T>::floydWarshall() const {
FWResult result;
Expand Down Expand Up @@ -2910,9 +2985,9 @@ int Graph<T>::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";
Expand Down Expand Up @@ -2999,7 +3074,7 @@ int Graph<T>::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
Expand Down Expand Up @@ -3029,8 +3104,9 @@ int Graph<T>::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();
Expand Down
87 changes: 87 additions & 0 deletions test/TransitiveReductionTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "CXXGraph.hpp"
#include "gtest/gtest.h"

TEST(TransitiveReductionTest, reduceEmpty) {
CXXGraph::T_EdgeSet<int> edgeSet;
CXXGraph::Graph<int> graph(edgeSet);
CXXGraph::Graph<int> result = graph.transitiveReduction();

ASSERT_EQ(result.getEdgeSet().size(), 0);
}

TEST(TransitiveReductionTest, reduceReducedDAG) {
CXXGraph::Node<int> node1("1", 1);
CXXGraph::Node<int> node2("2", 2);
CXXGraph::Node<int> node3("3", 3);
CXXGraph::Node<int> node4("4", 4);
CXXGraph::Node<int> node5("5", 5);
CXXGraph::Node<int> node6("6", 6);
CXXGraph::Node<int> node7("7", 7);

CXXGraph::DirectedEdge<int> edge1(1, node1, node2);
CXXGraph::DirectedEdge<int> edge2(2, node1, node3);
CXXGraph::DirectedEdge<int> edge3(3, node2, node4);
CXXGraph::DirectedEdge<int> edge4(4, node3, node4);
CXXGraph::DirectedEdge<int> edge5(5, node4, node5);
CXXGraph::DirectedEdge<int> edge6(6, node4, node6);
CXXGraph::DirectedEdge<int> edge7(7, node4, node7);

CXXGraph::T_EdgeSet<int> edgeSet;
edgeSet.insert(&edge1);
edgeSet.insert(&edge2);
edgeSet.insert(&edge3);
edgeSet.insert(&edge4);
edgeSet.insert(&edge5);
edgeSet.insert(&edge6);
edgeSet.insert(&edge7);

CXXGraph::Graph<int> graph(edgeSet);
CXXGraph::Graph<int> result = graph.transitiveReduction();

ASSERT_EQ(result.getEdgeSet(), graph.getEdgeSet());
}

TEST(TransitiveReductionTest, reduceDAG) {
CXXGraph::Node<int> node1("1", 1);
CXXGraph::Node<int> node2("2", 2);
CXXGraph::Node<int> node3("3", 3);
CXXGraph::Node<int> node4("4", 4);
CXXGraph::Node<int> node5("5", 5);
CXXGraph::Node<int> node6("6", 6);
CXXGraph::Node<int> node7("7", 7);

CXXGraph::DirectedEdge<int> edge1(1, node1, node2);
CXXGraph::DirectedEdge<int> edge2(2, node1, node3);
CXXGraph::DirectedEdge<int> edge3(3, node2, node4);
CXXGraph::DirectedEdge<int> edge4(4, node3, node4);
CXXGraph::DirectedEdge<int> edge5(5, node4, node5);
CXXGraph::DirectedEdge<int> edge6(6, node4, node6);
CXXGraph::DirectedEdge<int> edge7(7, node4, node7);

CXXGraph::T_EdgeSet<int> reducedEdgeSet;
reducedEdgeSet.insert(&edge1);
reducedEdgeSet.insert(&edge2);
reducedEdgeSet.insert(&edge3);
reducedEdgeSet.insert(&edge4);
reducedEdgeSet.insert(&edge5);
reducedEdgeSet.insert(&edge6);
reducedEdgeSet.insert(&edge7);
CXXGraph::Graph<int> reducedGraph(reducedEdgeSet);

// Add some more edges that can be reduced
CXXGraph::T_EdgeSet<int> edgeSet(reducedEdgeSet);
CXXGraph::DirectedEdge<int> edge8(8, node2, node5);
CXXGraph::DirectedEdge<int> edge9(9, node1, node4);
CXXGraph::DirectedEdge<int> edge10(10, node1, node7);
CXXGraph::DirectedEdge<int> edge11(11, node3, node6);
edgeSet.insert(&edge8);
edgeSet.insert(&edge9);
edgeSet.insert(&edge10);
edgeSet.insert(&edge11);
CXXGraph::Graph<int> graph(edgeSet);

// Perform reduction
CXXGraph::Graph<int> result = graph.transitiveReduction();

ASSERT_EQ(reducedGraph.getEdgeSet(), result.getEdgeSet());
}

0 comments on commit 09ec417

Please sign in to comment.