/** A directed graph. * * @file * @author Daniel Krebs * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC * @license GNU General Public License (version 3) * * VILLAScommon * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . *********************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace villas { namespace graph { template class DirectedGraph { public: using VertexIdentifier = Vertex::Identifier; using EdgeIdentifier = Edge::Identifier; using Path = std::list; DirectedGraph(const std::string& name = "DirectedGraph") : lastVertexId(0), lastEdgeId(0) { logger = logging.get(name); } std::shared_ptr getVertex(VertexIdentifier vertexId) const { if(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); } template VertexIdentifier findVertex(UnaryPredicate p) { for(auto& v : vertices) { auto& vertexId = v.first; auto& vertex = v.second; if(p(vertex)) { return vertexId; } } throw std::out_of_range("vertex not found"); } std::shared_ptr getEdge(EdgeIdentifier edgeId) const { if(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); vertices[vertex->id] = vertex; return vertex->id; } EdgeIdentifier addEdge(std::shared_ptr edge, VertexIdentifier fromVertexId, VertexIdentifier toVertexId) { // allocate edge id edge->id = lastEdgeId++; // connect it edge->from = fromVertexId; edge->to = toVertexId; logger->debug("New edge {}: {} -> {}", *edge, edge->from, edge->to); // this is a directed graph, so only push edge to starting vertex getVertex(edge->from)->edges.push_back(edge->id); // add new edge to graph edges[edge->id] = edge; return edge->id; } EdgeIdentifier addDefaultEdge(VertexIdentifier fromVertexId, VertexIdentifier toVertexId) { // create a new edge std::shared_ptr edge(new EdgeType); return addEdge(edge, fromVertexId, toVertexId); } 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 = it->first; auto& edge = it->second; 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; } using check_path_fn = std::function; static bool checkPath(const Path&) { return true; } bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId, Path& path, check_path_fn pathCheckFunc = checkPath) { if(fromVertexId == toVertexId) { // arrived at the destination return true; } else { auto fromVertex = getVertex(fromVertexId); for(auto& edgeId : fromVertex->edges) { auto edgeOfFromVertex = getEdge(edgeId); // loop detection bool loop = false; for(auto& edgeIdInPath : path) { auto edgeInPath = getEdge(edgeIdInPath); if(edgeInPath->from == edgeOfFromVertex->to) { loop = true; break; } } if(loop) { logger->debug("Loop detected via edge {}", edgeId); continue; } // remember the path we're investigating to detect loops path.push_back(edgeId); // recursive, depth-first search if(getPath(edgeOfFromVertex->to, toVertexId, path, pathCheckFunc) and pathCheckFunc(path)) { // path found, we're done return true; } else { // tear down path that didn't lead to the destination path.pop_back(); } } } return false; } void dump(const std::string& fileName = "") { logger->info("Vertices:"); for(auto& v : vertices) { auto& vertex = v.second; // format connected vertices into a list std::stringstream ssEdges; for(auto& edge : vertex->edges) { ssEdges << getEdge(edge)->to << " "; } logger->info(" {} connected to: {}", *vertex, ssEdges.str()); } std::fstream s(fileName, s.out | s.trunc); if(s.is_open()) { s << "digraph memgraph {" << std::endl; } logger->info("Edges:"); for(auto& e : edges) { auto& edge = e.second; logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); if(s.is_open()) { auto from = getVertex(edge->from); auto to = getVertex(edge->to); s << std::dec; s << " \"" << *from << "\" -> \"" << *to << "\"" << " [label=\"" << *edge << "\"];" << std::endl; } } if(s.is_open()) { s << "}" << std::endl; s.close(); } } protected: VertexIdentifier lastVertexId; EdgeIdentifier lastEdgeId; std::map> vertices; std::map> edges; Logger logger; }; } // namespacae graph } /* namespace villas */