From f6c02b84299c42f377c3911d09ed3daea63bb6f6 Mon Sep 17 00:00:00 2001 From: Daniel Krebs Date: Tue, 30 Jan 2018 15:08:28 +0100 Subject: [PATCH] lib: add directed graph implementation incl. unittest --- fpga/include/villas/directed_graph.hpp | 201 +++++++++++++++++++++++++ fpga/tests/CMakeLists.txt | 1 + fpga/tests/graph.cpp | 32 ++++ fpga/tests/main.cpp | 11 ++ 4 files changed, 245 insertions(+) create mode 100644 fpga/include/villas/directed_graph.hpp create mode 100644 fpga/tests/graph.cpp diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp new file mode 100644 index 000000000..71716d49f --- /dev/null +++ b/fpga/include/villas/directed_graph.hpp @@ -0,0 +1,201 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "log.hpp" + + +namespace villas { +namespace graph { + +// use vector indices as identifiers +using VertexIdentifier = std::size_t; +using EdgeIdentifier = std::size_t; + +// forward declarations +class Edge; +class Vertex; + + +class Vertex { + template + friend class DirectedGraph; + +public: + bool + operator==(const Vertex& other) + { return this->id == other.id; } + +private: + VertexIdentifier id; + std::list edges; +}; + + +class Edge { + template + friend class DirectedGraph; + +public: + bool + operator==(const Edge& other) + { return this->id == other.id; } + +private: + EdgeIdentifier id; + VertexIdentifier from; + VertexIdentifier to; +}; + + +template +class DirectedGraph { +public: + + DirectedGraph(const std::string& name = "DirectedGraph") : + lastVertexId(0), lastEdgeId(0) + { + logger = loggerGetOrCreate(name); + } + + std::shared_ptr getVertex(VertexIdentifier vertexId) const + { + if(vertexId < 0 or vertexId >= lastVertexId) + throw std::invalid_argument("vertex doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return vertices.at(vertexId); + } + + std::shared_ptr getEdge(EdgeIdentifier edgeId) const + { + if(edgeId < 0 or edgeId >= lastEdgeId) + throw std::invalid_argument("edge doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return edges.at(edgeId); + } + + std::size_t getEdgeCount() const + { return edges.size(); } + + std::size_t getVertexCount() const + { return vertices.size(); } + + VertexIdentifier addVertex(std::shared_ptr vertex) + { + vertex->id = lastVertexId++; + + logger->debug("New vertex: {}", vertex->id); + vertices[vertex->id] = vertex; + + return vertex->id; + } + + EdgeIdentifier addEdge(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId) + { + std::shared_ptr edge(new EdgeType); + edge->id = lastEdgeId++; + + logger->debug("New edge {}: {} -> {}", edge->id, fromVertexId, toVertexId); + + // connect it + edge->from = fromVertexId; + edge->to = toVertexId; + + // this is a directed graph, so only push edge to starting vertex + getVertex(fromVertexId)->edges.push_back(edge->id); + + // add new edge to graph + edges[edge->id] = edge; + + return edge->id; + } + + void removeEdge(EdgeIdentifier edgeId) + { + auto edge = getEdge(edgeId); + auto startVertex = getVertex(edge->from); + + // remove edge only from starting vertex (this is a directed graph) + logger->debug("Remove edge {} from vertex {}", edgeId, edge->from); + startVertex->edges.remove(edgeId); + + logger->debug("Remove edge {}", edgeId); + edges.erase(edgeId); + } + + void removeVertex(VertexIdentifier vertexId) + { + // delete every edge that start or ends at this vertex + auto it = edges.begin(); + while(it != edges.end()) { + auto& [edgeId, edge] = *it; + bool removeEdge = false; + + if(edge->to == vertexId) { + logger->debug("Remove edge {} from vertex {}'s edge list", + edgeId, edge->from); + + removeEdge = true; + + auto startVertex = getVertex(edge->from); + startVertex->edges.remove(edge->id); + } + + if((edge->from == vertexId) or removeEdge) { + logger->debug("Remove edge {}", edgeId); + // remove edge from global edge list + it = edges.erase(it); + } else { + ++it; + } + } + + logger->debug("Remove vertex {}", vertexId); + vertices.erase(vertexId); + } + + const std::list& + vertexGetEdges(VertexIdentifier vertexId) const + { return getVertex(vertexId)->edges; } + + void dump() + { + logger->info("Vertices:"); + for(auto& [vertexId, vertex] : vertices) { + // format connected vertices into a list + std::stringstream ssEdges; + for(auto& edge : vertex->edges) { + ssEdges << getEdge(edge)->to << " "; + } + + logger->info(" {} connected to: {}", vertexId, ssEdges.str()); + } + + logger->info("Edges:"); + for(auto& [edgeId, edge] : edges) { + logger->info(" {}: {} -> {}", edgeId, edge->from, edge->to); + } + } + +private: + VertexIdentifier lastVertexId; + EdgeIdentifier lastEdgeId; + + std::map> vertices; + std::map> edges; + + SpdLogger logger; +}; + +} // namespacae graph +} // namespace villas diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt index bb9988ba2..edd6fdd3e 100644 --- a/fpga/tests/CMakeLists.txt +++ b/fpga/tests/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES # rtds_rtt.c timer.cpp # xsg.c + graph.cpp ) add_executable(unit-tests ${SOURCES}) diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp new file mode 100644 index 000000000..92f81cc74 --- /dev/null +++ b/fpga/tests/graph.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include + +Test(graph, directed, .description = "DirectedGraph") +{ + villas::graph::DirectedGraph<> g; + + std::shared_ptr v1(new villas::graph::Vertex); + std::shared_ptr v2(new villas::graph::Vertex); + std::shared_ptr v3(new villas::graph::Vertex); + + auto v1id = g.addVertex(v1); + auto v2id = g.addVertex(v2); + auto v3id = g.addVertex(v3); + cr_assert(g.getVertexCount() == 3); + + g.addEdge(v1id, v2id); + g.addEdge(v3id, v2id); + g.addEdge(v1id, v3id); + g.addEdge(v2id, v1id); + cr_assert(g.getEdgeCount() == 4); + cr_assert(g.vertexGetEdges(v1id).size() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 1); + cr_assert(g.vertexGetEdges(v3id).size() == 1); + + g.removeVertex(v1id); + g.dump(); + cr_assert(g.getVertexCount() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 0); +} diff --git a/fpga/tests/main.cpp b/fpga/tests/main.cpp index 680af8ab6..5f83151e3 100644 --- a/fpga/tests/main.cpp +++ b/fpga/tests/main.cpp @@ -111,3 +111,14 @@ TestSuite(fpga, .fini = fini, .description = "VILLASfpga" ); + +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} + +TestSuite(graph, + .init = init_graph, + .description = "Graph library" +);