Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ jobs:
python -m pip install -r docs/requirements.txt

- name: Build package
env:
MACOSX_DEPLOYMENT_TARGET: 11.0
run: |
CXXFLAGS="-std=c++17" python scripts/build/install.py

Expand Down
154 changes: 154 additions & 0 deletions pydatastructs/graphs/_backend/cpp/Algorithms.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include <Python.h>
#include <unordered_map>
#include <queue>
#include <string>
#include "AdjacencyList.hpp"

static inline AdjacencyListGraphNode* get_node(AdjacencyListGraph* graph, const std::string& name) {
auto it = graph->node_map.find(name);
return (it != graph->node_map.end()) ? it->second : nullptr;
}

static PyObject* bfs_adjacency_list(PyObject* self, PyObject* args, PyObject* kwds) {
static char* kwlist[] = {(char*)"graph", (char*)"source_node", (char*)"operation", (char*)"extra_arg", NULL};

PyObject* py_graph = nullptr;
const char* source_node = nullptr;
PyObject* operation = nullptr;
PyObject* extra_arg = nullptr;

fprintf(stderr, "[bfs] Parsing arguments...\n");

if (!PyArg_ParseTupleAndKeywords(args, kwds, "OsO|O", kwlist,
&py_graph, &source_node, &operation, &extra_arg)) {
fprintf(stderr, "[bfs] Failed to parse arguments\n");
return NULL;
}

fprintf(stderr, "[bfs] Arguments parsed:\n");
fprintf(stderr, " - source_node: %s\n", source_node);
fprintf(stderr, " - extra_arg: %s\n", (extra_arg ? Py_TYPE(extra_arg)->tp_name : "NULL"));
fprintf(stderr, "[bfs] Checking type of py_graph...\n");
fprintf(stderr, " - Expected: %s\n", AdjacencyListGraphType.tp_name);
fprintf(stderr, " - Actual: %s\n", Py_TYPE(py_graph)->tp_name);
fprintf(stderr, " - Expected address: %p\n", &AdjacencyListGraphType);
fprintf(stderr, " - Actual type addr: %p\n", (void*)Py_TYPE(py_graph));

fprintf(stderr, "[bfs] Attempting to import _graph...\n");
PyObject* graph_module = PyImport_ImportModule("_graph");
if (!graph_module) {
PyErr_Print();
PyErr_SetString(PyExc_ImportError, "Could not import _graph module");
return NULL;
}

PyObject* expected_type = PyObject_GetAttrString(graph_module, "AdjacencyListGraph");
Py_DECREF(graph_module);

if (!expected_type || !PyType_Check(expected_type)) {
Py_XDECREF(expected_type);
PyErr_SetString(PyExc_TypeError, "Could not retrieve AdjacencyListGraph type");
return NULL;
}

if (!PyObject_IsInstance(py_graph, expected_type)) {
Py_DECREF(expected_type);
PyErr_SetString(PyExc_TypeError, "Expected an AdjacencyListGraph instance");
return NULL;
}

if (!PyCallable_Check(operation)) {
PyErr_SetString(PyExc_TypeError, "Expected a callable for operation");
fprintf(stderr, "[bfs] operation is not callable\n");
return NULL;
}

AdjacencyListGraph* graph = (AdjacencyListGraph*)py_graph;

if (!get_node(graph, source_node)) {
PyErr_SetString(PyExc_ValueError, "Source node does not exist in the graph");
fprintf(stderr, "[bfs] source_node not found in graph\n");
return NULL;
}

fprintf(stderr, "[bfs] Starting BFS from node: %s\n", source_node);

std::unordered_map<std::string, bool> visited;
std::queue<std::string> q;

q.push(source_node);
visited[source_node] = true;

while (!q.empty()) {
std::string curr = q.front();
q.pop();

fprintf(stderr, "[bfs] Visiting node: %s\n", curr.c_str());

auto* curr_node = get_node(graph, curr);
if (!curr_node) {
fprintf(stderr, "[bfs] Warning: node %s not found in node_map\n", curr.c_str());
continue;
}

const auto& neighbors = curr_node->adjacent;

if (!neighbors.empty()) {
for (const auto& [next_name, _] : neighbors) {
if (!visited[next_name]) {
fprintf(stderr, "[bfs] Considering neighbor: %s\n", next_name.c_str());

PyObject* result = nullptr;

if (extra_arg)
result = PyObject_CallFunction(operation, "ssO", curr.c_str(), next_name.c_str(), extra_arg);
else
result = PyObject_CallFunction(operation, "ss", curr.c_str(), next_name.c_str());

if (!result) {
fprintf(stderr, "[bfs] PyObject_CallFunction failed on (%s, %s)\n", curr.c_str(), next_name.c_str());
PyErr_Print();
return NULL;
}

int keep_going = PyObject_IsTrue(result);
Py_DECREF(result);

if (!keep_going) {
fprintf(stderr, "[bfs] Operation requested to stop traversal at edge (%s -> %s)\n", curr.c_str(), next_name.c_str());
Py_RETURN_NONE;
}

visited[next_name] = true;
q.push(next_name);
}
}
} else {
fprintf(stderr, "[bfs] Leaf node reached: %s\n", curr.c_str());

PyObject* result = nullptr;

if (extra_arg)
result = PyObject_CallFunction(operation, "sO", curr.c_str(), extra_arg);
else
result = PyObject_CallFunction(operation, "s", curr.c_str());

if (!result) {
fprintf(stderr, "[bfs] PyObject_CallFunction failed at leaf node (%s)\n", curr.c_str());
PyErr_Print();
return NULL;
}

int keep_going = PyObject_IsTrue(result);
Py_DECREF(result);

if (!keep_going) {
fprintf(stderr, "[bfs] Operation requested to stop traversal at leaf node %s\n", curr.c_str());
Py_RETURN_NONE;
}
}
}

fprintf(stderr, "[bfs] BFS traversal complete\n");
Py_RETURN_NONE;
}
46 changes: 46 additions & 0 deletions pydatastructs/graphs/_backend/cpp/algorithms.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <Python.h>
#include "Algorithms.hpp"

static PyTypeObject* get_adjacency_list_graph_type() {
static PyTypeObject* cached_type = nullptr;

if (cached_type != nullptr) return cached_type;

PyObject* graph_mod = PyImport_ImportModule("pydatastructs.graphs._backend.cpp._graph");
if (!graph_mod) {
PyErr_SetString(PyExc_ImportError, "[algorithms] Failed to import _graph module");
return nullptr;
}

PyObject* type_obj = PyObject_GetAttrString(graph_mod, "AdjacencyListGraph");
Py_DECREF(graph_mod);

if (!type_obj || !PyType_Check(type_obj)) {
Py_XDECREF(type_obj);
PyErr_SetString(PyExc_TypeError, "[algorithms] AdjacencyListGraph is not a type object");
return nullptr;
}

cached_type = reinterpret_cast<PyTypeObject*>(type_obj);
return cached_type;
}

extern PyTypeObject* get_adjacency_list_graph_type();

static PyMethodDef AlgorithmsMethods[] = {
{"bfs_adjacency_list", (PyCFunction)bfs_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS with callback"},
{NULL, NULL, 0, NULL}
};

static struct PyModuleDef algorithms_module = {
PyModuleDef_HEAD_INIT,
"_algorithms", NULL, -1, AlgorithmsMethods
};

PyMODINIT_FUNC PyInit__algorithms(void) {
PyObject* graph_mod = PyImport_ImportModule("pydatastructs.graphs._backend.cpp._graph");
if (!graph_mod) return nullptr;
Py_DECREF(graph_mod);

return PyModule_Create(&algorithms_module);
}
6 changes: 5 additions & 1 deletion pydatastructs/graphs/_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
graph = '.'.join([project, module, backend, cpp, '_graph'])
graph_sources = ['/'.join([project, module, backend, cpp,
'graph.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"]
algorithms = '.'.join([project, module, backend, cpp, '_algorithms'])
algorithms_sources = ['/'.join([project, module, backend, cpp,
'algorithms.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"]

include_dir = os.path.abspath(os.path.join(project, 'utils', '_backend', 'cpp'))

extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir])]
extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"]),
Extension(algorithms, sources=algorithms_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"])]
25 changes: 15 additions & 10 deletions pydatastructs/graphs/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pydatastructs.graphs.graph import Graph
from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel
from pydatastructs import PriorityQueue
from pydatastructs.graphs._backend.cpp import _algorithms

__all__ = [
'breadth_first_search',
Expand Down Expand Up @@ -81,16 +82,20 @@ def breadth_first_search(
>>> G.add_edge(V2.name, V3.name)
>>> breadth_first_search(G, V1.name, f, V3.name)
"""
raise_if_backend_is_not_python(
breadth_first_search, kwargs.get('backend', Backend.PYTHON))
import pydatastructs.graphs.algorithms as algorithms
func = "_breadth_first_search_" + graph._impl
if not hasattr(algorithms, func):
raise NotImplementedError(
"Currently breadth first search isn't implemented for "
"%s graphs."%(graph._impl))
return getattr(algorithms, func)(
graph, source_node, operation, *args, **kwargs)
backend = kwargs.get('backend', Backend.PYTHON)
if backend == Backend.PYTHON:
import pydatastructs.graphs.algorithms as algorithms
func = "_breadth_first_search_" + graph._impl
if not hasattr(algorithms, func):
raise NotImplementedError(
"Currently breadth first search isn't implemented for "
"%s graphs."%(graph._impl))
return getattr(algorithms, func)(
graph, source_node, operation, *args, **kwargs)
else:
if (graph._impl == "adjacency_list"):
extra_arg = args[0] if args else None
return _algorithms.bfs_adjacency_list(graph,source_node, operation, extra_arg)

def _breadth_first_search_adjacency_list(
graph, source_node, operation, *args, **kwargs):
Expand Down
17 changes: 17 additions & 0 deletions pydatastructs/graphs/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
topological_sort_parallel, max_flow, find_bridges)
from pydatastructs.utils.raises_util import raises
from pydatastructs.utils.misc_util import AdjacencyListGraphNode, AdjacencyMatrixGraphNode
from pydatastructs.graphs._backend.cpp import _graph
from pydatastructs.utils.misc_util import Backend

def test_breadth_first_search():

Expand Down Expand Up @@ -39,6 +42,18 @@ def bfs_tree(curr_node, next_node, parent):
breadth_first_search(G1, V1.name, bfs_tree, parent)
assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \
(parent[V3.name] == V2.name and parent[V2.name] == V1.name)

if (ds=='List'):
V9 = AdjacencyListGraphNode("9",0,backend = Backend.CPP)
V10 = AdjacencyListGraphNode("10",0,backend = Backend.CPP)
V11 = AdjacencyListGraphNode("11",0,backend = Backend.CPP)
G2 = Graph(V9, V10, V11,implementation = 'adjacency_list', backend = Backend.CPP)
assert G2.num_vertices()==3
G2.add_edge("9", "10")
G2.add_edge("10", "11")
breadth_first_search(G2, V1.name, bfs_tree, parent, backend = Backend.CPP)



V4 = GraphNode(0)
V5 = GraphNode(1)
Expand Down Expand Up @@ -131,6 +146,8 @@ def bfs_tree(curr_node, next_node, parent):
_test_breadth_first_search_parallel("List")
_test_breadth_first_search_parallel("Matrix")

test_breadth_first_search()

def test_minimum_spanning_tree():

def _test_minimum_spanning_tree(func, ds, algorithm, *args):
Expand Down
Loading
Loading