From bec476fb472732b1e2d0502f65dcab675b31e7f4 Mon Sep 17 00:00:00 2001 From: dance858 Date: Mon, 9 Mar 2026 17:04:19 -0700 Subject: [PATCH 01/10] new bindings --- sparsediffpy/_bindings/atoms/dense_matmul.h | 101 ++++++++++++++++++++ sparsediffpy/_bindings/atoms/left_matmul.h | 2 +- sparsediffpy/_bindings/atoms/right_matmul.h | 2 +- sparsediffpy/_bindings/bindings.c | 13 ++- 4 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 sparsediffpy/_bindings/atoms/dense_matmul.h diff --git a/sparsediffpy/_bindings/atoms/dense_matmul.h b/sparsediffpy/_bindings/atoms/dense_matmul.h new file mode 100644 index 0000000..d8aef8f --- /dev/null +++ b/sparsediffpy/_bindings/atoms/dense_matmul.h @@ -0,0 +1,101 @@ +#ifndef ATOM_DENSE_MATMUL_H +#define ATOM_DENSE_MATMUL_H + +#include "bivariate.h" +#include "common.h" + +/* Dense left matrix multiplication: A @ f(x) where A is a dense matrix. + * + * Python signature: + * make_dense_left_matmul(child, A_data_flat, m, n) + * + * - child: the child expression capsule f(x). + * - A_data_flat: contiguous row-major numpy float64 array of size m*n. + * - m, n: dimensions of matrix A. */ +static PyObject *py_make_dense_left_matmul(PyObject *self, PyObject *args) +{ + PyObject *child_capsule; + PyObject *data_obj; + int m, n; + if (!PyArg_ParseTuple(args, "OOii", &child_capsule, &data_obj, &m, &n)) + { + return NULL; + } + + expr *child = (expr *) PyCapsule_GetPointer(child_capsule, EXPR_CAPSULE_NAME); + if (!child) + { + PyErr_SetString(PyExc_ValueError, "invalid child capsule"); + return NULL; + } + + PyArrayObject *data_array = + (PyArrayObject *) PyArray_FROM_OTF(data_obj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); + if (!data_array) + { + return NULL; + } + + double *A_data = (double *) PyArray_DATA(data_array); + + expr *node = new_left_matmul_dense(child, m, n, A_data); + Py_DECREF(data_array); + + if (!node) + { + PyErr_SetString(PyExc_RuntimeError, + "failed to create dense_left_matmul node"); + return NULL; + } + expr_retain(node); + return PyCapsule_New(node, EXPR_CAPSULE_NAME, expr_capsule_destructor); +} + +/* Dense right matrix multiplication: f(x) @ A where A is a dense matrix. + * + * Python signature: + * make_dense_right_matmul(child, A_data_flat, m, n) + * + * - child: the child expression capsule f(x). + * - A_data_flat: contiguous row-major numpy float64 array of size m*n. + * - m, n: dimensions of matrix A. */ +static PyObject *py_make_dense_right_matmul(PyObject *self, PyObject *args) +{ + PyObject *child_capsule; + PyObject *data_obj; + int m, n; + if (!PyArg_ParseTuple(args, "OOii", &child_capsule, &data_obj, &m, &n)) + { + return NULL; + } + + expr *child = (expr *) PyCapsule_GetPointer(child_capsule, EXPR_CAPSULE_NAME); + if (!child) + { + PyErr_SetString(PyExc_ValueError, "invalid child capsule"); + return NULL; + } + + PyArrayObject *data_array = + (PyArrayObject *) PyArray_FROM_OTF(data_obj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY); + if (!data_array) + { + return NULL; + } + + double *A_data = (double *) PyArray_DATA(data_array); + + expr *node = new_right_matmul_dense(child, m, n, A_data); + Py_DECREF(data_array); + + if (!node) + { + PyErr_SetString(PyExc_RuntimeError, + "failed to create dense_right_matmul node"); + return NULL; + } + expr_retain(node); + return PyCapsule_New(node, EXPR_CAPSULE_NAME, expr_capsule_destructor); +} + +#endif /* ATOM_DENSE_MATMUL_H */ \ No newline at end of file diff --git a/sparsediffpy/_bindings/atoms/left_matmul.h b/sparsediffpy/_bindings/atoms/left_matmul.h index 27fe3a4..09fa985 100644 --- a/sparsediffpy/_bindings/atoms/left_matmul.h +++ b/sparsediffpy/_bindings/atoms/left_matmul.h @@ -5,7 +5,7 @@ #include "common.h" /* Left matrix multiplication: A @ f(x) where A is a constant matrix */ -static PyObject *py_make_left_matmul(PyObject *self, PyObject *args) +static PyObject *py_make_sparse_left_matmul(PyObject *self, PyObject *args) { PyObject *child_capsule; PyObject *data_obj, *indices_obj, *indptr_obj; diff --git a/sparsediffpy/_bindings/atoms/right_matmul.h b/sparsediffpy/_bindings/atoms/right_matmul.h index c1c3481..f808058 100644 --- a/sparsediffpy/_bindings/atoms/right_matmul.h +++ b/sparsediffpy/_bindings/atoms/right_matmul.h @@ -5,7 +5,7 @@ #include "common.h" /* Right matrix multiplication: f(x) @ A where A is a constant matrix */ -static PyObject *py_make_right_matmul(PyObject *self, PyObject *args) +static PyObject *py_make_sparse_right_matmul(PyObject *self, PyObject *args) { PyObject *child_capsule; PyObject *data_obj, *indices_obj, *indptr_obj; diff --git a/sparsediffpy/_bindings/bindings.c b/sparsediffpy/_bindings/bindings.c index 2e51960..9f3c4b0 100644 --- a/sparsediffpy/_bindings/bindings.c +++ b/sparsediffpy/_bindings/bindings.c @@ -11,6 +11,7 @@ #include "atoms/const_vector_mult.h" #include "atoms/constant.h" #include "atoms/cos.h" +#include "atoms/dense_matmul.h" #include "atoms/diag_vec.h" #include "atoms/entr.h" #include "atoms/exp.h" @@ -109,10 +110,14 @@ static PyMethodDef DNLPMethods[] = { {"make_entr", py_make_entr, METH_VARARGS, "Create entr node"}, {"make_logistic", py_make_logistic, METH_VARARGS, "Create logistic node"}, {"make_xexp", py_make_xexp, METH_VARARGS, "Create xexp node"}, - {"make_left_matmul", py_make_left_matmul, METH_VARARGS, - "Create left matmul node (A @ f(x))"}, - {"make_right_matmul", py_make_right_matmul, METH_VARARGS, - "Create right matmul node (f(x) @ A)"}, + {"make_sparse_left_matmul", py_make_sparse_left_matmul, METH_VARARGS, + "Create sparse left matmul node (A @ f(x))"}, + {"make_dense_left_matmul", py_make_dense_left_matmul, METH_VARARGS, + "Create dense left matmul node (A @ f(x)) where A is dense"}, + {"make_sparse_right_matmul", py_make_sparse_right_matmul, METH_VARARGS, + "Create sparse right matmul node (f(x) @ A)"}, + {"make_dense_right_matmul", py_make_dense_right_matmul, METH_VARARGS, + "Create dense right matmul node (f(x) @ A) where A is dense"}, {"make_quad_form", py_make_quad_form, METH_VARARGS, "Create quadratic form node (x' * Q * x)"}, {"make_quad_over_lin", py_make_quad_over_lin, METH_VARARGS, From 3fec61a605222f221b4332e00e7abf8fef369784 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 18:55:47 -0400 Subject: [PATCH 02/10] update SparseDiffEngine to latest main Co-Authored-By: Claude Opus 4.6 --- SparseDiffEngine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SparseDiffEngine b/SparseDiffEngine index 7c84b13..9480159 160000 --- a/SparseDiffEngine +++ b/SparseDiffEngine @@ -1 +1 @@ -Subproject commit 7c84b137ea7ac51846eaa5a30fe3ea707bc9d7aa +Subproject commit 94801591f2f059ed3de6d68ab3db5a370bf3d667 From fdfd21fd4c0e0320051879965d3fbfb891888e30 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 19:06:32 -0400 Subject: [PATCH 03/10] add BLAS linking for all platforms (Accelerate/OpenBLAS/vcpkg) The updated SparseDiffEngine submodule introduces dense_matrix.c which requires CBLAS. Link Accelerate on macOS, OpenBLAS on Linux, and OpenBLAS via vcpkg on Windows. Install BLAS dev packages in CI via CIBW_BEFORE_BUILD for Linux and Windows. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 3 ++ setup.py | 40 +++++++++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index d1b3a38..d2ec3a5 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -64,6 +64,9 @@ jobs: CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux*" CIBW_ARCHS_MACOS: "x86_64 universal2" CIBW_ARCHS_LINUX: "auto aarch64" + CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" + CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows" + CIBW_ENVIRONMENT_WINDOWS: "VCPKG_INSTALLED_DIR=C:\\vcpkg\\installed\\x64-windows" - name: Check wheels shell: bash diff --git a/setup.py b/setup.py index 3b572ad..e02b24f 100644 --- a/setup.py +++ b/setup.py @@ -16,14 +16,13 @@ import builtins import glob +import os import platform from setuptools import Extension, setup from setuptools.command.build_ext import build_ext - -def not_on_windows(s: str) -> str: - return s if platform.system().lower() != "windows" else "" +system = platform.system().lower() class build_ext_numpy(build_ext): @@ -44,9 +43,25 @@ def finalize_options(self) -> None: # Define _POSIX_C_SOURCE on Linux for clock_gettime and struct timespec defines = [] -if platform.system().lower() == "linux": +if system == "linux": defines.append(("_POSIX_C_SOURCE", "200809L")) +# Platform-specific BLAS configuration +if system == "darwin": + blas_link_args = ["-framework", "Accelerate"] + blas_include_dirs = [] +elif system == "linux": + blas_link_args = ["-lopenblas"] + blas_include_dirs = [] +else: + # Windows: OpenBLAS via vcpkg + vcpkg_root = os.environ.get( + "VCPKG_INSTALLED_DIR", + r"C:\vcpkg\installed\x64-windows", + ) + blas_link_args = [os.path.join(vcpkg_root, "lib", "openblas.lib")] + blas_include_dirs = [os.path.join(vcpkg_root, "include")] + sparsediffengine = Extension( "sparsediffpy._sparsediffengine", sources=diff_engine_sources, @@ -54,16 +69,15 @@ def finalize_options(self) -> None: "SparseDiffEngine/include/", "SparseDiffEngine/src/", "sparsediffpy/_bindings/", - ], + ] + blas_include_dirs, define_macros=defines, - extra_compile_args=[ - "-O3", - "-std=c99", - "-Wall", - not_on_windows("-Wextra"), - '-DDIFF_ENGINE_VERSION="0.1.3"', - ], - extra_link_args=["-lm"] if platform.system().lower() != "windows" else [], + extra_compile_args=( + ["-O3", "-std=c99", "-Wall", "-Wextra", + '-DDIFF_ENGINE_VERSION="0.1.3"'] + if system != "windows" else + ['/O2', '/DDIFF_ENGINE_VERSION="0.1.3"'] + ), + extra_link_args=(["-lm"] if system != "windows" else []) + blas_link_args, ) setup( From f5ee2a029712476eec45d50d6ee0d9f60be417ab Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 19:34:15 -0400 Subject: [PATCH 04/10] fix Linux BLAS include path for manylinux containers Add /usr/include/openblas to include_dirs on Linux so cblas.h resolves in manylinux (CentOS-based) containers where openblas-devel installs headers to /usr/include/openblas/ instead of /usr/include/. Co-Authored-By: Claude Opus 4.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e02b24f..42b9e39 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ def finalize_options(self) -> None: blas_include_dirs = [] elif system == "linux": blas_link_args = ["-lopenblas"] - blas_include_dirs = [] + blas_include_dirs = ["/usr/include/openblas"] else: # Windows: OpenBLAS via vcpkg vcpkg_root = os.environ.get( From 449589780d8c0dc8ea84189b9f9a36e2b80856f3 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 19:45:58 -0400 Subject: [PATCH 05/10] fix Windows vcpkg path in CI (backslash escaping issue) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CIBW_ENVIRONMENT_WINDOWS values are parsed by shlex, which treats backslashes as escape characters. Use forward slashes instead — Windows accepts them and they survive shlex parsing. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index d2ec3a5..d9ecfac 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -66,7 +66,7 @@ jobs: CIBW_ARCHS_LINUX: "auto aarch64" CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows" - CIBW_ENVIRONMENT_WINDOWS: "VCPKG_INSTALLED_DIR=C:\\vcpkg\\installed\\x64-windows" + CIBW_ENVIRONMENT_WINDOWS: "VCPKG_INSTALLED_DIR=C:/vcpkg/installed/x64-windows" - name: Check wheels shell: bash From d96fcc95eb6d842f4ad79f49b7840d2faf8e473a Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 19:53:27 -0400 Subject: [PATCH 06/10] cache vcpkg on Windows CI to skip OpenBLAS rebuild Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index d9ecfac..f63198b 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -57,6 +57,13 @@ jobs: with: platforms: all + - name: Cache vcpkg packages + if: runner.os == 'Windows' + uses: actions/cache@v4 + with: + path: C:\vcpkg\installed + key: vcpkg-windows-openblas-v1 + - name: Build wheels uses: pypa/cibuildwheel@v3.3.0 env: From 32c0c67b953690cb97107a0705fd1a70f1dc6e19 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 20:05:56 -0400 Subject: [PATCH 07/10] debug Windows cblas.h: add dir listing and openblas include fallback Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 2 +- setup.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index f63198b..1db67ff 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -72,7 +72,7 @@ jobs: CIBW_ARCHS_MACOS: "x86_64 universal2" CIBW_ARCHS_LINUX: "auto aarch64" CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" - CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows" + CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows && dir C:\\vcpkg\\installed\\x64-windows\\include\\ && dir C:\\vcpkg\\installed\\x64-windows\\lib\\" CIBW_ENVIRONMENT_WINDOWS: "VCPKG_INSTALLED_DIR=C:/vcpkg/installed/x64-windows" - name: Check wheels diff --git a/setup.py b/setup.py index 42b9e39..67ab935 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,10 @@ def finalize_options(self) -> None: r"C:\vcpkg\installed\x64-windows", ) blas_link_args = [os.path.join(vcpkg_root, "lib", "openblas.lib")] - blas_include_dirs = [os.path.join(vcpkg_root, "include")] + blas_include_dirs = [ + os.path.join(vcpkg_root, "include"), + os.path.join(vcpkg_root, "include", "openblas"), + ] sparsediffengine = Extension( "sparsediffpy._sparsediffengine", From 9667d490ea862211e48169cb1d1733a16616cb5f Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 20:07:32 -0400 Subject: [PATCH 08/10] skip BLAS and dense_matrix on Windows (matches C engine) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 10 ---------- setup.py | 19 ++++++++----------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 1db67ff..cd28c2a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -57,13 +57,6 @@ jobs: with: platforms: all - - name: Cache vcpkg packages - if: runner.os == 'Windows' - uses: actions/cache@v4 - with: - path: C:\vcpkg\installed - key: vcpkg-windows-openblas-v1 - - name: Build wheels uses: pypa/cibuildwheel@v3.3.0 env: @@ -72,9 +65,6 @@ jobs: CIBW_ARCHS_MACOS: "x86_64 universal2" CIBW_ARCHS_LINUX: "auto aarch64" CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" - CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows && dir C:\\vcpkg\\installed\\x64-windows\\include\\ && dir C:\\vcpkg\\installed\\x64-windows\\lib\\" - CIBW_ENVIRONMENT_WINDOWS: "VCPKG_INSTALLED_DIR=C:/vcpkg/installed/x64-windows" - - name: Check wheels shell: bash run: | diff --git a/setup.py b/setup.py index 67ab935..497631b 100644 --- a/setup.py +++ b/setup.py @@ -36,9 +36,13 @@ def finalize_options(self) -> None: # Collect all C source files from SparseDiffEngine +_exclude = {"dnlp_diff_engine"} +if system == "windows": + _exclude.add("dense_matrix") + diff_engine_sources = [ s for s in glob.glob("SparseDiffEngine/src/**/*.c", recursive=True) - if "dnlp_diff_engine" not in s + if not any(ex in s for ex in _exclude) ] + ["sparsediffpy/_bindings/bindings.c"] # Define _POSIX_C_SOURCE on Linux for clock_gettime and struct timespec @@ -54,16 +58,9 @@ def finalize_options(self) -> None: blas_link_args = ["-lopenblas"] blas_include_dirs = ["/usr/include/openblas"] else: - # Windows: OpenBLAS via vcpkg - vcpkg_root = os.environ.get( - "VCPKG_INSTALLED_DIR", - r"C:\vcpkg\installed\x64-windows", - ) - blas_link_args = [os.path.join(vcpkg_root, "lib", "openblas.lib")] - blas_include_dirs = [ - os.path.join(vcpkg_root, "include"), - os.path.join(vcpkg_root, "include", "openblas"), - ] + # Windows: no BLAS available + blas_link_args = [] + blas_include_dirs = [] sparsediffengine = Extension( "sparsediffpy._sparsediffengine", From f07a23c680aeaefc4be2ffafbe2e4caeee840f27 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Wed, 11 Mar 2026 20:20:31 -0400 Subject: [PATCH 09/10] fix Windows build: stub dense_matrix symbols and guard dense bindings Co-Authored-By: Claude Opus 4.6 --- setup.py | 3 +++ sparsediffpy/_bindings/bindings.c | 6 ++++++ sparsediffpy/_bindings/dense_matrix_stub.c | 25 ++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 sparsediffpy/_bindings/dense_matrix_stub.c diff --git a/setup.py b/setup.py index 497631b..dc7bae9 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,9 @@ def finalize_options(self) -> None: if not any(ex in s for ex in _exclude) ] + ["sparsediffpy/_bindings/bindings.c"] +if system == "windows": + diff_engine_sources.append("sparsediffpy/_bindings/dense_matrix_stub.c") + # Define _POSIX_C_SOURCE on Linux for clock_gettime and struct timespec defines = [] if system == "linux": diff --git a/sparsediffpy/_bindings/bindings.c b/sparsediffpy/_bindings/bindings.c index 9f3c4b0..02a0c37 100644 --- a/sparsediffpy/_bindings/bindings.c +++ b/sparsediffpy/_bindings/bindings.c @@ -11,7 +11,9 @@ #include "atoms/const_vector_mult.h" #include "atoms/constant.h" #include "atoms/cos.h" +#ifndef _MSC_VER #include "atoms/dense_matmul.h" +#endif #include "atoms/diag_vec.h" #include "atoms/entr.h" #include "atoms/exp.h" @@ -112,12 +114,16 @@ static PyMethodDef DNLPMethods[] = { {"make_xexp", py_make_xexp, METH_VARARGS, "Create xexp node"}, {"make_sparse_left_matmul", py_make_sparse_left_matmul, METH_VARARGS, "Create sparse left matmul node (A @ f(x))"}, +#ifndef _MSC_VER {"make_dense_left_matmul", py_make_dense_left_matmul, METH_VARARGS, "Create dense left matmul node (A @ f(x)) where A is dense"}, +#endif {"make_sparse_right_matmul", py_make_sparse_right_matmul, METH_VARARGS, "Create sparse right matmul node (f(x) @ A)"}, +#ifndef _MSC_VER {"make_dense_right_matmul", py_make_dense_right_matmul, METH_VARARGS, "Create dense right matmul node (f(x) @ A) where A is dense"}, +#endif {"make_quad_form", py_make_quad_form, METH_VARARGS, "Create quadratic form node (x' * Q * x)"}, {"make_quad_over_lin", py_make_quad_over_lin, METH_VARARGS, diff --git a/sparsediffpy/_bindings/dense_matrix_stub.c b/sparsediffpy/_bindings/dense_matrix_stub.c new file mode 100644 index 0000000..d7e3fe5 --- /dev/null +++ b/sparsediffpy/_bindings/dense_matrix_stub.c @@ -0,0 +1,25 @@ +/* + * Stub implementations for dense matrix functions on Windows, + * where BLAS is not available. These satisfy the linker for + * left_matmul.c which references them, but the dense matmul + * bindings are excluded via #ifndef _MSC_VER so they are + * never actually called. + */ +#ifdef _MSC_VER +#include "utils/matrix.h" +#include + +Matrix *new_dense_matrix(int m, int n, const double *data) +{ + (void) m; + (void) n; + (void) data; + return NULL; +} + +Matrix *dense_matrix_trans(const Dense_Matrix *self) +{ + (void) self; + return NULL; +} +#endif From 0646432184fa595453b2886963930a35ab20e2ef Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Thu, 12 Mar 2026 00:39:07 -0400 Subject: [PATCH 10/10] migrate build system from setuptools to scikit-build-core Replace the manual setup.py (which compiled ~49 C files with per-platform BLAS logic and Windows workarounds) with a top-level CMakeLists.txt that delegates to the submodule's existing CMake build. This eliminates the dense_matrix_stub.c, the #ifdef _MSC_VER guards in bindings.c, and enables dense matmul support on Windows via vcpkg OpenBLAS. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-and-publish.yml | 2 + CMakeLists.txt | 29 +++++++ pyproject.toml | 12 ++- setup.py | 89 ---------------------- sparsediffpy/_bindings/bindings.c | 6 -- sparsediffpy/_bindings/dense_matrix_stub.c | 25 ------ 6 files changed, 36 insertions(+), 127 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 setup.py delete mode 100644 sparsediffpy/_bindings/dense_matrix_stub.c diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index cd28c2a..2e27fa1 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -65,6 +65,8 @@ jobs: CIBW_ARCHS_MACOS: "x86_64 universal2" CIBW_ARCHS_LINUX: "auto aarch64" CIBW_BEFORE_BUILD_LINUX: "yum install -y openblas-devel || apt-get install -y libopenblas-dev" + CIBW_BEFORE_BUILD_WINDOWS: "vcpkg install openblas:x64-windows" + CIBW_ENVIRONMENT_WINDOWS: "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" - name: Check wheels shell: bash run: | diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c391e91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.15) +project(SparseDiffPy LANGUAGES C) + +add_subdirectory(SparseDiffEngine) + +find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module NumPy) + +# Linux: CMake find_package(BLAS) doesn't set include dirs; +# openblas puts cblas.h in /usr/include/openblas/ +if(UNIX AND NOT APPLE) + find_path(OPENBLAS_INCLUDE_DIR cblas.h PATHS /usr/include/openblas /usr/include) + if(OPENBLAS_INCLUDE_DIR) + target_include_directories(dnlp_diff PRIVATE ${OPENBLAS_INCLUDE_DIR}) + endif() +endif() + +python3_add_library(_sparsediffengine MODULE + sparsediffpy/_bindings/bindings.c +) + +target_include_directories(_sparsediffengine PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/SparseDiffEngine/include + ${CMAKE_CURRENT_SOURCE_DIR}/SparseDiffEngine/src + ${CMAKE_CURRENT_SOURCE_DIR}/sparsediffpy/_bindings +) + +target_link_libraries(_sparsediffengine PRIVATE dnlp_diff Python3::NumPy) + +install(TARGETS _sparsediffengine DESTINATION sparsediffpy) diff --git a/pyproject.toml b/pyproject.toml index f40d0e7..10465ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["numpy >= 2.0.0", "setuptools >= 68.1.0", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core >= 0.10", "numpy >= 2.0.0"] +build-backend = "scikit_build_core.build" [project] name = "sparsediffpy" @@ -14,8 +14,6 @@ license = "Apache-2.0" file = "README.md" content-type = "text/markdown" -[tool.setuptools] -include-package-data = false - -[tool.setuptools.packages.find] -include = ["sparsediffpy*"] +[tool.scikit-build] +cmake.minimum-version = "3.15" +wheel.packages = ["sparsediffpy"] diff --git a/setup.py b/setup.py deleted file mode 100644 index dc7bae9..0000000 --- a/setup.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Copyright 2025, the SparseDiffPy developers - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import builtins -import glob -import os -import platform - -from setuptools import Extension, setup -from setuptools.command.build_ext import build_ext - -system = platform.system().lower() - - -class build_ext_numpy(build_ext): - """Custom build_ext that injects NumPy headers.""" - - def finalize_options(self) -> None: - build_ext.finalize_options(self) - builtins.__NUMPY_SETUP__ = False - import numpy - self.include_dirs.append(numpy.get_include()) - - -# Collect all C source files from SparseDiffEngine -_exclude = {"dnlp_diff_engine"} -if system == "windows": - _exclude.add("dense_matrix") - -diff_engine_sources = [ - s for s in glob.glob("SparseDiffEngine/src/**/*.c", recursive=True) - if not any(ex in s for ex in _exclude) -] + ["sparsediffpy/_bindings/bindings.c"] - -if system == "windows": - diff_engine_sources.append("sparsediffpy/_bindings/dense_matrix_stub.c") - -# Define _POSIX_C_SOURCE on Linux for clock_gettime and struct timespec -defines = [] -if system == "linux": - defines.append(("_POSIX_C_SOURCE", "200809L")) - -# Platform-specific BLAS configuration -if system == "darwin": - blas_link_args = ["-framework", "Accelerate"] - blas_include_dirs = [] -elif system == "linux": - blas_link_args = ["-lopenblas"] - blas_include_dirs = ["/usr/include/openblas"] -else: - # Windows: no BLAS available - blas_link_args = [] - blas_include_dirs = [] - -sparsediffengine = Extension( - "sparsediffpy._sparsediffengine", - sources=diff_engine_sources, - include_dirs=[ - "SparseDiffEngine/include/", - "SparseDiffEngine/src/", - "sparsediffpy/_bindings/", - ] + blas_include_dirs, - define_macros=defines, - extra_compile_args=( - ["-O3", "-std=c99", "-Wall", "-Wextra", - '-DDIFF_ENGINE_VERSION="0.1.3"'] - if system != "windows" else - ['/O2', '/DDIFF_ENGINE_VERSION="0.1.3"'] - ), - extra_link_args=(["-lm"] if system != "windows" else []) + blas_link_args, -) - -setup( - cmdclass={"build_ext": build_ext_numpy}, - ext_modules=[sparsediffengine], -) diff --git a/sparsediffpy/_bindings/bindings.c b/sparsediffpy/_bindings/bindings.c index 02a0c37..9f3c4b0 100644 --- a/sparsediffpy/_bindings/bindings.c +++ b/sparsediffpy/_bindings/bindings.c @@ -11,9 +11,7 @@ #include "atoms/const_vector_mult.h" #include "atoms/constant.h" #include "atoms/cos.h" -#ifndef _MSC_VER #include "atoms/dense_matmul.h" -#endif #include "atoms/diag_vec.h" #include "atoms/entr.h" #include "atoms/exp.h" @@ -114,16 +112,12 @@ static PyMethodDef DNLPMethods[] = { {"make_xexp", py_make_xexp, METH_VARARGS, "Create xexp node"}, {"make_sparse_left_matmul", py_make_sparse_left_matmul, METH_VARARGS, "Create sparse left matmul node (A @ f(x))"}, -#ifndef _MSC_VER {"make_dense_left_matmul", py_make_dense_left_matmul, METH_VARARGS, "Create dense left matmul node (A @ f(x)) where A is dense"}, -#endif {"make_sparse_right_matmul", py_make_sparse_right_matmul, METH_VARARGS, "Create sparse right matmul node (f(x) @ A)"}, -#ifndef _MSC_VER {"make_dense_right_matmul", py_make_dense_right_matmul, METH_VARARGS, "Create dense right matmul node (f(x) @ A) where A is dense"}, -#endif {"make_quad_form", py_make_quad_form, METH_VARARGS, "Create quadratic form node (x' * Q * x)"}, {"make_quad_over_lin", py_make_quad_over_lin, METH_VARARGS, diff --git a/sparsediffpy/_bindings/dense_matrix_stub.c b/sparsediffpy/_bindings/dense_matrix_stub.c deleted file mode 100644 index d7e3fe5..0000000 --- a/sparsediffpy/_bindings/dense_matrix_stub.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Stub implementations for dense matrix functions on Windows, - * where BLAS is not available. These satisfy the linker for - * left_matmul.c which references them, but the dense matmul - * bindings are excluded via #ifndef _MSC_VER so they are - * never actually called. - */ -#ifdef _MSC_VER -#include "utils/matrix.h" -#include - -Matrix *new_dense_matrix(int m, int n, const double *data) -{ - (void) m; - (void) n; - (void) data; - return NULL; -} - -Matrix *dense_matrix_trans(const Dense_Matrix *self) -{ - (void) self; - return NULL; -} -#endif