Skip to content

Commit c89efa3

Browse files
committed
draft of bindings, same structure as presolver
1 parent 533bd48 commit c89efa3

4 files changed

Lines changed: 260 additions & 0 deletions

File tree

.gitignore

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Prerequisites
2+
*.d
3+
4+
# Object files
5+
*.o
6+
*.ko
7+
*.obj
8+
*.elf
9+
10+
# Linker output
11+
*.ilk
12+
*.map
13+
*.exp
14+
15+
# Precompiled Headers
16+
*.gch
17+
*.pch
18+
19+
# Libraries
20+
*.lib
21+
*.a
22+
*.la
23+
*.lo
24+
25+
# Shared objects (inc. Windows DLLs)
26+
*.dll
27+
*.so
28+
*.so.*
29+
*.dylib
30+
31+
# Executables
32+
*.exe
33+
*.out
34+
*.app
35+
*.i*86
36+
*.x86_64
37+
*.hex
38+
39+
# Debug files
40+
*.dSYM/
41+
*.su
42+
*.idb
43+
*.pdb
44+
45+
# Kernel Module Compile Results
46+
*.mod*
47+
*.cmd
48+
.tmp_versions/
49+
modules.order
50+
Module.symvers
51+
Mkfile.old
52+
dkms.conf
53+
54+
# CMake
55+
CMakeCache.txt
56+
CMakeFiles/
57+
CMakeScripts/
58+
Testing/
59+
Makefile
60+
cmake_install.cmake
61+
install_manifest.txt
62+
compile_commands.json
63+
CTestTestfile.cmake
64+
_deps
65+
build/
66+
Build/
67+
out/
68+
69+
# IDEs
70+
.vscode/
71+
.idea/
72+
*.swp
73+
*.swo
74+
*~
75+
.DS_Store

python/CMakeLists.txt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
project(DNLP_diff_engine LANGUAGES C VERSION 0.1.0
3+
DESCRIPTION "Python bindings for DNLP diff engine")
4+
5+
if(DEFINED ENV{VIRTUAL_ENV})
6+
set(Python3_EXECUTABLE "$ENV{VIRTUAL_ENV}/bin/python3")
7+
endif()
8+
9+
# Find Python3 and NumPy
10+
find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy)
11+
message(STATUS "Python3 executable: ${Python3_EXECUTABLE}")
12+
message(STATUS "Python3 version: ${Python3_VERSION}")
13+
message(STATUS "NumPy include dirs: ${Python3_NumPy_INCLUDE_DIRS}")
14+
15+
# Verify NumPy version
16+
execute_process(
17+
COMMAND ${Python3_EXECUTABLE} -c "import numpy; print(numpy.__version__)"
18+
OUTPUT_VARIABLE NUMPY_VERSION
19+
OUTPUT_STRIP_TRAILING_WHITESPACE
20+
)
21+
message(STATUS "NumPy version: ${NUMPY_VERSION}")
22+
23+
# Build core project from source
24+
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/.. ${CMAKE_BINARY_DIR}/core_build)
25+
26+
# create Python module
27+
add_library(bindings MODULE ${CMAKE_CURRENT_LIST_DIR}/bindings.c)
28+
target_include_directories(bindings
29+
PRIVATE
30+
${Python3_INCLUDE_DIRS}
31+
${Python3_NumPy_INCLUDE_DIRS}
32+
${CMAKE_CURRENT_LIST_DIR}/../include
33+
${CMAKE_CURRENT_LIST_DIR}/../src
34+
${CMAKE_CURRENT_LIST_DIR}/../src/utils
35+
)
36+
37+
# Link against core and Python libraries
38+
target_link_libraries(bindings
39+
PRIVATE
40+
dnlp_diff
41+
Python3::Python
42+
Python3::NumPy
43+
)
44+
45+
# Place the module in the python source folder alongside example.py
46+
set(PY_MODULE_OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR})
47+
file(MAKE_DIRECTORY ${PY_MODULE_OUTPUT_DIR})
48+
49+
set_target_properties(bindings PROPERTIES
50+
PREFIX ""
51+
OUTPUT_NAME "DNLP_diff_engine"
52+
SUFFIX ".${Python3_SOABI}.so"
53+
54+
# All configs → same folder
55+
LIBRARY_OUTPUT_DIRECTORY ${PY_MODULE_OUTPUT_DIR}
56+
RUNTIME_OUTPUT_DIRECTORY ${PY_MODULE_OUTPUT_DIR} # Critical for Windows .pyd
57+
)

python/bindings.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#define PY_SSIZE_T_CLEAN
2+
#include <Python.h>
3+
#include <numpy/arrayobject.h>
4+
#include <stdbool.h>
5+
6+
#include "affine.h"
7+
#include "elementwise_univariate.h"
8+
#include "expr.h"
9+
10+
// Capsule name for expr* pointers
11+
#define EXPR_CAPSULE_NAME "DNLP_EXPR"
12+
13+
static int ensure_numpy(void)
14+
{
15+
import_array();
16+
return 0;
17+
}
18+
19+
static void expr_capsule_destructor(PyObject *capsule)
20+
{
21+
expr *node = (expr *) PyCapsule_GetPointer(capsule, EXPR_CAPSULE_NAME);
22+
if (node)
23+
{
24+
free_expr(node);
25+
}
26+
}
27+
28+
static PyObject *py_make_variable(PyObject *self, PyObject *args)
29+
{
30+
int d1, d2, var_id, n_vars;
31+
if (!PyArg_ParseTuple(args, "iiii", &d1, &d2, &var_id, &n_vars))
32+
{
33+
return NULL;
34+
}
35+
36+
expr *node = new_variable(d1, d2, var_id, n_vars);
37+
if (!node)
38+
{
39+
PyErr_SetString(PyExc_RuntimeError, "failed to create variable node");
40+
return NULL;
41+
}
42+
return PyCapsule_New(node, EXPR_CAPSULE_NAME, expr_capsule_destructor);
43+
}
44+
45+
static PyObject *py_make_log(PyObject *self, PyObject *args)
46+
{
47+
PyObject *child_capsule;
48+
if (!PyArg_ParseTuple(args, "O", &child_capsule))
49+
{
50+
return NULL;
51+
}
52+
expr *child = (expr *) PyCapsule_GetPointer(child_capsule, EXPR_CAPSULE_NAME);
53+
if (!child)
54+
{
55+
PyErr_SetString(PyExc_ValueError, "invalid child capsule");
56+
return NULL;
57+
}
58+
59+
expr *node = new_log(child);
60+
if (!node)
61+
{
62+
PyErr_SetString(PyExc_RuntimeError, "failed to create log node");
63+
return NULL;
64+
}
65+
return PyCapsule_New(node, EXPR_CAPSULE_NAME, expr_capsule_destructor);
66+
}
67+
68+
static PyObject *py_forward(PyObject *self, PyObject *args)
69+
{
70+
PyObject *node_capsule;
71+
PyObject *u_obj;
72+
if (!PyArg_ParseTuple(args, "OO", &node_capsule, &u_obj))
73+
{
74+
return NULL;
75+
}
76+
77+
expr *node = (expr *) PyCapsule_GetPointer(node_capsule, EXPR_CAPSULE_NAME);
78+
if (!node)
79+
{
80+
PyErr_SetString(PyExc_ValueError, "invalid node capsule");
81+
return NULL;
82+
}
83+
84+
PyArrayObject *u_array =
85+
(PyArrayObject *) PyArray_FROM_OTF(u_obj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
86+
if (!u_array)
87+
{
88+
return NULL;
89+
}
90+
91+
node->forward(node, (const double *) PyArray_DATA(u_array));
92+
93+
npy_intp size = node->size;
94+
PyObject *out = PyArray_SimpleNew(1, &size, NPY_DOUBLE);
95+
if (!out)
96+
{
97+
Py_DECREF(u_array);
98+
return NULL;
99+
}
100+
memcpy(PyArray_DATA((PyArrayObject *) out), node->value, size * sizeof(double));
101+
102+
Py_DECREF(u_array);
103+
return out;
104+
}
105+
106+
static PyMethodDef DNLPMethods[] = {
107+
{"make_variable", py_make_variable, METH_VARARGS, "Create variable node"},
108+
{"make_log", py_make_log, METH_VARARGS, "Create log node"},
109+
{"forward", py_forward, METH_VARARGS, "Run forward pass and return values"},
110+
{NULL, NULL, 0, NULL}};
111+
112+
static struct PyModuleDef dnlp_module = {PyModuleDef_HEAD_INIT, "DNLP_diff_engine",
113+
NULL, -1, DNLPMethods};
114+
115+
PyMODINIT_FUNC PyInit_DNLP_diff_engine(void)
116+
{
117+
if (ensure_numpy() < 0) return NULL;
118+
return PyModule_Create(&dnlp_module);
119+
}

python/example.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import numpy as np
2+
import DNLP_diff_engine as dnlp
3+
4+
x = dnlp.make_variable(3, 1, 0, 3)
5+
log_x = dnlp.make_log(x)
6+
7+
u = np.array([1.0, 2.0, 3.0])
8+
out = dnlp.forward(log_x, u)
9+
print("log(x) forward:", out)

0 commit comments

Comments
 (0)