From 1e154d4275656647774d627e4f6ce4438795491a Mon Sep 17 00:00:00 2001 From: dance858 Date: Wed, 25 Feb 2026 07:19:05 -0800 Subject: [PATCH 1/3] coo binders --- sparsediffpy/_bindings/bindings.c | 13 +++ sparsediffpy/_bindings/problem/hessian.h | 95 +++++++++++++++++++ sparsediffpy/_bindings/problem/init_hessian.h | 22 +++++ .../_bindings/problem/init_jacobian.h | 21 ++++ sparsediffpy/_bindings/problem/jacobian.h | 75 +++++++++++++++ 5 files changed, 226 insertions(+) diff --git a/sparsediffpy/_bindings/bindings.c b/sparsediffpy/_bindings/bindings.c index 8a0d794..265e318 100644 --- a/sparsediffpy/_bindings/bindings.c +++ b/sparsediffpy/_bindings/bindings.c @@ -150,6 +150,19 @@ static PyMethodDef DNLPMethods[] = { "Compute Lagrangian Hessian"}, {"get_hessian", py_get_hessian, METH_VARARGS, "Get Lagrangian Hessian without recomputing"}, + {"problem_init_jacobian_coo", py_problem_init_jacobian_coo, METH_VARARGS, + "Initialize Jacobian COO structures"}, + {"get_jacobian_sparsity_coo", py_get_jacobian_sparsity_coo, METH_VARARGS, + "Get Jacobian sparsity in COO format"}, + {"eval_jacobian_vals", py_eval_jacobian_vals, METH_VARARGS, + "Evaluate Jacobian and return values array"}, + {"problem_init_hessian_coo_lower_triangular", + py_problem_init_hessian_coo_lower_triangular, METH_VARARGS, + "Initialize lower-triangular Hessian COO structures"}, + {"get_hessian_sparsity_coo", py_get_hessian_sparsity_coo, METH_VARARGS, + "Get Hessian sparsity in COO format (lower triangular)"}, + {"eval_hessian_vals_coo", py_eval_hessian_vals_coo, METH_VARARGS, + "Evaluate Hessian and return COO values array"}, {NULL, NULL, 0, NULL}}; static struct PyModuleDef sparsediffpy_module = { diff --git a/sparsediffpy/_bindings/problem/hessian.h b/sparsediffpy/_bindings/problem/hessian.h index ea7f226..edae117 100644 --- a/sparsediffpy/_bindings/problem/hessian.h +++ b/sparsediffpy/_bindings/problem/hessian.h @@ -120,4 +120,99 @@ static PyObject *py_get_hessian(PyObject *self, PyObject *args) return Py_BuildValue("(OOO(ii))", data, indices, indptr, H->m, H->n); } +static PyObject *py_get_hessian_sparsity_coo(PyObject *self, PyObject *args) +{ + PyObject *prob_capsule; + if (!PyArg_ParseTuple(args, "O", &prob_capsule)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + if (!prob->lagrange_hessian_coo) + { + PyErr_SetString(PyExc_RuntimeError, + "hessian COO not initialized - call " + "problem_init_hessian_coo_lower_triangular first"); + return NULL; + } + + COO_Matrix *coo = prob->lagrange_hessian_coo; + npy_intp nnz = coo->nnz; + + PyObject *rows = PyArray_SimpleNew(1, &nnz, NPY_INT32); + PyObject *cols = PyArray_SimpleNew(1, &nnz, NPY_INT32); + + if (!rows || !cols) + { + Py_XDECREF(rows); + Py_XDECREF(cols); + return NULL; + } + + memcpy(PyArray_DATA((PyArrayObject *) rows), coo->rows, nnz * sizeof(int)); + memcpy(PyArray_DATA((PyArrayObject *) cols), coo->cols, nnz * sizeof(int)); + + return Py_BuildValue("(OO(ii))", rows, cols, coo->m, coo->n); +} + +static PyObject *py_eval_hessian_vals_coo(PyObject *self, PyObject *args) +{ + PyObject *prob_capsule; + double obj_factor; + PyObject *lagrange_obj; + + if (!PyArg_ParseTuple(args, "OdO", &prob_capsule, &obj_factor, &lagrange_obj)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + /* Convert lagrange to contiguous C array */ + PyArrayObject *lagrange_arr = (PyArrayObject *) PyArray_FROM_OTF( + lagrange_obj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); + if (!lagrange_arr) + { + return NULL; + } + + double *lagrange = (double *) PyArray_DATA(lagrange_arr); + + /* Compute Hessian */ + problem_hessian(prob, obj_factor, lagrange); + + Py_DECREF(lagrange_arr); + + /* Refresh COO values from the CSR Hessian */ + refresh_lower_triangular_coo(prob->lagrange_hessian_coo, + prob->lagrange_hessian->x); + + COO_Matrix *coo = prob->lagrange_hessian_coo; + npy_intp nnz = coo->nnz; + + PyObject *data = PyArray_SimpleNew(1, &nnz, NPY_DOUBLE); + if (!data) + { + return NULL; + } + + memcpy(PyArray_DATA((PyArrayObject *) data), coo->x, nnz * sizeof(double)); + + return data; +} + #endif /* PROBLEM_HESSIAN_H */ diff --git a/sparsediffpy/_bindings/problem/init_hessian.h b/sparsediffpy/_bindings/problem/init_hessian.h index 7873974..365569e 100644 --- a/sparsediffpy/_bindings/problem/init_hessian.h +++ b/sparsediffpy/_bindings/problem/init_hessian.h @@ -39,4 +39,26 @@ static PyObject *py_problem_init_hessian(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject *py_problem_init_hessian_coo_lower_triangular(PyObject *self, + PyObject *args) +{ + PyObject *prob_capsule; + if (!PyArg_ParseTuple(args, "O", &prob_capsule)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + problem_init_hessian_coo_lower_triangular(prob); + + Py_RETURN_NONE; +} + #endif /* PROBLEM_INIT_HESSIAN_H */ diff --git a/sparsediffpy/_bindings/problem/init_jacobian.h b/sparsediffpy/_bindings/problem/init_jacobian.h index 8cd9bc8..c8a0c4e 100644 --- a/sparsediffpy/_bindings/problem/init_jacobian.h +++ b/sparsediffpy/_bindings/problem/init_jacobian.h @@ -39,4 +39,25 @@ static PyObject *py_problem_init_jacobian(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject *py_problem_init_jacobian_coo(PyObject *self, PyObject *args) +{ + PyObject *prob_capsule; + if (!PyArg_ParseTuple(args, "O", &prob_capsule)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + problem_init_jacobian_coo(prob); + + Py_RETURN_NONE; +} + #endif /* PROBLEM_INIT_JACOBIAN_H */ diff --git a/sparsediffpy/_bindings/problem/jacobian.h b/sparsediffpy/_bindings/problem/jacobian.h index 1ba0a42..3df0835 100644 --- a/sparsediffpy/_bindings/problem/jacobian.h +++ b/sparsediffpy/_bindings/problem/jacobian.h @@ -102,4 +102,79 @@ static PyObject *py_get_jacobian(PyObject *self, PyObject *args) return Py_BuildValue("(OOO(ii))", data, indices, indptr, jac->m, jac->n); } +static PyObject *py_get_jacobian_sparsity_coo(PyObject *self, PyObject *args) +{ + PyObject *prob_capsule; + if (!PyArg_ParseTuple(args, "O", &prob_capsule)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + if (!prob->jacobian_coo) + { + PyErr_SetString( + PyExc_RuntimeError, + "jacobian COO not initialized - call problem_init_jacobian_coo first"); + return NULL; + } + + COO_Matrix *coo = prob->jacobian_coo; + npy_intp nnz = coo->nnz; + + PyObject *rows = PyArray_SimpleNew(1, &nnz, NPY_INT32); + PyObject *cols = PyArray_SimpleNew(1, &nnz, NPY_INT32); + + if (!rows || !cols) + { + Py_XDECREF(rows); + Py_XDECREF(cols); + return NULL; + } + + memcpy(PyArray_DATA((PyArrayObject *) rows), coo->rows, nnz * sizeof(int)); + memcpy(PyArray_DATA((PyArrayObject *) cols), coo->cols, nnz * sizeof(int)); + + return Py_BuildValue("(OO(ii))", rows, cols, coo->m, coo->n); +} + +static PyObject *py_eval_jacobian_vals(PyObject *self, PyObject *args) +{ + PyObject *prob_capsule; + if (!PyArg_ParseTuple(args, "O", &prob_capsule)) + { + return NULL; + } + + problem *prob = + (problem *) PyCapsule_GetPointer(prob_capsule, PROBLEM_CAPSULE_NAME); + if (!prob) + { + PyErr_SetString(PyExc_ValueError, "invalid problem capsule"); + return NULL; + } + + problem_jacobian(prob); + + CSR_Matrix *jac = prob->jacobian; + npy_intp nnz = jac->nnz; + + PyObject *data = PyArray_SimpleNew(1, &nnz, NPY_DOUBLE); + if (!data) + { + return NULL; + } + + memcpy(PyArray_DATA((PyArrayObject *) data), jac->x, nnz * sizeof(double)); + + return data; +} + #endif /* PROBLEM_JACOBIAN_H */ From f0ab1ee017f58711d2c95c7257e4cda72dcbd07c Mon Sep 17 00:00:00 2001 From: dance858 Date: Wed, 25 Feb 2026 09:50:32 -0800 Subject: [PATCH 2/3] new bindings --- sparsediffpy/_bindings/bindings.c | 6 +++--- sparsediffpy/_bindings/problem/hessian.h | 4 ++-- sparsediffpy/_bindings/problem/jacobian.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sparsediffpy/_bindings/bindings.c b/sparsediffpy/_bindings/bindings.c index 265e318..2e51960 100644 --- a/sparsediffpy/_bindings/bindings.c +++ b/sparsediffpy/_bindings/bindings.c @@ -154,14 +154,14 @@ static PyMethodDef DNLPMethods[] = { "Initialize Jacobian COO structures"}, {"get_jacobian_sparsity_coo", py_get_jacobian_sparsity_coo, METH_VARARGS, "Get Jacobian sparsity in COO format"}, - {"eval_jacobian_vals", py_eval_jacobian_vals, METH_VARARGS, + {"problem_eval_jacobian_vals", py_problem_eval_jacobian_vals, METH_VARARGS, "Evaluate Jacobian and return values array"}, {"problem_init_hessian_coo_lower_triangular", py_problem_init_hessian_coo_lower_triangular, METH_VARARGS, "Initialize lower-triangular Hessian COO structures"}, - {"get_hessian_sparsity_coo", py_get_hessian_sparsity_coo, METH_VARARGS, + {"get_problem_hessian_sparsity_coo", py_get_problem_hessian_sparsity_coo, METH_VARARGS, "Get Hessian sparsity in COO format (lower triangular)"}, - {"eval_hessian_vals_coo", py_eval_hessian_vals_coo, METH_VARARGS, + {"problem_eval_hessian_vals_coo", py_problem_eval_hessian_vals_coo, METH_VARARGS, "Evaluate Hessian and return COO values array"}, {NULL, NULL, 0, NULL}}; diff --git a/sparsediffpy/_bindings/problem/hessian.h b/sparsediffpy/_bindings/problem/hessian.h index edae117..b9a31d8 100644 --- a/sparsediffpy/_bindings/problem/hessian.h +++ b/sparsediffpy/_bindings/problem/hessian.h @@ -120,7 +120,7 @@ static PyObject *py_get_hessian(PyObject *self, PyObject *args) return Py_BuildValue("(OOO(ii))", data, indices, indptr, H->m, H->n); } -static PyObject *py_get_hessian_sparsity_coo(PyObject *self, PyObject *args) +static PyObject *py_get_problem_hessian_sparsity_coo(PyObject *self, PyObject *args) { PyObject *prob_capsule; if (!PyArg_ParseTuple(args, "O", &prob_capsule)) @@ -163,7 +163,7 @@ static PyObject *py_get_hessian_sparsity_coo(PyObject *self, PyObject *args) return Py_BuildValue("(OO(ii))", rows, cols, coo->m, coo->n); } -static PyObject *py_eval_hessian_vals_coo(PyObject *self, PyObject *args) +static PyObject *py_problem_eval_hessian_vals_coo(PyObject *self, PyObject *args) { PyObject *prob_capsule; double obj_factor; diff --git a/sparsediffpy/_bindings/problem/jacobian.h b/sparsediffpy/_bindings/problem/jacobian.h index 3df0835..e348a36 100644 --- a/sparsediffpy/_bindings/problem/jacobian.h +++ b/sparsediffpy/_bindings/problem/jacobian.h @@ -145,7 +145,7 @@ static PyObject *py_get_jacobian_sparsity_coo(PyObject *self, PyObject *args) return Py_BuildValue("(OO(ii))", rows, cols, coo->m, coo->n); } -static PyObject *py_eval_jacobian_vals(PyObject *self, PyObject *args) +static PyObject *py_problem_eval_jacobian_vals(PyObject *self, PyObject *args) { PyObject *prob_capsule; if (!PyArg_ParseTuple(args, "O", &prob_capsule)) From a45655756f9c50480e6227d87f9cebbd863c12f3 Mon Sep 17 00:00:00 2001 From: dance858 Date: Wed, 25 Feb 2026 09:54:40 -0800 Subject: [PATCH 3/3] preparing for new release --- SparseDiffEngine | 2 +- pyproject.toml | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SparseDiffEngine b/SparseDiffEngine index 5316598..7c84b13 160000 --- a/SparseDiffEngine +++ b/SparseDiffEngine @@ -1 +1 @@ -Subproject commit 5316598a490200483a65d26af14085d7c089eb20 +Subproject commit 7c84b137ea7ac51846eaa5a30fe3ea707bc9d7aa diff --git a/pyproject.toml b/pyproject.toml index 021f18c..f40d0e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sparsediffpy" -version = "0.1.2" +version = "0.1.3" description = "Python bindings for SparseDiffEngine automatic differentiation" requires-python = ">=3.11" dependencies = ["numpy >= 2.0.0"] diff --git a/setup.py b/setup.py index 9a5198c..3b572ad 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ def finalize_options(self) -> None: "-std=c99", "-Wall", not_on_windows("-Wextra"), - '-DDIFF_ENGINE_VERSION="0.1.0"', + '-DDIFF_ENGINE_VERSION="0.1.3"', ], extra_link_args=["-lm"] if platform.system().lower() != "windows" else [], )