Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion pydatastructs/graphs/_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])]
extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"])]
65 changes: 56 additions & 9 deletions pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
#include <string>
#include <unordered_map>
#include "GraphNode.hpp"
#include <variant>

extern PyTypeObject AdjacencyListGraphNodeType;

typedef struct {
PyObject_HEAD
std::string name;
PyObject* data;
std::variant<std::monostate, int64_t, double, std::string> data;
DataType data_type;
std::unordered_map<std::string, PyObject*> adjacent;
} AdjacencyListGraphNode;

static void AdjacencyListGraphNode_dealloc(AdjacencyListGraphNode* self) {
Py_XDECREF(self->data);
for (auto& pair : self->adjacent) {
Py_XDECREF(pair.second);
}
Expand All @@ -30,6 +31,9 @@ static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args,
if (!self) return NULL;
new (&self->adjacent) std::unordered_map<std::string, PyObject*>();
new (&self->name) std::string();
new (&self->data) std::variant<std::monostate, int64_t, double, std::string>();
self->data_type = DataType::None;
self->data = std::monostate{};

static char* kwlist[] = { "name", "data", "adjacency_list", NULL };
const char* name;
Expand All @@ -42,8 +46,24 @@ static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args,
}

self->name = std::string(name);
Py_INCREF(data);
self->data = data;

if (data == Py_None) {
self->data_type = DataType::None;
self->data = std::monostate{};
} else if (PyLong_Check(data)) {
self->data_type = DataType::Int;
self->data = static_cast<int64_t>(PyLong_AsLongLong(data));
} else if (PyFloat_Check(data)) {
self->data_type = DataType::Double;
self->data = PyFloat_AsDouble(data);
} else if (PyUnicode_Check(data)) {
const char* str = PyUnicode_AsUTF8(data);
self->data_type = DataType::String;
self->data = std::string(str);
} else {
PyErr_SetString(PyExc_TypeError, "Unsupported data type. Must be int, float, str, or None.");
return NULL;
}

if (PyList_Check(adjacency_list)) {
Py_ssize_t size = PyList_Size(adjacency_list);
Expand Down Expand Up @@ -127,14 +147,41 @@ static int AdjacencyListGraphNode_set_name(AdjacencyListGraphNode* self, PyObjec
}

static PyObject* AdjacencyListGraphNode_get_data(AdjacencyListGraphNode* self, void* closure) {
Py_INCREF(self->data);
return self->data;
switch (self->data_type) {
case DataType::Int:
return PyLong_FromLongLong(std::get<int64_t>(self->data));
case DataType::Double:
return PyFloat_FromDouble(std::get<double>(self->data));
case DataType::String:
return PyUnicode_FromString(std::get<std::string>(self->data).c_str());
case DataType::None:
default:
Py_RETURN_NONE;
}
}

static int AdjacencyListGraphNode_set_data(AdjacencyListGraphNode* self, PyObject* value, void* closure) {
Py_XDECREF(self->data);
Py_INCREF(value);
self->data = value;
if (value == Py_None) {
self->data_type = DataType::None;
self->data = std::monostate{};
} else if (PyLong_Check(value)) {
self->data_type = DataType::Int;
self->data = static_cast<int64_t>(PyLong_AsLongLong(value));
} else if (PyFloat_Check(value)) {
self->data_type = DataType::Double;
self->data = PyFloat_AsDouble(value);
} else if (PyUnicode_Check(value)) {
const char* str = PyUnicode_AsUTF8(value);
if (!str) {
PyErr_SetString(PyExc_ValueError, "Invalid UTF-8 string.");
return -1;
}
self->data_type = DataType::String;
self->data = std::string(str);
} else {
PyErr_SetString(PyExc_TypeError, "Unsupported data type. Must be int, float, str, or None.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other cases set to normal PyObject data type.

return -1;
}
return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ typedef struct {
} AdjacencyMatrixGraphNode;

static void AdjacencyMatrixGraphNode_dealloc(AdjacencyMatrixGraphNode* self){
Py_XDECREF(self->super.data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is PyObject then keep this inside if.

Py_TYPE(self)->tp_free(reinterpret_cast<PyTypeObject*>(self));
}

Expand Down
117 changes: 98 additions & 19 deletions pydatastructs/utils/_backend/cpp/GraphNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,32 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <string>
#include <variant>

enum class DataType {
None,
Int,
Double,
String
};

typedef struct {
PyObject_HEAD
std::string name;
PyObject* data;
std::variant<std::monostate, int64_t, double, std::string> data;
DataType data_type;
} GraphNode;

static void GraphNode_dealloc(GraphNode* self){
Py_XDECREF(self->data);
Py_TYPE(self)->tp_free(reinterpret_cast<PyTypeObject*>(self));
}

static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds){
GraphNode* self;
self = reinterpret_cast<GraphNode*>(type->tp_alloc(type,0));
new (&self->name) std::string();
new (&self->data) std::variant<std::monostate, int64_t, double, std::string>();
self->data_type = DataType::None;
if (!self) return NULL;

static char* kwlist[] = { "name", "data", NULL };
Expand All @@ -32,54 +42,123 @@ static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwd
}

self->name = std::string(name);
Py_INCREF(data);
self->data = data;

if (data == Py_None) {
self->data = std::monostate{};
self->data_type = DataType::None;
} else if (PyLong_Check(data)) {
self->data = static_cast<int64_t>(PyLong_AsLongLong(data));
self->data_type = DataType::Int;
} else if (PyFloat_Check(data)) {
self->data = PyFloat_AsDouble(data);
self->data_type = DataType::Double;
} else if (PyUnicode_Check(data)) {
const char* s = PyUnicode_AsUTF8(data);
self->data = std::string(s);
self->data_type = DataType::String;
} else {
PyErr_SetString(PyExc_TypeError, "data must be int, float, str, or None");
return NULL;
}

return reinterpret_cast<PyObject*>(self);
}

static PyObject* GraphNode_str(GraphNode* self) {
return PyUnicode_FromString(("('" + self->name + "', " + PyUnicode_AsUTF8(PyObject_Str(self->data)) + ")").c_str());
std::string repr = "('" + self->name + "', ";

switch (self->data_type) {
case DataType::None:
repr += "None";
break;
case DataType::Int:
repr += std::to_string(std::get<int64_t>(self->data));
break;
case DataType::Double:
repr += std::to_string(std::get<double>(self->data));
break;
case DataType::String:
repr += "'" + std::get<std::string>(self->data) + "'";
break;
}
repr += ")";
return PyUnicode_FromString(repr.c_str());
}

static PyObject* GraphNode_get(GraphNode* self, void *closure) {
if (closure == (void*)"name") {
return PyUnicode_FromString(self->name.c_str());
}
if (closure == (void*)"data") {
Py_INCREF(self->data);
return self->data;
} else if (closure == (void*)"data") {
switch (self->data_type) {
case DataType::None:
Py_RETURN_NONE;
case DataType::Int:
return PyLong_FromLongLong(std::get<int64_t>(self->data));
case DataType::Double:
return PyFloat_FromDouble(std::get<double>(self->data));
case DataType::String:
return PyUnicode_FromString(std::get<std::string>(self->data).c_str());
}
}
Py_RETURN_NONE;
}

static int GraphNode_set(GraphNode* self, PyObject *value, void *closure) {
if (value == NULL) {
PyErr_SetString(PyExc_ValueError, "value is NULL");
if (!value) {
PyErr_SetString(PyExc_ValueError, "Cannot delete attributes");
return -1;
}

if (closure == (void*)"name") {
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError, "value to be set must be a string");
PyErr_SetString(PyExc_TypeError, "name must be a string");
return -1;
}
self->name = PyUnicode_AsUTF8(value);
}
else if (closure == (void*)"data") {
PyObject *tmp = self->data;
Py_INCREF(value);
self->data = value;
Py_DECREF(tmp);
}
else {
if (value == Py_None) {
self->data = std::monostate{};
self->data_type = DataType::None;
} else if (PyLong_Check(value)) {
self->data = static_cast<int64_t>(PyLong_AsLongLong(value));
self->data_type = DataType::Int;
} else if (PyFloat_Check(value)) {
self->data = PyFloat_AsDouble(value);
self->data_type = DataType::Double;
} else if (PyUnicode_Check(value)) {
self->data = std::string(PyUnicode_AsUTF8(value));
self->data_type = DataType::String;
} else {
PyErr_SetString(PyExc_TypeError, "data must be int, float, str, or None");
return -1;
}
} else {
PyErr_SetString(PyExc_AttributeError, "Unknown attribute");
return -1;
}

return 0;
}

static PyGetSetDef GraphNode_getsetters[] = {
{
const_cast<char*>("name"),
reinterpret_cast<getter>(GraphNode_get),
reinterpret_cast<setter>(GraphNode_set),
const_cast<char*>("name"),
reinterpret_cast<void*>(const_cast<char*>("name"))
},
{
const_cast<char*>("data"),
reinterpret_cast<getter>(GraphNode_get),
reinterpret_cast<setter>(GraphNode_set),
const_cast<char*>("data"),
reinterpret_cast<void*>(const_cast<char*>("data"))
},
{nullptr}
};

static PyTypeObject GraphNodeType = {
/* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "GraphNode",
/* tp_basicsize */ sizeof(GraphNode),
Expand Down Expand Up @@ -109,7 +188,7 @@ static PyTypeObject GraphNodeType = {
/* tp_iternext */ 0,
/* tp_methods */ 0,
/* tp_members */ 0,
/* tp_getset */ 0,
/* tp_getset */ GraphNode_getsetters,
/* tp_base */ &PyBaseObject_Type,
/* tp_dict */ 0,
/* tp_descr_get */ 0,
Expand Down
23 changes: 14 additions & 9 deletions pydatastructs/utils/_extensions.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
from setuptools import Extension
import os
import sys

project = 'pydatastructs'

module = 'utils'

backend = '_backend'

cpp = 'cpp'

nodes = '.'.join([project, module, backend, cpp, '_nodes'])
nodes_sources = ['/'.join([project, module, backend, cpp,
'nodes.cpp'])]
nodes_sources = [os.path.join(project, module, backend, cpp, 'nodes.cpp')]

graph_utils = '.'.join([project, module, backend, cpp, '_graph_utils'])
graph_utils_sources = ['/'.join([project, module, backend, cpp,
'graph_utils.cpp'])]
graph_utils_sources = [os.path.join(project, module, backend, cpp, 'graph_utils.cpp')]

extra_compile_args = ["-std=c++17"]

if sys.platform == "darwin":
extra_compile_args.append("-mmacosx-version-min=10.13")
elif sys.platform == "win32":
extra_compile_args = ["/std:c++17"]

extensions = [
Extension(nodes, sources=nodes_sources),
Extension(graph_utils, sources = graph_utils_sources)
Extension(nodes, sources=nodes_sources, language="c++", extra_compile_args=extra_compile_args),
Extension(graph_utils, sources=graph_utils_sources, language="c++", extra_compile_args=extra_compile_args),
]
Loading