diff --git a/.gitignore b/.gitignore index c4164b7..b791954 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +hello_build +test_2 # Temporary files *~* *#* diff --git a/CMakeLists.txt b/CMakeLists.txt index 931bc7e..47c6fc0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,11 @@ +cmake_minimum_required(VERSION 3.15...3.29) + project(sp LANGUAGES CXX) # Require C++14-compliant compiler; only available for CMake v. 3.1 and up set(CMAKE_CXX_STANDARD 14) - -cmake_minimum_required(VERSION 3.1) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) SET(CMAKE_COLOR_MAKEFILE ON) SET(CMAKE_VERBOSE_MAKEFILE OFF) diff --git a/external/catch.hpp b/external/catch.hpp index 6b5129d..af1073f 100644 --- a/external/catch.hpp +++ b/external/catch.hpp @@ -2130,7 +2130,11 @@ namespace Catch{ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ) /* NOLINT */ #else - #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ ) + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #endif #endif #elif defined(CATCH_PLATFORM_LINUX) diff --git a/include/graph.h b/include/graph.h index 8db4498..a626a44 100644 --- a/include/graph.h +++ b/include/graph.h @@ -34,7 +34,8 @@ struct ShortestPath { // Iterate until source has been reached while (destination != source) { destination = parent.at(destination); - if (destination != source) path.emplace_back(destination); + if (destination != source) + path.emplace_back(destination); } // Reverse to arrange from source to destination std::reverse(path.begin(), path.end()); @@ -54,7 +55,7 @@ struct ShortestPath { //! \brief Graph class to store vertices and edge and compute shortest path //! \details Graph class has Priority Queue Dijkstra algorithm for SSSP class Graph { - public: +public: //! Vertex id type using vertex_t = int; //! Weight type, that can be added with + @@ -89,14 +90,24 @@ class Graph { //! Generate a simple graph void generate_simple_graph(); + //! Create a graph based on edges from Pandas DataFrame + //! \param[in] edge_starts name of the dataframe column containing edge start + //! nodes \param[in] edge_ends name of the dataframe column containing edge + //! end nodes \param[in] edge_wghs name of the dataframe column containing + //! edge weights \param[in] nedges numbers of edges (number of rows in the + //! edges dataframe) \param[in] nvertices numbers of vertices (maximum of + //! start and end node ids in the edges dataframe) + void generate_graph(int *edge_starts, int *edge_ends, double *edge_wghs, + int nedges, int nvertices); + //! Read MatrixMarket graph file format //! \param[in] filename Name of input MatrixMarket file //! \retval status File read status - bool read_graph_matrix_market(const std::string& filename); + bool read_graph_matrix_market(const std::string &filename); //! Write MatrixMarket graph file format //! \param[in] filename Name of output MatrixMarket file - bool write_graph_matrix_market(const std::string& filename); + bool write_graph_matrix_market(const std::string &filename); //! Compute the shortest path using priority queue //! \param[in] source ID of source vertex1 @@ -105,7 +116,7 @@ class Graph { ShortestPath dijkstra_priority_queue(vertex_t source, vertex_t destination = -1); - private: +private: //! Assign number of vertices //! \param[in] nvertices Number of vertices in graph void assign_nvertices(unsigned nvertices) { this->nvertices_ = nvertices; } @@ -120,4 +131,4 @@ class Graph { tsl::robin_map>> vertex_edges_; }; -#endif // _GRAPH_H_ +#endif // _GRAPH_H_ diff --git a/include/unordered_map_tuple_hash.h b/include/unordered_map_tuple_hash.h index 080e48e..b4b05cc 100644 --- a/include/unordered_map_tuple_hash.h +++ b/include/unordered_map_tuple_hash.h @@ -14,36 +14,33 @@ namespace { // See Mike Seymour in magic-numbers-in-boosthash-combine: // https://stackoverflow.com/questions/4948780 -template -inline void hash_combine(std::size_t& seed, T const& v) { +template inline void hash_combine(std::size_t &seed, T const &v) { seed ^= hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } // Recursive template code derived from Matthieu M. template ::value - 1> struct HashValueImpl { - static void apply(size_t& seed, Tuple const& tuple) { + static void apply(size_t &seed, Tuple const &tuple) { HashValueImpl::apply(seed, tuple); hash_combine(seed, get(tuple)); } }; -template -struct HashValueImpl { - static void apply(size_t& seed, Tuple const& tuple) { +template struct HashValueImpl { + static void apply(size_t &seed, Tuple const &tuple) { hash_combine(seed, get<0>(tuple)); } }; -} // namespace +} // namespace -template -struct hash> { - size_t operator()(std::tuple const& tt) const { +template struct hash> { + size_t operator()(std::tuple const &tt) const { size_t seed = 0; HashValueImpl>::apply(seed, tt); return seed; } }; -} // namespace std +} // namespace std -#endif // _UNORDERED_MAP_TUPLE_HASH_H_ +#endif // _UNORDERED_MAP_TUPLE_HASH_H_ diff --git a/interface.py b/interface.py index 5c427ba..8bc23f9 100644 --- a/interface.py +++ b/interface.py @@ -1,9 +1,27 @@ from __future__ import print_function +import sys from sys import argv -from ctypes import * +from pathlib import Path +from ctypes import c_bool, c_int, c_int32, c_double, cdll, byref, Structure, POINTER +from numpy.ctypeslib import ndpointer +import pandas as pd +import numpy as np + +import os +absolute_path = os.path.dirname(os.path.abspath(__file__)) + +root = Path(absolute_path) / "build" + +if sys.platform == "darwin": + ext = ".dylib" +elif sys.platform.startswith("win"): + ext = ".dll" +else: + ext = ".so" + +libsp = cdll.LoadLibrary(str(root / f"liblsp{ext}")) -libsp = cdll.LoadLibrary("./build/liblsp.so") libsp.distance.restype = c_double class ShortestPath(Structure): @@ -37,19 +55,34 @@ def writegraph(self, filename): libsp.simplegraph.restype = POINTER(Graph) libsp.readgraph.restype = POINTER(Graph) +libsp.creategraph.restype = POINTER(Graph) +libsp.creategraph.argtypes = [ + ndpointer(c_int32, flags='C_CONTIGUOUS'), + ndpointer(c_int32, flags='C_CONTIGUOUS'), + ndpointer(c_double, flags='C_CONTIGUOUS'), + c_int, c_int, c_bool] def simplegraph(directed=True): return libsp.simplegraph(directed).contents - def readgraph(filename, directed=True): return libsp.readgraph(filename, directed).contents +def from_dataframe(edges=None, start_node_col=None, end_node_col=None, weight_col=None, directed=True): + # print(np.max(edges[[start_node_col, end_node_col]].values)) + return libsp.creategraph( + edges[start_node_col].values.astype(np.int32), + edges[end_node_col].values.astype(np.int32), + edges[weight_col].values, + edges.shape[0], ### the number of edges is computed from the edges dataframe, not an input to the python interface + np.max(edges[[start_node_col, end_node_col]].values), ### number of vertices is the maximum of the start or end node id in the edges dataframe + directed).contents + def test(): g = simplegraph() #g = readgraph(b"../sf.mtx") - res = g.update_edge(1, 3, c_double(0.5)) + g.update_edge(1, 3, c_double(0.5)) sp = g.dijkstra(1, -1) print("origin:", sp.origin) @@ -58,6 +91,33 @@ def test(): print(destination, sp.distance(destination)) print( " -> ".join("%s"%vertex[1] for vertex in sp.route(destination)) ) + sp.clear() + +def test_df(): + print("test 1") + df = pd.DataFrame({'start':[0,1,2,3,4,5,6,7], 'end':[1,2,3,4,5,6,7,0], 'wgh':[0.1,0.5,1.9,1.1,1.2,1.5,1.6,1.9]}) + g = from_dataframe(df, 'start', 'end', 'wgh') + + origin, destin = 1, 5 + sp = g.dijkstra(origin, destin) + + print("origin is {}, destination is {}".format(sp.origin, destin)) + print("path is {} --> ".format(sp.origin), " -> ".join("%s"%vertex[1] for vertex in sp.route(destin)) ) + print("distance is ", sp.distance(destin)) + sp.clear() + + print("\ntest 2") + df = pd.DataFrame({'start':[0,2,4,10,12], 'end':[1,3,5,12,0], 'wgh':[0.1,0.5,1.9,1.1,1.2]}) + # df = pd.DataFrame({'start':[0,1,2,3,4], 'end':[1,2,3,4,0], 'wgh':[0.1,0.5,1.9,1.1,1.2]}) + g = from_dataframe(df, 'start', 'end', 'wgh') + + origin, destin = 10,1 #0,4 + sp = g.dijkstra(origin, destin) + + print("origin is {}, destination is {}".format(sp.origin, destin)) + print("path is {} --> ".format(sp.origin), " -> ".join("%s"%vertex[1] for vertex in sp.route(destin)) ) + print("distance is ", sp.distance(destin)) + sp.clear() if __name__ == '__main__': - test() + test_df() diff --git a/src/graph.cc b/src/graph.cc index 2e8483a..46f2714 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -134,6 +134,21 @@ void Graph::generate_simple_graph() { this->add_edge(5, 6, 9.7); } +// Create graph based on edges from Pandas DataFrame +void Graph::generate_graph(int* edge_starts, int* edge_ends, double* edge_wghs, + int nedges, int nvertices) { + // std::cout << nvertices << std::endl; + this->assign_nvertices(nvertices + 1); + + for (unsigned int e = 0; e < nedges; e++) { + int v1 = edge_starts[e]; + int v2 = edge_ends[e]; + double weight = edge_wghs[e]; + // std::cout << v1 << " " << v2 << " " << weight << std::endl; + this->add_edge(v1, v2, weight); + } +} + // Dijktra shortest paths from src to all other vertices ShortestPath Graph::dijkstra_priority_queue(vertex_t source, vertex_t destination) { diff --git a/src/py.cc b/src/py.cc index 1ecf542..623e142 100644 --- a/src/py.cc +++ b/src/py.cc @@ -24,6 +24,22 @@ Graph* readgraph(char* filename, bool directed) { return graph; } +//! Create a graph based on edges from Pandas DataFrame +//! \param[in] edge_starts name of the dataframe column containing edge start +//! nodes \param[in] edge_ends name of the dataframe column containing edge end +//! nodes \param[in] edge_wghs name of the dataframe column containing edge +//! weights \param[in] nedges numbers of edges (number of rows in the edges +//! dataframe) \param[in] nvertices numbers of vertices (maximum of start and +//! end node ids in the edges dataframe) \param[in] directed Boolean to +//! determine if a given graph is directed or not \retval graph Pointer to a +//! graph object +Graph* creategraph(int* edge_starts, int* edge_ends, double* edge_wghs, + int nedges, int nvertices, bool directed) { + Graph* graph = new Graph(directed); + graph->generate_graph(edge_starts, edge_ends, edge_wghs, nedges, nvertices); + return graph; +} + //! Write graph as MatrixMarket file //! \param[in] filename Graph filename //! \param[in] graph Graph object