From 68cee46d6d2ff88e48059ab5e22c1896dc6c9969 Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Thu, 5 Jun 2025 23:27:07 +0530 Subject: [PATCH 1/8] Backend for Adjacency List Graph added --- _graph.cp | 0 _graph.cpp | 0 graph.cpp | 0 pydatastructs/graphs/__init__.py | 1 + pydatastructs/graphs/_backend/__init__.py | 0 .../graphs/_backend/cpp/AdjacencyList.hpp | 355 ++++++++++++++++++ pydatastructs/graphs/_backend/cpp/__init__.py | 0 pydatastructs/graphs/_backend/cpp/graph.cpp | 45 +++ pydatastructs/graphs/_extensions.py | 18 + pydatastructs/graphs/adjacency_list.py | 23 +- pydatastructs/graphs/graph.py | 11 +- .../graphs/tests/tempCodeRunnerFile.py | 1 + .../graphs/tests/test_adjacency_list.py | 41 ++ .../_backend/cpp/AdjacencyListGraphNode.hpp | 2 +- .../_backend/cpp/AdjacencyMatrixGraphNode.hpp | 2 +- .../utils/_backend/cpp/GraphEdge.hpp | 2 +- .../utils/_backend/cpp/graph_bindings.hpp | 9 + .../utils/_backend/cpp/graph_utils.cpp | 1 + scripts/build/dummy_submodules_data.py | 4 +- setup.py | 2 + 20 files changed, 499 insertions(+), 18 deletions(-) create mode 100644 _graph.cp create mode 100644 _graph.cpp create mode 100644 graph.cpp create mode 100644 pydatastructs/graphs/_backend/__init__.py create mode 100644 pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp create mode 100644 pydatastructs/graphs/_backend/cpp/__init__.py create mode 100644 pydatastructs/graphs/_backend/cpp/graph.cpp create mode 100644 pydatastructs/graphs/_extensions.py create mode 100644 pydatastructs/graphs/tests/tempCodeRunnerFile.py create mode 100644 pydatastructs/utils/_backend/cpp/graph_bindings.hpp diff --git a/_graph.cp b/_graph.cp new file mode 100644 index 000000000..e69de29bb diff --git a/_graph.cpp b/_graph.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/graph.cpp b/graph.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 21e0a5f35..c1a70574a 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -9,6 +9,7 @@ from . import algorithms from . import adjacency_list from . import adjacency_matrix +from . import _extensions from .algorithms import ( breadth_first_search, diff --git a/pydatastructs/graphs/_backend/__init__.py b/pydatastructs/graphs/_backend/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp new file mode 100644 index 000000000..93b4eae7b --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -0,0 +1,355 @@ +#ifndef ADJACENCY_LIST_GRAPH_HPP +#define ADJACENCY_LIST_GRAPH_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include +#include "AdjacencyListGraphNode.hpp" +#include "GraphEdge.hpp" + +extern PyTypeObject AdjacencyListGraphType; + +typedef struct { + + PyObject_HEAD + std::vector nodes; + std::unordered_map edges; + std::unordered_map node_map; + +} AdjacencyListGraph; + +static void AdjacencyListGraph_dealloc(AdjacencyListGraph* self) { + for (AdjacencyListGraphNode* node : self->nodes) { + Py_XDECREF(node); + } + self->nodes.clear(); + + for (auto& pair : self->edges) { + Py_XDECREF(pair.second); + } + self->edges.clear(); + + self->node_map.clear(); + + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { + AdjacencyListGraph* self = (AdjacencyListGraph*)type->tp_alloc(type, 0); + if (!self) + return NULL; + + new (&self->nodes) std::vector(); + new (&self->edges) std::unordered_map(); + new (&self->node_map) std::unordered_map(); + + Py_ssize_t num_args = PyTuple_Size(args); + for (Py_ssize_t i = 0; i < num_args; ++i) { + PyObject* node_obj = PyTuple_GetItem(args, i); + if (!PyObject_IsInstance(node_obj, (PyObject*)&AdjacencyListGraphNodeType)) { + PyErr_SetString(PyExc_TypeError, "All arguments must be AdjacencyListGraphNode instances"); + + self->nodes.~vector(); + self->edges.~unordered_map(); + self->node_map.~unordered_map(); + Py_TYPE(self)->tp_free((PyObject*)self); + return NULL; + } + + AdjacencyListGraphNode* node = (AdjacencyListGraphNode*)node_obj; + + if (self->node_map.find(node->name) != self->node_map.end()) { + PyErr_Format(PyExc_ValueError, "Duplicate node with name '%s'", node->name.c_str()); + + self->nodes.~vector(); + self->edges.~unordered_map(); + self->node_map.~unordered_map(); + Py_TYPE(self)->tp_free((PyObject*)self); + return NULL; + } + + Py_INCREF(node); + self->nodes.push_back(node); + self->node_map[node->name] = node; + } + return (PyObject*)self; +} + +static std::string make_edge_key(const std::string& source, const std::string& target) { + return source + "_" + target; +} + +static PyObject* AdjacencyListGraph_add_vertex(AdjacencyListGraph* self, PyObject* args) { + PyObject* node_obj; + + if (!PyArg_ParseTuple(args, "O", &node_obj)) { + PyErr_SetString(PyExc_ValueError, "Expected a single AdjacencyListGraphNode object"); + return NULL; + } + + if (!PyObject_IsInstance(node_obj, (PyObject*)&AdjacencyListGraphNodeType)) { + PyErr_SetString(PyExc_TypeError, "Object is not an AdjacencyListGraphNode"); + return NULL; + } + + AdjacencyListGraphNode* node = (AdjacencyListGraphNode*)node_obj; + + if (self->node_map.find(node->name) != self->node_map.end()) { + PyErr_SetString(PyExc_ValueError, "Node with this name already exists"); + return NULL; + } + + Py_INCREF(node); + self->nodes.push_back(node); + self->node_map[node->name] = node; + + Py_RETURN_NONE; +} + + +static PyObject* AdjacencyListGraph_is_adjacent(AdjacencyListGraph* self, PyObject* args) { + const char* node1_name_c; + const char* node2_name_c; + if (!PyArg_ParseTuple(args, "ss", &node1_name_c, &node2_name_c)) + return NULL; + + std::string node1_name(node1_name_c); + std::string node2_name(node2_name_c); + + auto it1 = self->node_map.find(node1_name); + if (it1 == self->node_map.end()) { + PyErr_SetString(PyExc_KeyError, "node1 not found"); + return NULL; + } + AdjacencyListGraphNode* node1 = it1->second; + + if (node1->adjacent.find(node2_name) != node1->adjacent.end()) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +static PyObject* AdjacencyListGraph_num_vertices(AdjacencyListGraph* self, PyObject* Py_UNUSED(ignored)) { + return PyLong_FromSize_t(self->nodes.size()); +} + +static PyObject* AdjacencyListGraph_num_edges(AdjacencyListGraph* self, PyObject* Py_UNUSED(ignored)) { + return PyLong_FromSize_t(self->edges.size()); +} + +static PyObject* AdjacencyListGraph_neighbors(AdjacencyListGraph* self, PyObject* args) { + const char* node_name_c; + if (!PyArg_ParseTuple(args, "s", &node_name_c)) + return NULL; + + std::string node_name(node_name_c); + auto it = self->node_map.find(node_name); + if (it == self->node_map.end()) { + PyErr_SetString(PyExc_KeyError, "Node not found"); + return NULL; + } + AdjacencyListGraphNode* node = it->second; + + PyObject* neighbors_list = PyList_New(0); + if (!neighbors_list) return NULL; + + for (const auto& adj_pair : node->adjacent) { + Py_INCREF(adj_pair.second); + PyList_Append(neighbors_list, (PyObject*)adj_pair.second); + } + + return neighbors_list; +} + +static PyObject* AdjacencyListGraph_remove_vertex(AdjacencyListGraph* self, PyObject* args) { + const char* name_c; + if (!PyArg_ParseTuple(args, "s", &name_c)) + return NULL; + + std::string name(name_c); + auto it = self->node_map.find(name); + if (it == self->node_map.end()) { + PyErr_SetString(PyExc_KeyError, "Node not found"); + return NULL; + } + + AdjacencyListGraphNode* node_to_remove = it->second; + + auto& nodes_vec = self->nodes; + nodes_vec.erase(std::remove(nodes_vec.begin(), nodes_vec.end(), node_to_remove), nodes_vec.end()); + + self->node_map.erase(it); + + Py_XDECREF(node_to_remove); + + for (auto& node_pair : self->node_map) { + AdjacencyListGraphNode* node = node_pair.second; + auto adj_it = node->adjacent.find(name); + if (adj_it != node->adjacent.end()) { + Py_XDECREF(adj_it->second); + node->adjacent.erase(adj_it); + } + } + + for (auto it = self->edges.begin(); it != self->edges.end(); ) { + const std::string& key = it->first; + + bool involves_node = false; + size_t underscore = key.find('_'); + if (underscore != std::string::npos) { + std::string source = key.substr(0, underscore); + std::string target = key.substr(underscore + 1); + if (source == name || target == name) + involves_node = true; + } + + if (involves_node) { + Py_XDECREF(it->second); + it = self->edges.erase(it); + } else { + ++it; + } + } + + Py_RETURN_NONE; +} + + +static PyObject* AdjacencyListGraph_get_edge(AdjacencyListGraph* self, PyObject* args) { + const char* source_c; + const char* target_c; + if (!PyArg_ParseTuple(args, "ss", &source_c, &target_c)) + return NULL; + + std::string key = make_edge_key(source_c, target_c); + auto it = self->edges.find(key); + if (it != self->edges.end()) { + Py_INCREF(it->second); + return (PyObject*)it->second; + } + + Py_RETURN_NONE; +} + +static PyObject* AdjacencyListGraph_remove_edge(AdjacencyListGraph* self, PyObject* args) { + const char* source_c; + const char* target_c; + if (!PyArg_ParseTuple(args, "ss", &source_c, &target_c)) + return NULL; + + std::string source(source_c); + std::string target(target_c); + + auto it_source = self->node_map.find(source); + auto it_target = self->node_map.find(target); + if (it_source == self->node_map.end() || it_target == self->node_map.end()) { + PyErr_SetString(PyExc_KeyError, "Source or target node not found"); + return NULL; + } + + AdjacencyListGraphNode* source_node = it_source->second; + + auto adj_it = source_node->adjacent.find(target); + if (adj_it != source_node->adjacent.end()) { + Py_XDECREF(adj_it->second); + source_node->adjacent.erase(adj_it); + } + + std::string key = make_edge_key(source, target); + auto edge_it = self->edges.find(key); + if (edge_it != self->edges.end()) { + Py_XDECREF(edge_it->second); + self->edges.erase(edge_it); + } + + Py_RETURN_NONE; +} + +static PyObject* AdjacencyListGraph_add_edge(AdjacencyListGraph* self, PyObject* args) { + const char* source_cstr; + const char* target_cstr; + PyObject* value = Py_None; + + if (!PyArg_ParseTuple(args, "ss|O", &source_cstr, &target_cstr, &value)) { + PyErr_SetString(PyExc_ValueError, "Expected source (str), target (str), and optional weight"); + return NULL; + } + + std::string source(source_cstr); + std::string target(target_cstr); + + if (self->node_map.find(source) == self->node_map.end()) { + PyErr_SetString(PyExc_ValueError, "Source node does not exist"); + return NULL; + } + if (self->node_map.find(target) == self->node_map.end()) { + PyErr_SetString(PyExc_ValueError, "Target node does not exist"); + return NULL; + } + + AdjacencyListGraphNode* source_node = self->node_map[source]; + AdjacencyListGraphNode* target_node = self->node_map[target]; + + PyObject* edge_args = PyTuple_Pack(3, (PyObject*)source_node, (PyObject*)target_node, value); + if (!edge_args) return NULL; + + PyObject* edge_obj = PyObject_CallObject((PyObject*)&GraphEdgeType, edge_args); + Py_DECREF(edge_args); + + if (!edge_obj) + return NULL; + + std::string key = make_edge_key(source, target); + + auto it = self->edges.find(key); + if (it != self->edges.end()) { + Py_XDECREF(it->second); + it->second = (GraphEdge*)edge_obj; + } else { + self->edges[key] = (GraphEdge*)edge_obj; + } + + auto adj_it = source_node->adjacent.find(target); + if (adj_it == source_node->adjacent.end()) { + Py_INCREF(target_node); + source_node->adjacent[target] = (PyObject*)target_node; + } + + Py_RETURN_NONE; +} + + + + +static PyMethodDef AdjacencyListGraph_methods[] = { + {"add_vertex", (PyCFunction)AdjacencyListGraph_add_vertex, METH_VARARGS, "Add a vertex to the graph"}, + {"add_edge", (PyCFunction)AdjacencyListGraph_add_edge, METH_VARARGS, "Add an edge to the graph"}, + {"is_adjacent", (PyCFunction)AdjacencyListGraph_is_adjacent, METH_VARARGS, "Check adjacency between two nodes"}, + {"num_vertices", (PyCFunction)AdjacencyListGraph_num_vertices, METH_NOARGS, "Number of vertices"}, + {"num_edges", (PyCFunction)AdjacencyListGraph_num_edges, METH_NOARGS, "Number of edges"}, + {"neighbors", (PyCFunction)AdjacencyListGraph_neighbors, METH_VARARGS, "Get neighbors of a node"}, + {"remove_vertex", (PyCFunction)AdjacencyListGraph_remove_vertex, METH_VARARGS, "Remove a vertex"}, + {"get_edge", (PyCFunction)AdjacencyListGraph_get_edge, METH_VARARGS, "Get edge between source and target"}, + {"remove_edge", (PyCFunction)AdjacencyListGraph_remove_edge, METH_VARARGS, "Remove edge between source and target"}, + {NULL} +}; + + +PyTypeObject AdjacencyListGraphType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_init= 0, + .tp_name = "_graph.AdjacencyListGraph", + .tp_basicsize = sizeof(AdjacencyListGraph), + .tp_itemsize = 0, + .tp_dealloc = (destructor)AdjacencyListGraph_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = "Adjacency List Graph data structure", + .tp_methods = AdjacencyListGraph_methods, + .tp_new = AdjacencyListGraph_new, +}; + +#endif + diff --git a/pydatastructs/graphs/_backend/cpp/__init__.py b/pydatastructs/graphs/_backend/cpp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/graphs/_backend/cpp/graph.cpp b/pydatastructs/graphs/_backend/cpp/graph.cpp new file mode 100644 index 000000000..9a12d94ef --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/graph.cpp @@ -0,0 +1,45 @@ +#define PY_SSIZE_T_CLEAN +#include +#include "AdjacencyList.hpp" +#include "AdjacencyListGraphNode.hpp" +#include "graph_bindings.hpp" + + + +static struct PyModuleDef graph_module = { + PyModuleDef_HEAD_INIT, + "_graph", + "C++ module for graphs", + -1, + NULL, +}; + +PyMODINIT_FUNC PyInit__graph(void) { + PyObject* m; + + if (PyType_Ready(&AdjacencyListGraphType) < 0) + return NULL; + + if (PyType_Ready(&AdjacencyListGraphNodeType) < 0) + return NULL; + + m = PyModule_Create(&graph_module); + if (m == NULL) + return NULL; + + Py_INCREF(&AdjacencyListGraphType); + if (PyModule_AddObject(m, "AdjacencyListGraph", (PyObject*)&AdjacencyListGraphType) < 0) { + Py_DECREF(&AdjacencyListGraphType); + Py_DECREF(m); + return NULL; + } + + Py_INCREF(&AdjacencyListGraphNodeType); + if (PyModule_AddObject(m, "AdjacencyListGraphNode", (PyObject*)&AdjacencyListGraphNodeType) < 0) { + Py_DECREF(&AdjacencyListGraphNodeType); + Py_DECREF(m); + return NULL; + } + + return m; +} \ No newline at end of file diff --git a/pydatastructs/graphs/_extensions.py b/pydatastructs/graphs/_extensions.py new file mode 100644 index 000000000..3796fa2ad --- /dev/null +++ b/pydatastructs/graphs/_extensions.py @@ -0,0 +1,18 @@ +from setuptools import Extension +import os + +project = 'pydatastructs' + +module = 'graphs' + +backend = '_backend' + +cpp = 'cpp' + +graph = '.'.join([project, module, backend, cpp, '_graph']) +graph_sources = ['/'.join([project, module, backend, cpp, + 'graph.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])] \ No newline at end of file diff --git a/pydatastructs/graphs/adjacency_list.py b/pydatastructs/graphs/adjacency_list.py index cc9de94b6..6eb2e307b 100644 --- a/pydatastructs/graphs/adjacency_list.py +++ b/pydatastructs/graphs/adjacency_list.py @@ -1,4 +1,5 @@ from pydatastructs.graphs.graph import Graph +from pydatastructs.graphs._backend.cpp import _graph from pydatastructs.utils.misc_util import ( GraphEdge, Backend, raise_if_backend_is_not_python) @@ -16,14 +17,20 @@ class AdjacencyList(Graph): pydatastructs.graphs.graph.Graph """ def __new__(cls, *vertices, **kwargs): - raise_if_backend_is_not_python( - cls, kwargs.get('backend', Backend.PYTHON)) - obj = object.__new__(cls) - for vertex in vertices: - obj.__setattr__(vertex.name, vertex) - obj.vertices = [vertex.name for vertex in vertices] - obj.edge_weights = {} - return obj + + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.PYTHON: + obj = object.__new__(cls) + for vertex in vertices: + obj.__setattr__(vertex.name, vertex) + obj.vertices = [vertex.name for vertex in vertices] + obj.edge_weights = {} + return obj + else: + graph = _graph.AdjacencyListGraph() + for vertice in vertices: + graph.add_vertex(vertice) + return graph @classmethod def methods(self): diff --git a/pydatastructs/graphs/graph.py b/pydatastructs/graphs/graph.py index 24f33a17b..acdf0473e 100644 --- a/pydatastructs/graphs/graph.py +++ b/pydatastructs/graphs/graph.py @@ -70,14 +70,15 @@ class Graph(object): __slots__ = ['_impl'] def __new__(cls, *args, **kwargs): - raise_if_backend_is_not_python( - cls, kwargs.get('backend', Backend.PYTHON)) - default_impl = args[0]._impl if args else 'adjacency_list' + backend = kwargs.get('backend', Backend.PYTHON) + try: + default_impl = args[0]._impl if args else 'adjacency_list' + except: + default_impl = 'adjacency_list' implementation = kwargs.get('implementation', default_impl) if implementation == 'adjacency_list': from pydatastructs.graphs.adjacency_list import AdjacencyList - obj = AdjacencyList(*args) - obj._impl = implementation + obj = AdjacencyList(*args, **kwargs) return obj elif implementation == 'adjacency_matrix': from pydatastructs.graphs.adjacency_matrix import AdjacencyMatrix diff --git a/pydatastructs/graphs/tests/tempCodeRunnerFile.py b/pydatastructs/graphs/tests/tempCodeRunnerFile.py new file mode 100644 index 000000000..73e692a0a --- /dev/null +++ b/pydatastructs/graphs/tests/tempCodeRunnerFile.py @@ -0,0 +1 @@ +print(type(v_3)) \ No newline at end of file diff --git a/pydatastructs/graphs/tests/test_adjacency_list.py b/pydatastructs/graphs/tests/test_adjacency_list.py index 3dcef8a7a..acfd013b0 100644 --- a/pydatastructs/graphs/tests/test_adjacency_list.py +++ b/pydatastructs/graphs/tests/test_adjacency_list.py @@ -1,6 +1,7 @@ from pydatastructs.graphs import Graph from pydatastructs.utils import AdjacencyListGraphNode from pydatastructs.utils.raises_util import raises +from pydatastructs.utils.misc_util import Backend def test_adjacency_list(): v_1 = AdjacencyListGraphNode('v_1', 1) @@ -42,3 +43,43 @@ def test_adjacency_list(): assert raises(ValueError, lambda: g.add_edge('u', 'v')) assert raises(ValueError, lambda: g.add_edge('v', 'x')) + + v_4 = AdjacencyListGraphNode('v_4', 4, backend = Backend.CPP) + v_5 = AdjacencyListGraphNode('v_5', 5, backend = Backend.CPP) + g2 = Graph(v_4,v_5,implementation = 'adjacency_list', backend = Backend.CPP) + v_6 = AdjacencyListGraphNode('v_6', 6, backend = Backend.CPP) + assert raises(ValueError, lambda: g2.add_vertex(v_5)) + g2.add_vertex(v_6) + g2.add_edge('v_4', 'v_5') + g2.add_edge('v_5', 'v_6') + g2.add_edge('v_4', 'v_6') + assert g2.is_adjacent('v_4', 'v_5') is True + assert g2.is_adjacent('v_5', 'v_6') is True + assert g2.is_adjacent('v_4', 'v_6') is True + assert g2.is_adjacent('v_5', 'v_4') is False + assert g2.is_adjacent('v_6', 'v_5') is False + assert g2.is_adjacent('v_6', 'v_4') is False + assert g2.num_edges() == 3 + assert g2.num_vertices() == 3 + neighbors = g2.neighbors('v_4') + assert neighbors == [v_6, v_5] + v = AdjacencyListGraphNode('v', 4, backend = Backend.CPP) + g2.add_vertex(v) + g2.add_edge('v_4', 'v', 0) + g2.add_edge('v_5', 'v', 0) + g2.add_edge('v_6', 'v', 0) + assert g2.is_adjacent('v_4', 'v') is True + assert g2.is_adjacent('v_5', 'v') is True + assert g2.is_adjacent('v_6', 'v') is True + e1 = g2.get_edge('v_4', 'v') + e2 = g2.get_edge('v_5', 'v') + e3 = g2.get_edge('v_6', 'v') + assert (str(e1)) == "('v_4', 'v')" + assert (str(e2)) == "('v_5', 'v')" + assert (str(e3)) == "('v_6', 'v')" + g2.remove_edge('v_4', 'v') + assert g2.is_adjacent('v_4', 'v') is False + g2.remove_vertex('v') + assert raises(ValueError, lambda: g2.add_edge('v_4', 'v')) + + diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp index 8aa42f66a..65e54e4fb 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp @@ -187,7 +187,7 @@ static PyMethodDef AdjacencyListGraphNode_methods[] = { {NULL} }; -PyTypeObject AdjacencyListGraphNodeType = { +inline PyTypeObject AdjacencyListGraphNodeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "AdjacencyListGraphNode", /* tp_basicsize */ sizeof(AdjacencyListGraphNode), /* tp_itemsize */ 0, diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp index 8c35754f6..e3c8b664e 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp @@ -26,7 +26,7 @@ static PyObject* AdjacencyMatrixGraphNode_new(PyTypeObject* type, PyObject* args return reinterpret_cast(self); } -PyTypeObject AdjacencyMatrixGraphNodeType = { +inline PyTypeObject AdjacencyMatrixGraphNodeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "AdjacencyMatrixGraphNode", /* tp_basicsize */ sizeof(AdjacencyMatrixGraphNode), /* tp_itemsize */ 0, diff --git a/pydatastructs/utils/_backend/cpp/GraphEdge.hpp b/pydatastructs/utils/_backend/cpp/GraphEdge.hpp index 1ef21008a..3c9234634 100644 --- a/pydatastructs/utils/_backend/cpp/GraphEdge.hpp +++ b/pydatastructs/utils/_backend/cpp/GraphEdge.hpp @@ -64,7 +64,7 @@ static PyObject* GraphEdge_str(GraphEdge* self) { return str_repr; } -PyTypeObject GraphEdgeType = { +inline PyTypeObject GraphEdgeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "GraphEdge", /* tp_basicsize */ sizeof(GraphEdge), /* tp_itemsize */ 0, diff --git a/pydatastructs/utils/_backend/cpp/graph_bindings.hpp b/pydatastructs/utils/_backend/cpp/graph_bindings.hpp new file mode 100644 index 000000000..5c42e3a10 --- /dev/null +++ b/pydatastructs/utils/_backend/cpp/graph_bindings.hpp @@ -0,0 +1,9 @@ +#ifndef GRAPH_BINDINGS_HPP +#define GRAPH_BINDINGS_HPP + +#include + +extern PyTypeObject AdjacencyListGraphType; +extern PyTypeObject AdjacencyListGraphNodeType; + +#endif \ No newline at end of file diff --git a/pydatastructs/utils/_backend/cpp/graph_utils.cpp b/pydatastructs/utils/_backend/cpp/graph_utils.cpp index c00c83b9d..d47390fc1 100644 --- a/pydatastructs/utils/_backend/cpp/graph_utils.cpp +++ b/pydatastructs/utils/_backend/cpp/graph_utils.cpp @@ -3,6 +3,7 @@ #include "AdjacencyListGraphNode.hpp" #include "AdjacencyMatrixGraphNode.hpp" #include "GraphEdge.hpp" +#include "graph_bindings.hpp" static struct PyModuleDef graph_utils_struct = { PyModuleDef_HEAD_INIT, diff --git a/scripts/build/dummy_submodules_data.py b/scripts/build/dummy_submodules_data.py index 115d080a2..1dde15e81 100644 --- a/scripts/build/dummy_submodules_data.py +++ b/scripts/build/dummy_submodules_data.py @@ -1,9 +1,9 @@ project = 'pydatastructs' -modules = ['linear_data_structures', 'miscellaneous_data_structures', 'utils', 'trees'] +modules = ['linear_data_structures', 'miscellaneous_data_structures', 'utils', 'trees', 'graphs'] backend = '_backend' cpp = 'cpp' -dummy_submodules_list = [('_arrays.py', '_algorithms.py'), ('_stack.py',), ('_nodes.py', '_graph_utils.py',), ('_trees.py',)] +dummy_submodules_list = [('_arrays.py', '_algorithms.py'), ('_stack.py',), ('_nodes.py', '_graph_utils.py',), ('_trees.py',), ('_graph.py',)] diff --git a/setup.py b/setup.py index 60c4ec36d..bbe3faef0 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from pydatastructs import linear_data_structures from pydatastructs import miscellaneous_data_structures from pydatastructs import trees +from pydatastructs import graphs with open("README.md", "r") as fh: long_description = fh.read() @@ -13,6 +14,7 @@ extensions.extend(linear_data_structures._extensions.extensions) extensions.extend(miscellaneous_data_structures._extensions.extensions) extensions.extend(trees._extensions.extensions) +extensions.extend(graphs._extensions.extensions) setuptools.setup( name="cz-pydatastructs", From c72a13302eaa40decad8641b4f57036d3942b334 Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Sun, 8 Jun 2025 15:57:40 +0530 Subject: [PATCH 2/8] Adjacency Matrix Graph backend added --- .../graphs/_backend/cpp/AdjacencyList.hpp | 36 ++- .../graphs/_backend/cpp/AdjacencyMatrix.hpp | 252 ++++++++++++++++++ pydatastructs/graphs/_backend/cpp/graph.cpp | 17 +- pydatastructs/graphs/_extensions.py | 2 +- pydatastructs/graphs/adjacency_list.py | 3 +- pydatastructs/graphs/adjacency_matrix.py | 26 +- pydatastructs/graphs/graph.py | 3 +- .../graphs/tests/tempCodeRunnerFile.py | 1 - .../graphs/tests/test_adjacency_list.py | 2 - .../graphs/tests/test_adjacency_matrix.py | 21 ++ .../_backend/cpp/AdjacencyMatrixGraphNode.hpp | 2 + .../utils/_backend/cpp/GraphEdge.hpp | 10 +- .../utils/_backend/cpp/graph_bindings.hpp | 4 +- 13 files changed, 340 insertions(+), 39 deletions(-) create mode 100644 pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp delete mode 100644 pydatastructs/graphs/tests/tempCodeRunnerFile.py diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp index 93b4eae7b..62d6677aa 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -14,6 +14,7 @@ extern PyTypeObject AdjacencyListGraphType; typedef struct { PyObject_HEAD + PyObject* dict; std::vector nodes; std::unordered_map edges; std::unordered_map node_map; @@ -37,7 +38,7 @@ static void AdjacencyListGraph_dealloc(AdjacencyListGraph* self) { } static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { - AdjacencyListGraph* self = (AdjacencyListGraph*)type->tp_alloc(type, 0); + AdjacencyListGraph* self = reinterpret_cast(type->tp_alloc(type, 0)); if (!self) return NULL; @@ -54,11 +55,11 @@ static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyOb self->nodes.~vector(); self->edges.~unordered_map(); self->node_map.~unordered_map(); - Py_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free(reinterpret_cast(self)); return NULL; } - AdjacencyListGraphNode* node = (AdjacencyListGraphNode*)node_obj; + AdjacencyListGraphNode* node = reinterpret_cast(node_obj); if (self->node_map.find(node->name) != self->node_map.end()) { PyErr_Format(PyExc_ValueError, "Duplicate node with name '%s'", node->name.c_str()); @@ -66,7 +67,7 @@ static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyOb self->nodes.~vector(); self->edges.~unordered_map(); self->node_map.~unordered_map(); - Py_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free(reinterpret_cast(self)); return NULL; } @@ -74,7 +75,16 @@ static PyObject* AdjacencyListGraph_new(PyTypeObject* type, PyObject* args, PyOb self->nodes.push_back(node); self->node_map[node->name] = node; } - return (PyObject*)self; + PyObject* impl_str = PyUnicode_FromString("adjacency_list"); + + if (PyObject_SetAttrString(reinterpret_cast(self), "_impl", impl_str) < 0) { + Py_DECREF(impl_str); + PyErr_SetString(PyExc_RuntimeError, "Failed to set _impl attribute"); + return NULL; + } + + Py_DECREF(impl_str); + return reinterpret_cast(self); } static std::string make_edge_key(const std::string& source, const std::string& target) { @@ -94,7 +104,7 @@ static PyObject* AdjacencyListGraph_add_vertex(AdjacencyListGraph* self, PyObjec return NULL; } - AdjacencyListGraphNode* node = (AdjacencyListGraphNode*)node_obj; + AdjacencyListGraphNode* node = reinterpret_cast(node_obj); if (self->node_map.find(node->name) != self->node_map.end()) { PyErr_SetString(PyExc_ValueError, "Node with this name already exists"); @@ -228,7 +238,7 @@ static PyObject* AdjacencyListGraph_get_edge(AdjacencyListGraph* self, PyObject* auto it = self->edges.find(key); if (it != self->edges.end()) { Py_INCREF(it->second); - return (PyObject*)it->second; + return reinterpret_cast(it->second); } Py_RETURN_NONE; @@ -293,10 +303,10 @@ static PyObject* AdjacencyListGraph_add_edge(AdjacencyListGraph* self, PyObject* AdjacencyListGraphNode* source_node = self->node_map[source]; AdjacencyListGraphNode* target_node = self->node_map[target]; - PyObject* edge_args = PyTuple_Pack(3, (PyObject*)source_node, (PyObject*)target_node, value); + PyObject* edge_args = PyTuple_Pack(3, reinterpret_cast(source_node), reinterpret_cast(target_node), value); if (!edge_args) return NULL; - PyObject* edge_obj = PyObject_CallObject((PyObject*)&GraphEdgeType, edge_args); + PyObject* edge_obj = PyObject_CallObject(reinterpret_cast(&GraphEdgeType), edge_args); Py_DECREF(edge_args); if (!edge_obj) @@ -307,15 +317,15 @@ static PyObject* AdjacencyListGraph_add_edge(AdjacencyListGraph* self, PyObject* auto it = self->edges.find(key); if (it != self->edges.end()) { Py_XDECREF(it->second); - it->second = (GraphEdge*)edge_obj; + it->second = reinterpret_cast(edge_obj); } else { - self->edges[key] = (GraphEdge*)edge_obj; + self->edges[key] = reinterpret_cast(edge_obj); } auto adj_it = source_node->adjacent.find(target); if (adj_it == source_node->adjacent.end()) { Py_INCREF(target_node); - source_node->adjacent[target] = (PyObject*)target_node; + source_node->adjacent[target] = reinterpret_cast(target_node); } Py_RETURN_NONE; @@ -349,7 +359,7 @@ PyTypeObject AdjacencyListGraphType = { .tp_doc = "Adjacency List Graph data structure", .tp_methods = AdjacencyListGraph_methods, .tp_new = AdjacencyListGraph_new, + .tp_dictoffset = offsetof(AdjacencyListGraph, dict) }; #endif - diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp new file mode 100644 index 000000000..ab0b00e29 --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp @@ -0,0 +1,252 @@ +#ifndef ADJACENCY_MATRIX_GRAPH_HPP +#define ADJACENCY_MATRIX_GRAPH_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include +#include "AdjacencyMatrixGraphNode.hpp" +#include "GraphEdge.hpp" +#include "GraphNode.hpp" + +extern PyTypeObject AdjacencyMatrixGraphNodeType; + +typedef struct { + PyObject_HEAD + PyObject* dict; + std::vector nodes; + std::unordered_map > matrix; + std::unordered_map node_map; + std::unordered_map edge_weights; +} AdjacencyMatrixGraph; + +static void AdjacencyMatrixGraph_dealloc(AdjacencyMatrixGraph* self) +{ + for (auto& pair : self->edge_weights) { + Py_XDECREF(pair.second); + } + self->edge_weights.clear(); + + for (AdjacencyMatrixGraphNode* node : self->nodes) { + Py_XDECREF(node); + } + self->nodes.clear(); + + self->node_map.clear(); + self->matrix.clear(); + + Py_XDECREF(self->dict); + + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +static PyObject* AdjacencyMatrixGraph_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + AdjacencyMatrixGraph* self; + self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != NULL) { + new (&self->nodes) std::vector(); + new (&self->node_map) std::unordered_map(); + new (&self->matrix) std::unordered_map >(); + new (&self->edge_weights) std::unordered_map(); + + PyObject* vertices; + if (!PyArg_ParseTuple(args, "O", &vertices)) { + Py_DECREF(self); + return NULL; + } + + if (!PyTuple_Check(vertices)) { + PyErr_SetString(PyExc_TypeError, "Expected a tuple of vertices"); + Py_DECREF(self); + return NULL; + } + + Py_ssize_t len = PyTuple_Size(vertices); + for (Py_ssize_t i = 0; i < len; ++i) { + PyObject* item = PyTuple_GetItem(vertices, i); + if (!PyObject_TypeCheck(item, &AdjacencyMatrixGraphNodeType)) { + PyErr_SetString(PyExc_TypeError, "All elements must be AdjacencyMatrixGraphNode instances"); + Py_DECREF(self); + return NULL; + } + Py_INCREF(item); + AdjacencyMatrixGraphNode* node = reinterpret_cast(item); + std::string name =(reinterpret_cast(node))->name; + self->nodes.push_back(node); + self->node_map[name] = node; + self->matrix[name] = std::unordered_map(); + } + + PyObject* impl_str = PyUnicode_FromString("adjacency_matrix"); + + if (PyObject_SetAttrString(reinterpret_cast(self), "_impl", impl_str) < 0) { + Py_DECREF(impl_str); + PyErr_SetString(PyExc_RuntimeError, "Failed to set _impl attribute"); + return NULL; + } + + Py_DECREF(impl_str); + } + return reinterpret_cast(self); +} + +static PyObject* AdjacencyMatrixGraph_add_edge(AdjacencyMatrixGraph* self, PyObject* args, PyObject* kwds) +{ + const char *source, *target; + PyObject *cost_obj = Py_None; + static char* kwlist[] = {"source", "target", "cost", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss|O", kwlist, &source, &target, &cost_obj)) + return NULL; + + std::string src(source); + std::string dst(target); + + if (self->matrix.find(src) == self->matrix.end() || + self->matrix.find(dst) == self->matrix.end()) { + PyErr_Format(PyExc_ValueError, "Vertex %s or %s not in graph", source, target); + return NULL; + } + + self->matrix[src][dst] = true; + + if (cost_obj != Py_None) { + double cost = PyFloat_AsDouble(cost_obj); + if (PyErr_Occurred()) return NULL; + + PyObject* cost_pyfloat = PyFloat_FromDouble(cost); + if (!cost_pyfloat) return NULL; + + PyObject* args = Py_BuildValue("OOO", reinterpret_cast(self->node_map[src]), reinterpret_cast(self->node_map[dst]), cost_pyfloat); + Py_DECREF(cost_pyfloat); + if (!args) return NULL; + + PyObject* edge_obj = PyObject_CallObject(reinterpret_cast(&GraphEdgeType), args); + Py_DECREF(args); + if (!edge_obj) return NULL; + + self->edge_weights[src + "_" + dst] = reinterpret_cast(edge_obj); + } + + Py_RETURN_NONE; +} + +static PyObject* AdjacencyMatrixGraph_remove_edge(AdjacencyMatrixGraph* self, PyObject* args) +{ + const char *source, *target; + if (!PyArg_ParseTuple(args, "ss", &source, &target)) + return NULL; + + std::string src(source); + std::string dst(target); + + if (self->matrix.find(src) != self->matrix.end()) { + self->matrix[src][dst] = false; + self->edge_weights.erase(src + "_" + dst); + } + + Py_RETURN_NONE; +} + +static PyObject* AdjacencyMatrixGraph_neighbors(AdjacencyMatrixGraph* self, PyObject* args) +{ + const char *node; + if (!PyArg_ParseTuple(args, "s", &node)) { + return NULL; + } + + std::string key(node); + if (self->matrix.find(key) == self->matrix.end()) { + PyErr_SetString(PyExc_KeyError, "Node not found"); + return NULL; + } + + PyObject* list = PyList_New(0); + for (const auto& pair : self->matrix[key]) { + if (pair.second) { + const std::string& neighbor_name = pair.first; + AdjacencyMatrixGraphNode* neighbor = self->node_map[neighbor_name]; + Py_INCREF(neighbor); + PyList_Append(list, (PyObject*)neighbor); + } + } + return list; +} + + +static PyObject* AdjacencyMatrixGraph_num_vertices(AdjacencyMatrixGraph* self, PyObject* Py_UNUSED(ignored)) +{ + return PyLong_FromSize_t(self->nodes.size()); +} + +static PyObject* AdjacencyMatrixGraph_num_edges(AdjacencyMatrixGraph* self, PyObject* Py_UNUSED(ignored)) +{ + size_t count = 0; + for (const auto& row : self->matrix) { + for (const auto& entry : row.second) { + if (entry.second) + count++; + } + } + return PyLong_FromSize_t(count); +} + +static PyObject* AdjacencyMatrixGraph_get_edge(AdjacencyMatrixGraph* self, PyObject* args) +{ + const char *source, *target; + if (!PyArg_ParseTuple(args, "ss", &source, &target)) + return NULL; + + std::string key = std::string(source) + "_" + std::string(target); + auto it = self->edge_weights.find(key); + if (it != self->edge_weights.end()) { + return reinterpret_cast(it->second); + } + Py_RETURN_NONE; +} + +static PyObject* AdjacencyMatrixGraph_is_adjacent(AdjacencyMatrixGraph* self, PyObject* args) +{ + const char *source, *target; + if (!PyArg_ParseTuple(args, "ss", &source, &target)) + return NULL; + + std::string src(source); + std::string dst(target); + if (self->matrix.find(src) == self->matrix.end()) + Py_RETURN_FALSE; + + auto& row = self->matrix[src]; + if (row.find(dst) != row.end() && row[dst]) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +static PyMethodDef AdjacencyMatrixGraph_methods[] = { + {"add_edge", (PyCFunction)AdjacencyMatrixGraph_add_edge, METH_VARARGS | METH_KEYWORDS, "Add an edge between two nodes."}, + {"remove_edge", (PyCFunction)AdjacencyMatrixGraph_remove_edge, METH_VARARGS, "Remove an edge between two nodes."}, + {"neighbors", (PyCFunction)AdjacencyMatrixGraph_neighbors, METH_VARARGS, "Return neighbors of a node."}, + {"num_vertices", (PyCFunction)AdjacencyMatrixGraph_num_vertices, METH_NOARGS, "Return number of vertices."}, + {"num_edges", (PyCFunction)AdjacencyMatrixGraph_num_edges, METH_NOARGS, "Return number of edges."}, + {"get_edge", (PyCFunction)AdjacencyMatrixGraph_get_edge, METH_VARARGS, "Return the edge object between two nodes if exists."}, + {"is_adjacent", (PyCFunction)AdjacencyMatrixGraph_is_adjacent, METH_VARARGS, "Check if there is an edge between two nodes."}, + {NULL} +}; + +PyTypeObject AdjacencyMatrixGraphType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_graph.AdjacencyMatrixGraph", + .tp_basicsize = sizeof(AdjacencyMatrixGraph), + .tp_itemsize = 0, + .tp_dealloc = (destructor)AdjacencyMatrixGraph_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = "Adjacency matrix graph", + .tp_methods = AdjacencyMatrixGraph_methods, + .tp_new = AdjacencyMatrixGraph_new, + .tp_dictoffset = offsetof(AdjacencyMatrixGraph, dict), +}; + +#endif diff --git a/pydatastructs/graphs/_backend/cpp/graph.cpp b/pydatastructs/graphs/_backend/cpp/graph.cpp index 9a12d94ef..5d04b1d15 100644 --- a/pydatastructs/graphs/_backend/cpp/graph.cpp +++ b/pydatastructs/graphs/_backend/cpp/graph.cpp @@ -1,7 +1,9 @@ #define PY_SSIZE_T_CLEAN #include #include "AdjacencyList.hpp" +#include "AdjacencyMatrix.hpp" #include "AdjacencyListGraphNode.hpp" +#include "AdjacencyMatrixGraphNode.hpp" #include "graph_bindings.hpp" @@ -23,6 +25,12 @@ PyMODINIT_FUNC PyInit__graph(void) { if (PyType_Ready(&AdjacencyListGraphNodeType) < 0) return NULL; + if (PyType_Ready(&AdjacencyMatrixGraphType) < 0) + return NULL; + + if (PyType_Ready(&AdjacencyMatrixGraphNodeType) < 0) + return NULL; + m = PyModule_Create(&graph_module); if (m == NULL) return NULL; @@ -41,5 +49,12 @@ PyMODINIT_FUNC PyInit__graph(void) { return NULL; } + Py_INCREF(&AdjacencyMatrixGraphType); + if (PyModule_AddObject(m, "AdjacencyMatrixGraph", (PyObject*)&AdjacencyMatrixGraphType) < 0) { + Py_DECREF(&AdjacencyMatrixGraphType); + Py_DECREF(m); + return NULL; + } + return m; -} \ No newline at end of file +} diff --git a/pydatastructs/graphs/_extensions.py b/pydatastructs/graphs/_extensions.py index 3796fa2ad..b3ddcd6f1 100644 --- a/pydatastructs/graphs/_extensions.py +++ b/pydatastructs/graphs/_extensions.py @@ -15,4 +15,4 @@ include_dir = os.path.abspath(os.path.join(project, 'utils', '_backend', 'cpp')) -extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir])] \ No newline at end of file +extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir])] diff --git a/pydatastructs/graphs/adjacency_list.py b/pydatastructs/graphs/adjacency_list.py index 6eb2e307b..bd901b380 100644 --- a/pydatastructs/graphs/adjacency_list.py +++ b/pydatastructs/graphs/adjacency_list.py @@ -17,7 +17,7 @@ class AdjacencyList(Graph): pydatastructs.graphs.graph.Graph """ def __new__(cls, *vertices, **kwargs): - + backend = kwargs.get('backend', Backend.PYTHON) if backend == Backend.PYTHON: obj = object.__new__(cls) @@ -25,6 +25,7 @@ def __new__(cls, *vertices, **kwargs): obj.__setattr__(vertex.name, vertex) obj.vertices = [vertex.name for vertex in vertices] obj.edge_weights = {} + obj._impl = 'adjacency_list' return obj else: graph = _graph.AdjacencyListGraph() diff --git a/pydatastructs/graphs/adjacency_matrix.py b/pydatastructs/graphs/adjacency_matrix.py index 48e0d3489..9c2326b86 100644 --- a/pydatastructs/graphs/adjacency_matrix.py +++ b/pydatastructs/graphs/adjacency_matrix.py @@ -1,4 +1,5 @@ from pydatastructs.graphs.graph import Graph +from pydatastructs.graphs._backend.cpp import _graph from pydatastructs.utils.misc_util import ( GraphEdge, raise_if_backend_is_not_python, Backend) @@ -17,17 +18,20 @@ class AdjacencyMatrix(Graph): pydatastructs.graphs.graph.Graph """ def __new__(cls, *vertices, **kwargs): - raise_if_backend_is_not_python( - cls, kwargs.get('backend', Backend.PYTHON)) - obj = object.__new__(cls) - obj.vertices = [vertex.name for vertex in vertices] - for vertex in vertices: - obj.__setattr__(vertex.name, vertex) - obj.matrix = {} - for vertex in vertices: - obj.matrix[vertex.name] = {} - obj.edge_weights = {} - return obj + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.PYTHON: + obj = object.__new__(cls) + obj.vertices = [vertex.name for vertex in vertices] + for vertex in vertices: + obj.__setattr__(vertex.name, vertex) + obj.matrix = {} + for vertex in vertices: + obj.matrix[vertex.name] = {} + obj.edge_weights = {} + obj._impl = 'adjacency_matrix' + return obj + else: + return _graph.AdjacencyMatrixGraph(vertices) @classmethod def methods(self): diff --git a/pydatastructs/graphs/graph.py b/pydatastructs/graphs/graph.py index acdf0473e..39c2692e3 100644 --- a/pydatastructs/graphs/graph.py +++ b/pydatastructs/graphs/graph.py @@ -82,8 +82,7 @@ def __new__(cls, *args, **kwargs): return obj elif implementation == 'adjacency_matrix': from pydatastructs.graphs.adjacency_matrix import AdjacencyMatrix - obj = AdjacencyMatrix(*args) - obj._impl = implementation + obj = AdjacencyMatrix(*args, **kwargs) return obj else: raise NotImplementedError("%s implementation is not a part " diff --git a/pydatastructs/graphs/tests/tempCodeRunnerFile.py b/pydatastructs/graphs/tests/tempCodeRunnerFile.py deleted file mode 100644 index 73e692a0a..000000000 --- a/pydatastructs/graphs/tests/tempCodeRunnerFile.py +++ /dev/null @@ -1 +0,0 @@ -print(type(v_3)) \ No newline at end of file diff --git a/pydatastructs/graphs/tests/test_adjacency_list.py b/pydatastructs/graphs/tests/test_adjacency_list.py index acfd013b0..6f82763ac 100644 --- a/pydatastructs/graphs/tests/test_adjacency_list.py +++ b/pydatastructs/graphs/tests/test_adjacency_list.py @@ -81,5 +81,3 @@ def test_adjacency_list(): assert g2.is_adjacent('v_4', 'v') is False g2.remove_vertex('v') assert raises(ValueError, lambda: g2.add_edge('v_4', 'v')) - - diff --git a/pydatastructs/graphs/tests/test_adjacency_matrix.py b/pydatastructs/graphs/tests/test_adjacency_matrix.py index 2dace4260..27dc81790 100644 --- a/pydatastructs/graphs/tests/test_adjacency_matrix.py +++ b/pydatastructs/graphs/tests/test_adjacency_matrix.py @@ -1,6 +1,7 @@ from pydatastructs.graphs import Graph from pydatastructs.utils import AdjacencyMatrixGraphNode from pydatastructs.utils.raises_util import raises +from pydatastructs.utils.misc_util import Backend def test_AdjacencyMatrix(): v_0 = AdjacencyMatrixGraphNode(0, 0) @@ -30,3 +31,23 @@ def test_AdjacencyMatrix(): assert raises(ValueError, lambda: g.add_edge('v', 'x')) assert raises(ValueError, lambda: g.add_edge(2, 3)) assert raises(ValueError, lambda: g.add_edge(3, 2)) + + v_3 = AdjacencyMatrixGraphNode('0', 0, backend = Backend.CPP) + v_4 = AdjacencyMatrixGraphNode('1', 1, backend = Backend.CPP) + v_5 = AdjacencyMatrixGraphNode('2', 2, backend = Backend.CPP) + g2 = Graph(v_3, v_4, v_5, implementation = 'adjacency_matrix', backend = Backend.CPP) + g2.add_edge('0', '1', 0) + g2.add_edge('1', '2', 0) + g2.add_edge('2', '0', 0) + assert g2.is_adjacent('0', '1') is True + assert g2.is_adjacent('1', '2') is True + assert g2.is_adjacent('2', '0') is True + assert g2.is_adjacent('1', '0') is False + assert g2.is_adjacent('2', '1') is False + assert g2.is_adjacent('0', '2') is False + neighbors = g2.neighbors('0') + assert neighbors == [v_4] + g2.remove_edge('0', '1') + assert g2.is_adjacent('0', '1') is False + assert raises(ValueError, lambda: g2.add_edge('u', 'v')) + assert raises(ValueError, lambda: g2.add_edge('v', 'x')) diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp index e3c8b664e..adf8573c3 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp @@ -6,6 +6,8 @@ #include #include "GraphNode.hpp" +extern PyTypeObject AdjacencyMatrixGraphNodeType; + typedef struct { GraphNode super; } AdjacencyMatrixGraphNode; diff --git a/pydatastructs/utils/_backend/cpp/GraphEdge.hpp b/pydatastructs/utils/_backend/cpp/GraphEdge.hpp index 3c9234634..757bd89bd 100644 --- a/pydatastructs/utils/_backend/cpp/GraphEdge.hpp +++ b/pydatastructs/utils/_backend/cpp/GraphEdge.hpp @@ -49,18 +49,16 @@ static PyObject* GraphEdge_new(PyTypeObject* type, PyObject* args, PyObject* kwd } static PyObject* GraphEdge_str(GraphEdge* self) { - PyObject* source_name = PyObject_GetAttrString(self->source, "name"); - PyObject* target_name = PyObject_GetAttrString(self->target, "name"); + std::string source_name = (reinterpret_cast(self->source))->name; + std::string target_name = (reinterpret_cast(self->target))->name; - if (!source_name || !target_name) { + if (source_name.empty() || target_name.empty()) { PyErr_SetString(PyExc_AttributeError, "Both nodes must have a 'name' attribute."); return NULL; } - PyObject* str_repr = PyUnicode_FromFormat("('%U', '%U')", source_name, target_name); + PyObject* str_repr = PyUnicode_FromFormat("('%s', '%s')", source_name.c_str(), target_name.c_str()); - Py_XDECREF(source_name); - Py_XDECREF(target_name); return str_repr; } diff --git a/pydatastructs/utils/_backend/cpp/graph_bindings.hpp b/pydatastructs/utils/_backend/cpp/graph_bindings.hpp index 5c42e3a10..aeabb5ab7 100644 --- a/pydatastructs/utils/_backend/cpp/graph_bindings.hpp +++ b/pydatastructs/utils/_backend/cpp/graph_bindings.hpp @@ -5,5 +5,7 @@ extern PyTypeObject AdjacencyListGraphType; extern PyTypeObject AdjacencyListGraphNodeType; +extern PyTypeObject AdjacencyMatrixGraphType; +extern PyTypeObject AdjacencyMatrixGraphNodeType; -#endif \ No newline at end of file +#endif From 1b7040ed0f1d230111f3fa4947282e2b4cb6d33e Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Sun, 8 Jun 2025 16:24:43 +0530 Subject: [PATCH 3/8] bug fix --- _graph.cp | 0 _graph.cpp | 0 graph.cpp | 0 requirements.txt | 1 + 4 files changed, 1 insertion(+) delete mode 100644 _graph.cp delete mode 100644 _graph.cpp delete mode 100644 graph.cpp diff --git a/_graph.cp b/_graph.cp deleted file mode 100644 index e69de29bb..000000000 diff --git a/_graph.cpp b/_graph.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/graph.cpp b/graph.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/requirements.txt b/requirements.txt index 0ea18bb95..a8b867d7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ codecov +pytest pytest-cov From 7808ff71e6b885beb09d93a1603f41fb546aec2f Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Sun, 8 Jun 2025 23:29:15 +0530 Subject: [PATCH 4/8] BLD: Update method to install pytest --- .github/workflows/ci.yml | 22 +++++++++++----------- environment.yml | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b16910bea..25963d7b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,9 @@ jobs: - name: Install requirements run: | - pip install -r requirements.txt - pip install -r docs/requirements.txt - + python -m pip install -r requirements.txt + python -m pip install -r docs/requirements.txt + - name: Install lcov run: | sudo apt-get update @@ -42,7 +42,7 @@ jobs: - name: Build package run: | CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py -# coverage tests +# coverage tests - name: Run tests run: | python -m pytest --doctest-modules --cov=./ --cov-report=xml -s @@ -50,7 +50,7 @@ jobs: - name: Capture Coverage Data with lcov run: | lcov --capture --directory . --output-file coverage.info --no-external - + - name: Generate HTML Coverage Report with genhtml run: | genhtml coverage.info --output-directory coverage_report @@ -97,8 +97,8 @@ jobs: - name: Install requirements run: | - pip install -r requirements.txt - pip install -r docs/requirements.txt + python -m pip install -r requirements.txt + python -m pip install -r docs/requirements.txt - name: Build package run: | @@ -138,8 +138,8 @@ jobs: - name: Install requirements run: | - pip install -r requirements.txt - pip install -r docs/requirements.txt + python -m pip install -r requirements.txt + python -m pip install -r docs/requirements.txt - name: Build package run: | @@ -186,8 +186,8 @@ jobs: - name: Install requirements run: | - pip install -r requirements.txt - pip install -r docs/requirements.txt + python -m pip install -r requirements.txt + python -m pip install -r docs/requirements.txt - name: Build package run: | diff --git a/environment.yml b/environment.yml index ba0dc8c1b..2d2ce160d 100644 --- a/environment.yml +++ b/environment.yml @@ -5,6 +5,7 @@ channels: dependencies: - python=3.8 - pip + - pytest - pip: - codecov - pytest-cov From 0c399b451e75e3e2848b40aabd83243c858b131f Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Mon, 9 Jun 2025 21:26:43 +0530 Subject: [PATCH 5/8] bug fix --- .github/workflows/ci.yml | 7 +-- .../graphs/_backend/cpp/AdjacencyList.hpp | 50 +++++++++++++++---- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25963d7b1..8f89b2d8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: Build package run: | CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py -# coverage tests + # coverage tests - name: Run tests run: | python -m pytest --doctest-modules --cov=./ --cov-report=xml -s @@ -102,7 +102,7 @@ jobs: - name: Build package run: | - python scripts/build/install.py + CXXFLAGS="-std=c++17 python scripts/build/install.py - name: Run tests run: | @@ -143,7 +143,7 @@ jobs: - name: Build package run: | - python scripts/build/install.py + CXXFLAGS="-std=c++17 python scripts/build/install.py - name: Run tests run: | @@ -191,6 +191,7 @@ jobs: - name: Build package run: | + set CL=/std:c++17 python scripts/build/install.py - name: Run tests diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp index 62d6677aa..29d53aeea 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "AdjacencyListGraphNode.hpp" #include "GraphEdge.hpp" @@ -349,17 +350,44 @@ static PyMethodDef AdjacencyListGraph_methods[] = { PyTypeObject AdjacencyListGraphType = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_init= 0, - .tp_name = "_graph.AdjacencyListGraph", - .tp_basicsize = sizeof(AdjacencyListGraph), - .tp_itemsize = 0, - .tp_dealloc = (destructor)AdjacencyListGraph_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_doc = "Adjacency List Graph data structure", - .tp_methods = AdjacencyListGraph_methods, - .tp_new = AdjacencyListGraph_new, - .tp_dictoffset = offsetof(AdjacencyListGraph, dict) + PyVarObject_HEAD_INIT(NULL, 0) // ob_base + "_graph.AdjacencyListGraph", // tp_name + sizeof(AdjacencyListGraph), // tp_basicsize + 0, // tp_itemsize + (destructor)AdjacencyListGraph_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset or tp_print (depends on Python version) + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_reserved + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags + "Adjacency List Graph data structure", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + AdjacencyListGraph_methods, // tp_methods + 0, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + offsetof(AdjacencyListGraph, dict), // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + AdjacencyListGraph_new // tp_new }; #endif From d85853517b1e9d36fe993d402c09e5e53689bc2f Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Mon, 9 Jun 2025 21:37:11 +0530 Subject: [PATCH 6/8] bug fix --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f89b2d8a..41e6a04cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: - name: Build package run: | - CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py + CXXFLAGS="-std=c++17 --coverage" CFLAGS="--coverage" python scripts/build/install.py # coverage tests - name: Run tests run: | @@ -102,7 +102,7 @@ jobs: - name: Build package run: | - CXXFLAGS="-std=c++17 python scripts/build/install.py + CXXFLAGS="-std=c++17" python scripts/build/install.py - name: Run tests run: | @@ -143,7 +143,7 @@ jobs: - name: Build package run: | - CXXFLAGS="-std=c++17 python scripts/build/install.py + CXXFLAGS="-std=c++17" python scripts/build/install.py - name: Run tests run: | From 3fc8e033206e1071203a63142047171e48b8f92d Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Mon, 9 Jun 2025 21:44:18 +0530 Subject: [PATCH 7/8] bug fix --- .github/workflows/ci.yml | 3 +- .../graphs/_backend/cpp/AdjacencyMatrix.hpp | 30 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41e6a04cf..664a7758e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,8 +190,9 @@ jobs: python -m pip install -r docs/requirements.txt - name: Build package + env: + CL: "/std:c++17" run: | - set CL=/std:c++17 python scripts/build/install.py - name: Run tests diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp index ab0b00e29..f14e47994 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp @@ -242,11 +242,39 @@ PyTypeObject AdjacencyMatrixGraphType = { .tp_basicsize = sizeof(AdjacencyMatrixGraph), .tp_itemsize = 0, .tp_dealloc = (destructor)AdjacencyMatrixGraph_dealloc, + .tp_print = 0, + .tp_getattr = 0, + .tp_setattr = 0, + .tp_as_async = 0, + .tp_repr = 0, + .tp_as_number = 0, + .tp_as_sequence = 0, + .tp_as_mapping = 0, + .tp_hash = 0, + .tp_call = 0, + .tp_str = 0, + .tp_getattro = 0, + .tp_setattro = 0, + .tp_as_buffer = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = "Adjacency matrix graph", + .tp_traverse = 0, + .tp_clear = 0, + .tp_richcompare = 0, + .tp_weaklistoffset = 0, + .tp_iter = 0, + .tp_iternext = 0, .tp_methods = AdjacencyMatrixGraph_methods, - .tp_new = AdjacencyMatrixGraph_new, + .tp_members = 0, + .tp_getset = 0, + .tp_base = 0, + .tp_dict = 0, + .tp_descr_get = 0, + .tp_descr_set = 0, .tp_dictoffset = offsetof(AdjacencyMatrixGraph, dict), + .tp_init = 0, + .tp_alloc = 0, + .tp_new = AdjacencyMatrixGraph_new, }; #endif From 77c9f0281f61f7b12e9529f8668d8dc85b33a9b1 Mon Sep 17 00:00:00 2001 From: Prerak Singh Date: Mon, 9 Jun 2025 21:52:54 +0530 Subject: [PATCH 8/8] bug fix --- .../graphs/_backend/cpp/AdjacencyList.hpp | 2 +- .../graphs/_backend/cpp/AdjacencyMatrix.hpp | 76 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp index 29d53aeea..5a1c3260b 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -370,7 +370,7 @@ PyTypeObject AdjacencyListGraphType = { 0, // tp_setattro 0, // tp_as_buffer Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags - "Adjacency List Graph data structure", // tp_doc + "Adjacency List Graph", // tp_doc 0, // tp_traverse 0, // tp_clear 0, // tp_richcompare diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp index f14e47994..0222f0b88 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp @@ -237,44 +237,44 @@ static PyMethodDef AdjacencyMatrixGraph_methods[] = { }; PyTypeObject AdjacencyMatrixGraphType = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_graph.AdjacencyMatrixGraph", - .tp_basicsize = sizeof(AdjacencyMatrixGraph), - .tp_itemsize = 0, - .tp_dealloc = (destructor)AdjacencyMatrixGraph_dealloc, - .tp_print = 0, - .tp_getattr = 0, - .tp_setattr = 0, - .tp_as_async = 0, - .tp_repr = 0, - .tp_as_number = 0, - .tp_as_sequence = 0, - .tp_as_mapping = 0, - .tp_hash = 0, - .tp_call = 0, - .tp_str = 0, - .tp_getattro = 0, - .tp_setattro = 0, - .tp_as_buffer = 0, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_doc = "Adjacency matrix graph", - .tp_traverse = 0, - .tp_clear = 0, - .tp_richcompare = 0, - .tp_weaklistoffset = 0, - .tp_iter = 0, - .tp_iternext = 0, - .tp_methods = AdjacencyMatrixGraph_methods, - .tp_members = 0, - .tp_getset = 0, - .tp_base = 0, - .tp_dict = 0, - .tp_descr_get = 0, - .tp_descr_set = 0, - .tp_dictoffset = offsetof(AdjacencyMatrixGraph, dict), - .tp_init = 0, - .tp_alloc = 0, - .tp_new = AdjacencyMatrixGraph_new, + PyVarObject_HEAD_INIT(NULL, 0) // ob_base + "_graph.AdjacencyMatrixGraph", // tp_name + sizeof(AdjacencyMatrixGraph), // tp_basicsize + 0, // tp_itemsize + (destructor)AdjacencyMatrixGraph_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset or tp_print (removed in Python 3.9) + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_reserved + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags + "Adjacency matrix graph", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + AdjacencyMatrixGraph_methods, // tp_methods + 0, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + offsetof(AdjacencyMatrixGraph, dict), // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + AdjacencyMatrixGraph_new // tp_new }; #endif