Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transitive reduction algorithm #304

Merged
merged 3 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Check warning

Code scanning / Cppcheck (reported by Codacy)

misra violation 802 with no text in the supplied rule-texts-file

misra violation 802 with no text in the supplied rule-texts-file
/**
* @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; });
ZigRazor marked this conversation as resolved.
Show resolved Hide resolved
(*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());
});
ZigRazor marked this conversation as resolved.
Show resolved Hide resolved
// 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;
ZigRazor marked this conversation as resolved.
Show resolved Hide resolved

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 15.5 rule

MISRA 15.5 rule
}

// 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;
ZigRazor marked this conversation as resolved.
Show resolved Hide resolved
}

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());
}