Skip to content
Merged
156 changes: 147 additions & 9 deletions include/Graph/Graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <limits.h>

#include <atomic>
#include <cassert>
#include <cmath>
#include <condition_variable>
#include <cstring>
Expand Down Expand Up @@ -531,6 +532,9 @@ class Graph {
const std::string &OFileName,
const std::string &graphName) const;

virtual int writeToMTXFile(const std::string &workingDir,
const std::string &OFileName, char delimier) const;

/**
* \brief
* This function reads the graph from an input file
Expand All @@ -554,6 +558,9 @@ class Graph {
virtual int readFromDotFile(const std::string &workingDir,
const std::string &fileName);

virtual int readFromMTXFile(const std::string &workingDir,
const std::string &fileName);

/**
* \brief
* This function partition a graph in a set of partitions
Expand Down Expand Up @@ -664,19 +671,19 @@ const std::set<const Node<T> *> Graph<T>::getNodeSet() const {

template <typename T>
void Graph<T>::setNodeData(const std::string &nodeUserId, T data) {
for(auto &nodeSetIt : this->nodeSet()) {
if (nodeSetIt->getUserId() == nodeUserId) {
nodeSetIt->setData(std::move(data));
break;
}
for (auto &nodeSetIt : this->nodeSet()) {
if (nodeSetIt->getUserId() == nodeUserId) {
nodeSetIt->setData(std::move(data));
break;
}
}
}

template <typename T>
void Graph<T>::setNodeData(std::map<std::string, T> &dataMap) {
// Construct the set of all the nodes in the graph
for(auto &nodeSetIt : this->nodeSet()) {
nodeSetIt->setData(std::move(dataMap[nodeSetIt->getUserId()]));
for (auto &nodeSetIt : this->nodeSet()) {
nodeSetIt->setData(std::move(dataMap[nodeSetIt->getUserId()]));
}
}

Expand Down Expand Up @@ -939,8 +946,7 @@ int Graph<T>::readFromDot(const std::string &workingDir,
}
iFile.close();

recreateGraph(edgeMap, edgeDirectedMap, nodeFeatMap,
edgeWeightMap);
recreateGraph(edgeMap, edgeDirectedMap, nodeFeatMap, edgeWeightMap);
return 0;
}

Expand Down Expand Up @@ -2893,12 +2899,144 @@ int Graph<T>::writeToDotFile(const std::string &workingDir,
return writeToDot(workingDir, OFileName, graphName);
}

template <typename T>
int Graph<T>::writeToMTXFile(const std::string &workingDir,
const std::string &OFileName,
char delimitier) const {
// Get the full path and open the file
const std::string completePathToFileGraph =
workingDir + '/' + OFileName + ".mtx";
std::ofstream iFile(completePathToFileGraph);

// Write the header of the file
std::string header = "%%MatrixMarket graph";
// Check if the adjacency matrix is symmetric, i.e., if all the edges are
// undirected
bool symmetric = true;
for (const auto &edgeIt : edgeSet) {
if (edgeIt->isDirected().has_value() && edgeIt->isDirected().value()) {
symmetric = false;
break;
}
}
// Write in the header whether the adj matrix is symmetric or not
if (symmetric) {
header += " symmetric\n";
} else {
header += '\n';
}
iFile << header;

// Write the line containing the number of nodes and edges
const std::string firstLine =
std::to_string(getNodeSet().size()) + delimitier +
std::to_string(getNodeSet().size()) + delimitier +
std::to_string(getEdgeSet().size()) + '\n';
iFile << firstLine;

// Construct the edges
for (const auto &edgeIt : edgeSet) {
std::string line;
line += edgeIt->getNodePair().first->getUserId() + delimitier;
line += edgeIt->getNodePair().second->getUserId() + delimitier;
if (edgeIt->isWeighted().has_value() && edgeIt->isWeighted().value()) {
line += std::to_string(edgeIt->isWeighted().value()) + '\n';
} else {
line += std::to_string(1.) + '\n';
}
iFile << line;
}

iFile.close();
return 0;
}

template <typename T>
int Graph<T>::readFromDotFile(const std::string &workingDir,
const std::string &fileName) {
return readFromDot(workingDir, fileName);
}

template <typename T>
int Graph<T>::readFromMTXFile(const std::string &workingDir,
const std::string &fileName) {
// Define the edge maps
std::unordered_map<unsigned long long, std::pair<std::string, std::string>>
edgeMap;
std::unordered_map<std::string, T> nodeFeatMap;
std::unordered_map<unsigned long long, bool> edgeDirectedMap;
std::unordered_map<unsigned long long, double> edgeWeightMap;

// Get full path to the file and open it
const std::string completePathToFileGraph =
workingDir + '/' + fileName + ".mtx";
std::ifstream iFile(completePathToFileGraph);
// Check that the file is open
if (!iFile.is_open()) {
return -1;
}

// Define the number of columns and rows in the matrix
int n_cols, n_rows;
int n_edges;
bool undirected = false;

// From the first line of the file read the number of rows, columns and edges
std::string row_content;
getline(iFile, row_content);
if (row_content.find("symmetric") != std::string::npos) {
undirected = true;
}

// Get rid of any commented lines between the header and the size line
while (row_content.find('%') != std::string::npos) {
getline(iFile, row_content);
}

// From the size line of the file read the number of rows, columns and edges
std::stringstream row_stream(row_content);
std::string value;
getline(row_stream, value, ' ');
n_rows = std::stoi(value);
getline(row_stream, value, ' ');
n_cols = std::stoi(value);
getline(row_stream, value, ' ');
n_edges = std::stoi(value);

// Since the matrix represents the adjacency matrix, it must be square
assert(n_rows == n_cols);

// Read the content of each line
std::string node1;
std::string node2;
std::string edge_weight;
unsigned long long edge_id = 0;
while (getline(iFile, row_content)) {
std::stringstream row_stream(row_content);

// Read the content of the node ids and the weight into strings
getline(row_stream, node1, ' ');
getline(row_stream, node2, ' ');
getline(row_stream, edge_weight);

edgeMap[edge_id] = std::pair<std::string, std::string>(node1, node2);
edgeWeightMap[edge_id] = std::stod(edge_weight);
edgeDirectedMap[edge_id] = !undirected;

// If the edge is a self-link, it must be undirected
if (node1 == node2) {
edgeDirectedMap[edge_id] = false;
}

// Increase the edge id
++edge_id;
}

iFile.close();
recreateGraph(edgeMap, edgeDirectedMap, nodeFeatMap, edgeWeightMap);
return 0;
}

template <typename T>
PartitionMap<T> Graph<T>::partitionGraph(
const Partitioning::PartitionAlgorithm algorithm,
Expand Down
148 changes: 148 additions & 0 deletions test/MTXTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include <Edge/Edge.hpp>
#include <Edge/Weighted.hpp>
#include <Node/Node.hpp>

#include "CXXGraph.hpp"
#include "gtest/gtest.h"

TEST(MTXTest, WriteToMtxDirectedWeighted) {
// Generate a simple test graph with few nodes and edges
CXXGraph::Node<int> node1("1", 1);
CXXGraph::Node<int> node2("2", 2);
CXXGraph::Node<int> node3("3", 3);
std::pair<const CXXGraph::Node<int> *, const CXXGraph::Node<int> *> pairNode(
&node1, &node2);
CXXGraph::DirectedWeightedEdge<int> edge1(1, pairNode, 5);
CXXGraph::DirectedWeightedEdge<int> edge2(2, node2, node3, 3);
CXXGraph::DirectedWeightedEdge<int> edge3(3, node3, node1, 7);
CXXGraph::T_EdgeSet<int> edgeSet;
edgeSet.insert(&edge1);
edgeSet.insert(&edge2);
edgeSet.insert(&edge3);
CXXGraph::Graph<int> graph(edgeSet);

// Write the graph to a DOT file
graph.writeToMTXFile(".", "example_graph_directed_weighted", ' ');
}

TEST(MTXTest, WriteToMtxUndirected) {
// Generate a simple test graph with few nodes and edges
CXXGraph::Node<int> node1("1", 1);
CXXGraph::Node<int> node2("2", 2);
CXXGraph::Node<int> node3("3", 3);
std::pair<const CXXGraph::Node<int> *, const CXXGraph::Node<int> *> pairNode(
&node1, &node2);
CXXGraph::UndirectedEdge<int> edge1(1, pairNode);
CXXGraph::UndirectedEdge<int> edge2(2, node2, node3);
CXXGraph::UndirectedEdge<int> edge3(3, node3, node1);
CXXGraph::T_EdgeSet<int> edgeSet;
edgeSet.insert(&edge1);
edgeSet.insert(&edge2);
edgeSet.insert(&edge3);
CXXGraph::Graph<int> graph(edgeSet);

// Write the graph to a DOT file
graph.writeToMTXFile(".", "example_graph_undirected", ' ');
}

TEST(MTXTest, WriteToMtxMixed) {
CXXGraph::Node<int> node1("1", 1);
CXXGraph::Node<int> node2("2", 2);
CXXGraph::Node<int> node3("3", 3);

CXXGraph::DirectedWeightedEdge<int> edge1(1, node1, node2, 1);
CXXGraph::UndirectedWeightedEdge<int> edge2(2, node2, node3, 1);
CXXGraph::DirectedEdge<int> edge3(3, node1, node3);

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

CXXGraph::Graph<int> graph(edgeSet);
graph.writeToMTXFile(".", "example_graph_mixed", ' ');
}

TEST(MTXTest, ReadFromMtxDirectedWeighted) {
CXXGraph::Graph<int> graph;
graph.readFromMTXFile("..", "test_mtx");

// Check the number of edges and nodes
ASSERT_EQ(graph.getEdgeSet().size(), 8);
ASSERT_EQ(graph.getNodeSet().size(), 5);

// Check the first edge
ASSERT_EQ(graph.getEdge(0).value()->getNodePair().first->getUserId(), "1");
ASSERT_EQ(graph.getEdge(0).value()->getNodePair().second->getUserId(), "1");
// Check that it is directed
ASSERT_EQ(graph.getEdge(0).value()->isDirected(), false);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(0).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(0).value())
->getWeight(),
1.);

// Check the last edge
ASSERT_EQ(graph.getEdge(7).value()->getNodePair().first->getUserId(), "5");
ASSERT_EQ(graph.getEdge(7).value()->getNodePair().second->getUserId(), "5");
// Check that it is directed
ASSERT_EQ(graph.getEdge(7).value()->isDirected(), false);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(7).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(7).value())
->getWeight(),
12.);

// Check an edge in the middle
ASSERT_EQ(graph.getEdge(3).value()->getNodePair().first->getUserId(), "1");
ASSERT_EQ(graph.getEdge(3).value()->getNodePair().second->getUserId(), "4");
// Check that it is directed
ASSERT_EQ(graph.getEdge(3).value()->isDirected(), true);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(3).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(3).value())
->getWeight(),
6.);
}

TEST(MTXTest, ReadFromMtxUndirectedWeighted) {
CXXGraph::Graph<int> graph;
graph.readFromMTXFile("..", "test_mtx_symmetric");

// Check the number of edges and nodes
ASSERT_EQ(graph.getEdgeSet().size(), 8);
ASSERT_EQ(graph.getNodeSet().size(), 5);

// Check the first edge
ASSERT_EQ(graph.getEdge(0).value()->getNodePair().first->getUserId(), "1");
ASSERT_EQ(graph.getEdge(0).value()->getNodePair().second->getUserId(), "1");
// Check that it is directed
ASSERT_EQ(graph.getEdge(0).value()->isDirected(), false);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(0).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(0).value())
->getWeight(),
1.);

// Check the last edge
ASSERT_EQ(graph.getEdge(7).value()->getNodePair().first->getUserId(), "5");
ASSERT_EQ(graph.getEdge(7).value()->getNodePair().second->getUserId(), "5");
// Check that it is directed
ASSERT_EQ(graph.getEdge(7).value()->isDirected(), false);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(7).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(7).value())
->getWeight(),
12.);

// Check an edge in the middle
ASSERT_EQ(graph.getEdge(3).value()->getNodePair().first->getUserId(), "1");
ASSERT_EQ(graph.getEdge(3).value()->getNodePair().second->getUserId(), "4");
// Check that it is directed
ASSERT_EQ(graph.getEdge(3).value()->isDirected(), false);
// Check that it's weighted and the value of the weight
ASSERT_EQ(graph.getEdge(3).value()->isWeighted(), true);
ASSERT_EQ(dynamic_cast<const CXXGraph::Weighted *>(graph.getEdge(3).value())
->getWeight(),
6.);
}
10 changes: 10 additions & 0 deletions test/test_mtx.mtx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
%%MatrixMarket graph
5 5 8
1 1 1.000e+00
2 2 1.050e+01
3 3 1.500e-02
1 4 6.000e+00
4 2 2.505e+02
4 4 -2.800e+02
4 5 3.332e+01
5 5 1.200e+01
13 changes: 13 additions & 0 deletions test/test_mtx_symmetric.mtx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%%MatrixMarket graph symmetric
%
%
%
5 5 8
1 1 1.000e+00
2 2 1.050e+01
3 3 1.500e-02
1 4 6.000e+00
4 2 2.505e+02
4 4 -2.800e+02
4 5 3.332e+01
5 5 1.200e+01