From e77bdd70d8e8d29a23d6819236e20fc586b1cad5 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 24 Nov 2025 16:18:02 +0000 Subject: [PATCH 1/4] WIP Add Cython bindings for the PETSc API --- petsctools/__init__.pxd | 0 petsctools/cpetsc.pxd | 56 +++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 3 +++ 3 files changed, 59 insertions(+) create mode 100644 petsctools/__init__.pxd create mode 100644 petsctools/cpetsc.pxd diff --git a/petsctools/__init__.pxd b/petsctools/__init__.pxd new file mode 100644 index 0000000..e69de29 diff --git a/petsctools/cpetsc.pxd b/petsctools/cpetsc.pxd new file mode 100644 index 0000000..4693d9a --- /dev/null +++ b/petsctools/cpetsc.pxd @@ -0,0 +1,56 @@ +"""This file basically exposes the PETSc API as a module for use in Cython.""" +from petsc4py cimport PETSc as _PETSc + +# clearer aliases from petsc4py, so the names here match the C API +ctypedef _PETSc.PetscMat Mat +ctypedef _PETSc.Mat Mat_py +ctypedef _PETSc.PetscSF PetscSF +ctypedef _PETSc.SF PetscSF_py +ctypedef _PETSc.PetscSection PetscSection +ctypedef _PETSc.Section PetscSection_py +ctypedef _PETSc.PetscIS IS +ctypedef _PETSc.IS IS_py + +# other PETSc imports +from petsc4py.PETSc cimport ( + CHKERR, + PetscErrorCode, +) + + +cdef extern from "petsc.h": + # fundamental types + ctypedef long PetscInt + ctypedef double PetscReal + ctypedef double PetscScalar + ctypedef enum PetscBool: + PETSC_TRUE + PETSC_FALSE + ctypedef enum InsertMode: + INSERT_VALUES + ADD_VALUES + ctypedef enum PetscCopyMode: + PETSC_COPY_VALUES + PETSC_OWN_POINTER + PETSC_USE_POINTER + + # memory management + PetscErrorCode PetscCalloc1(size_t,void*) + PetscErrorCode PetscMalloc1(size_t,void*) + PetscErrorCode PetscFree(void*) + + # Mat + PetscErrorCode MatSetValue(Mat,PetscInt,PetscInt,const PetscScalar,InsertMode) + PetscErrorCode MatSetValuesBlockedLocal(Mat,PetscInt,const PetscInt[],PetscInt,const PetscInt[],const PetscScalar[],InsertMode) + + # PetscSF + ctypedef struct PetscSFNode: + pass + + PetscErrorCode PetscSFGetGraph(PetscSF,PetscInt*,PetscInt*,PetscInt**,PetscSFNode**) + PetscErrorCode PetscSFSetGraph(PetscSF,PetscInt,PetscInt,PetscInt*,PetscCopyMode,PetscSFNode*,PetscCopyMode) + + # PetscSection + PetscErrorCode PetscSectionGetDof(PetscSection,PetscInt,PetscInt*) + PetscErrorCode PetscSectionSetDof(PetscSection,PetscInt,PetscInt) + PetscErrorCode PetscSectionGetOffset(PetscSection,PetscInt,PetscInt*) diff --git a/pyproject.toml b/pyproject.toml index 0d155d8..4bc3255 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ ci = [ {include-group = "test"}, ] +[tool.setuptools.package-data] +petsctools = ["__init__.pxd", "cpetsc.pxd"] + [tool.ruff] line-length = 79 From b87c05e57d126e1411b51ffec49551fa7eb412a8 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 24 Nov 2025 16:20:10 +0000 Subject: [PATCH 2/4] fixup --- petsctools/cpetsc.pxd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/petsctools/cpetsc.pxd b/petsctools/cpetsc.pxd index 4693d9a..07567a6 100644 --- a/petsctools/cpetsc.pxd +++ b/petsctools/cpetsc.pxd @@ -1,4 +1,7 @@ """This file basically exposes the PETSc API as a module for use in Cython.""" + +# IMPORTANT: This file cannot be accessed if petsctools is installed in editable mode. + from petsc4py cimport PETSc as _PETSc # clearer aliases from petsc4py, so the names here match the C API From 8a3196faf101a4ae0b59f7e40ffae170b8d417d5 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 24 Nov 2025 16:41:12 +0000 Subject: [PATCH 3/4] Add demo, will be included in the docs --- .gitignore | 7 ++- docs/source/_static/cython-demo/fast.pyx | 36 ++++++++++++++ docs/source/_static/cython-demo/setup.py | 63 ++++++++++++++++++++++++ docs/source/_static/cython-demo/slow.py | 18 +++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 docs/source/_static/cython-demo/fast.pyx create mode 100644 docs/source/_static/cython-demo/setup.py create mode 100644 docs/source/_static/cython-demo/slow.py diff --git a/.gitignore b/.gitignore index 614fae7..436e9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,12 @@ petsctools/config.ini -*/__pycache__ +**/__pycache__ *.egg-info docs/build docs/source/generated + +docs/source/_static/cython-demo/build +docs/source/_static/cython-demo/*.c +docs/source/_static/cython-demo/*.so +docs/source/_static/cython-demo/*.html diff --git a/docs/source/_static/cython-demo/fast.pyx b/docs/source/_static/cython-demo/fast.pyx new file mode 100644 index 0000000..fea9f2a --- /dev/null +++ b/docs/source/_static/cython-demo/fast.pyx @@ -0,0 +1,36 @@ +import time + +import cython +from petsc4py import PETSc + +from petsctools cimport cpetsc + + +def medium(): + N: cython.int = int(1e8) + section: PETSc.Section = PETSc.Section().create() + section.setChart(0, N) + + start = time.time() + i: cython.int + for i in range(N): + if i % 2 == 0: + section.setDof(i, 1) + print(f"Time elapsed: {time.time() - start}") + + +def fast(): + N: cython.int = int(1e8) + section: cpetsc.PetscSection_py = PETSc.Section().create() + section.setChart(0, N) + + start = time.time() + i: cython.int + for i in range(N): + if i % 2 == 0: + cpetsc.CHKERR(cpetsc.PetscSectionSetDof(section.sec, i, 1)) + print(f"Time elapsed: {time.time() - start}") + + +medium() +fast() diff --git a/docs/source/_static/cython-demo/setup.py b/docs/source/_static/cython-demo/setup.py new file mode 100644 index 0000000..4e7cbdc --- /dev/null +++ b/docs/source/_static/cython-demo/setup.py @@ -0,0 +1,63 @@ +from Cython.Build import cythonize +from setuptools import setup, Extension + +import petsctools +import os +import petsc4py + + +from dataclasses import dataclass, field + + +@dataclass +class ExternalDependency: + ''' This dataclass stores the relevant information for the compiler as fields + that correspond to the keyword arguments of `Extension`. For convenience it + also implements addition and `**` unpacking. + ''' + include_dirs: list[str] = field(default_factory=list, init=True) + extra_compile_args: list[str] = field(default_factory=list, init=True) + libraries: list[str] = field(default_factory=list, init=True) + library_dirs: list[str] = field(default_factory=list, init=True) + extra_link_args: list[str] = field(default_factory=list, init=True) + runtime_library_dirs: list[str] = field(default_factory=list, init=True) + + def __add__(self, other): + combined = {} + for f in self.__dataclass_fields__.keys(): + combined[f] = getattr(self, f) + getattr(other, f) + return self.__class__(**combined) + + def keys(self): + return self.__dataclass_fields__.keys() + + def __getitem__(self, key): + try: + return getattr(self, key) + except AttributeError: + raise KeyError(f"Key {key} not present") + + + +petsc_dir = petsctools.get_petsc_dir() +petsc_arch = petsctools.get_petsc_arch() +petsc_dirs = [petsc_dir, os.path.join(petsc_dir, petsc_arch)] +petsc_ = ExternalDependency( + libraries=["petsc"], + include_dirs=[petsc4py.get_include()] + [os.path.join(d, "include") for d in petsc_dirs], + library_dirs=[os.path.join(petsc_dirs[-1], "lib")], + runtime_library_dirs=[os.path.join(petsc_dirs[-1], "lib")], +) + +mods = [ + Extension( + name="cython_demo", + language="c", + sources=[os.path.join("cython_demo.pyx")], + **(petsc_), + annotate=True, + ) +] + + +setup(ext_modules=mods) diff --git a/docs/source/_static/cython-demo/slow.py b/docs/source/_static/cython-demo/slow.py new file mode 100644 index 0000000..82acb94 --- /dev/null +++ b/docs/source/_static/cython-demo/slow.py @@ -0,0 +1,18 @@ +import time + +from petsc4py import PETSc + + +def slow(): + N = int(1e8) + section = PETSc.Section().create() + section.setChart(0, N) + + start = time.time() + for i in range(N): + if i % 2 == 0: + section.setDof(i, 1) + print(f"Time elapsed: {time.time() - start}") + + +slow() From 0019c193d5d8dec222715752ef4bb0de43667909 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 26 Nov 2025 15:07:19 +0000 Subject: [PATCH 4/4] PetscSFNode fix --- petsctools/cpetsc.pxd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/petsctools/cpetsc.pxd b/petsctools/cpetsc.pxd index 07567a6..fb55c2c 100644 --- a/petsctools/cpetsc.pxd +++ b/petsctools/cpetsc.pxd @@ -48,9 +48,10 @@ cdef extern from "petsc.h": # PetscSF ctypedef struct PetscSFNode: - pass + PetscInt rank + PetscInt index - PetscErrorCode PetscSFGetGraph(PetscSF,PetscInt*,PetscInt*,PetscInt**,PetscSFNode**) + PetscErrorCode PetscSFGetGraph(PetscSF,PetscInt*,PetscInt*,const PetscInt**,const PetscSFNode**) PetscErrorCode PetscSFSetGraph(PetscSF,PetscInt,PetscInt,PetscInt*,PetscCopyMode,PetscSFNode*,PetscCopyMode) # PetscSection