From 27c67f206e059dbb564a36d5c7bf84639725d68c Mon Sep 17 00:00:00 2001
From: Daniel Krebs <github@daniel-krebs.net>
Date: Tue, 30 Jan 2018 17:28:13 +0100
Subject: [PATCH] lib/graph: add path-finding with loop detection and
 corresponding unittest

---
 fpga/include/villas/directed_graph.hpp | 45 ++++++++++++++++
 fpga/tests/graph.cpp                   | 71 +++++++++++++++++++++++++-
 2 files changed, 114 insertions(+), 2 deletions(-)

diff --git a/fpga/include/villas/directed_graph.hpp b/fpga/include/villas/directed_graph.hpp
index 71716d49f..d7752a9f0 100644
--- a/fpga/include/villas/directed_graph.hpp
+++ b/fpga/include/villas/directed_graph.hpp
@@ -6,6 +6,7 @@
 #include <sstream>
 #include <string>
 #include <stdexcept>
+#include <algorithm>
 
 #include "log.hpp"
 
@@ -168,6 +169,50 @@ public:
 	vertexGetEdges(VertexIdentifier vertexId) const
 	{ return getVertex(vertexId)->edges; }
 
+	bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId,
+	             std::list<EdgeIdentifier>& path)
+	{
+		if(fromVertexId == toVertexId) {
+			// arrived at the destination
+			return true;
+		} else {
+			auto fromVertex = getVertex(fromVertexId);
+
+			for(auto& edgeId : fromVertex->edges) {
+				auto edge = getEdge(edgeId);
+
+				// loop detection
+				bool loop = false;
+				for(auto& edgeIdInPath : path) {
+					auto edgeInPath = getEdge(edgeIdInPath);
+					if(edgeInPath->from == edgeId) {
+						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(edge->to, toVertexId, 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()
 	{
 		logger->info("Vertices:");
diff --git a/fpga/tests/graph.cpp b/fpga/tests/graph.cpp
index 92f81cc74..4eb25ac75 100644
--- a/fpga/tests/graph.cpp
+++ b/fpga/tests/graph.cpp
@@ -2,10 +2,14 @@
 
 #include <criterion/criterion.h>
 #include <villas/directed_graph.hpp>
+#include <villas/log.hpp>
 
-Test(graph, directed, .description = "DirectedGraph")
+Test(graph, basic, .description = "DirectedGraph")
 {
-	villas::graph::DirectedGraph<> g;
+	auto logger = loggerGetOrCreate("unittest:basic");
+	logger->info("Testing basic graph construction and modification");
+
+	villas::graph::DirectedGraph<> g("unittest:basic");
 
 	std::shared_ptr<villas::graph::Vertex> v1(new villas::graph::Vertex);
 	std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
@@ -30,3 +34,66 @@ Test(graph, directed, .description = "DirectedGraph")
 	cr_assert(g.getVertexCount() == 2);
 	cr_assert(g.vertexGetEdges(v2id).size() == 0);
 }
+
+Test(graph, path, .description = "Find path")
+{
+	auto logger = loggerGetOrCreate("unittest:path");
+	logger->info("Testing path finding algorithm");
+
+	villas::graph::DirectedGraph<> g("unittest:path");
+
+	std::shared_ptr<villas::graph::Vertex> v1(new villas::graph::Vertex);
+	std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
+	std::shared_ptr<villas::graph::Vertex> v3(new villas::graph::Vertex);
+	std::shared_ptr<villas::graph::Vertex> v4(new villas::graph::Vertex);
+	std::shared_ptr<villas::graph::Vertex> v5(new villas::graph::Vertex);
+	std::shared_ptr<villas::graph::Vertex> v6(new villas::graph::Vertex);
+
+	auto v1id = g.addVertex(v1);
+	auto v2id = g.addVertex(v2);
+	auto v3id = g.addVertex(v3);
+
+	auto v4id = g.addVertex(v4);
+	auto v5id = g.addVertex(v5);
+	auto v6id = g.addVertex(v6);
+
+	g.addEdge(v1id, v2id);
+	g.addEdge(v2id, v3id);
+
+	// create circular subgraph
+	g.addEdge(v4id, v5id);
+	g.addEdge(v5id, v4id);
+	g.addEdge(v5id, v6id);
+
+	g.dump();
+
+	logger->info("Find simple path via two edges");
+	std::list<villas::graph::EdgeIdentifier> path1;
+	cr_assert(g.getPath(v1id, v3id, path1));
+
+	logger->info("  Path from {} to {} via:", v1id, v3id);
+	for(auto& edge : path1) {
+		logger->info("  -> edge {}", edge);
+	}
+
+	logger->info("Find path between two unconnected sub-graphs");
+	std::list<villas::graph::EdgeIdentifier> path2;
+	cr_assert(not g.getPath(v1id, v4id, path2));
+	logger->info("  no path found -> ok");
+
+
+	logger->info("Find non-existing path in circular sub-graph");
+	std::list<villas::graph::EdgeIdentifier> path3;
+	cr_assert(not g.getPath(v4id, v2id, path3));
+	logger->info("  no path found -> ok");
+
+
+	logger->info("Find path in circular graph");
+	std::list<villas::graph::EdgeIdentifier> path4;
+	cr_assert(g.getPath(v4id, v6id, path4));
+
+	logger->info("  Path from {} to {} via:", v4id, v6id);
+	for(auto& edge : path4) {
+		logger->info("  -> edge {}", edge);
+	}
+}