PETSc C bindings

In some circumstances it is desirable to write PETSc C code instead of using the petsc4py Python bindings. For example:

  • The overhead involved in calling Python bindings may be unacceptable. This may be the case in very tight loops.

  • The desired API functionality is not available in petsc4py.

To support this, petsctools makes some of PETSc’s C API available to use through Cython. It does this in a similar way to petsc4py but emphasises readability and faithfulness to the API.

Demo

To demonstrate this, consider the following simple piece of PETSc code:

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()

This code is written in Python and consists of petsc4py API calls. The Python overhead is therefore maximised. Run on the author’s machine this code takes 4.0s to run to completion.

Compare this to the examples given in this Cython file:

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()

Here we have two cases. The first (medium) is very similar to slow but is able to partially compile itself because it is written in Cython. The second (fast) avoids petsc4py entirely and instead uses the PETSc C API directly as exposed through petsctools.

Switching to Cython and using the C API directly both lead to performance improvements. medium takes 1.8s and fast takes 0.78s.

Compiling Cython code

To compile the Cython code it must be registered as a compiled extension inside a setup.py file. A working example for the extension provided above looks like:

from setuptools import setup, Extension

import petsc4py
import petsctools


extension = Extension(
    name="fast",
    language="c",
    sources=["fast.pyx"],
    include_dirs=[
        petsc4py.get_include(),
        *petsctools.get_petsc_dirs(subdir="include"),
    ],
    library_dirs=petsctools.get_petsc_dirs(subdir="lib"),
    runtime_library_dirs=petsctools.get_petsc_dirs(subdir="lib"),
    libraries=["petsc", "mpi"],
)

setup(ext_modules=[extension])

This file should be added to your project along with a suitable pyproject.toml that indicates setuptools as the build-backend. setuptools, cython, petsc4py and petsctools are all necessary build-time dependencies.

Note

If you encounter errors like:

/.../petsc/include/petscsys.h:124:12: fatal error: mpi.h: No such file or directory
  124 |   #include <mpi.h>
      |            ^~~~~~~
compilation terminated.
error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1

then this usually means that setuptools cannot find your MPI distribution. To fix this simply set the environment variable CC=mpicc and try again.

Conventions used

  • All objects are available inside the cpetsc namespace after cimport-ing it (e.g. cpetsc.PetscSectionSetDof).

  • petsc4py PETSc objects are renamed to their C API equivalent with a _py suffix. For example cpetsc.PetscSection represents the C type and cpetsc.PetscSection_py the Cython type.

  • The C handle of the petsc4py objects are available through a specific attribute that depends on the type. Examples include:

    • cpetsc.Mat_py.matcpetsc.Mat

    • cpetsc.Vec_py.veccpetsc.Vec

    • cpetsc.IS_py.isetcpetsc.IS

    • cpetsc.PetscSection_py.seccpetsc.PetscSection

    For more information you will have to refer to the petsc4py source code.

Adding more functions

Adding additional PETSc functions to petsctools is straightforward. You simply have to add the bindings to the definitions file in a declarative way, just as you would write any other C file. For more information please refer to the Cython documentation.