Skip to main contentIBM Quantum Documentation
Important

IBM Quantum Platform is moving and this version will be sunset on July 1. To get started on the new platform, read the migration guide.

Extend Qiskit in Python with C

To accelerate your Qiskit Python programs with C, you can use the Qiskit C extension for Python. This requires additional steps to the standalone C usage; for more details, see the Install the Qiskit C API guide.

Warning

The Qiskit C API is still experimental, and has not yet committed to a fully stable programming or binary interface. Extension modules built against Qiskit are only guaranteed to work against the version of Qiskit used in the build.

Note

These instructions have only been tested on UNIX-like systems. Windows instructions are in progress._


Requirements

Start by ensuring you have installed the Qiskit C API. Next, install the Qiskit Python interface, as follows:

pip install -r requirements.txt -c constraints.txt
pip install .

Define the C extension

There are various options to write a C extension for Python. For simplicity, this guide starts with an approach that uses Python's built-in ctypes module. In the next section, the the Manual C extension section provides an example of building the C extension using Python's C API to reduce runtime overhead.

As an example, assume you write a C function to build an observable and would like to return it to Python. You can convert a C-side QkObs* to a Python-side SparseObservable object (both are backed by the same Rust data structure), using the provided converter qk_obs_to_python:

// file: extension.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>  // include Python header for access to PyObject
#define QISKIT_C_PYTHON_INTERFACE  // enable C->Python conversion functions
#include <qiskit.h>
 
PyObject *build_observable(void) {
    QkObs *obs = qk_obs_zero(100);
    // build the observable ...
    PyObject *pyobj = qk_obs_to_python(obs); // convert to Qiskit's Python ``SparseObservable``
    qk_obs_free(obs);
    return pyobj;
}

The following demonstrates how to compile this into a shared library - for example, qiskit_cextension.so. Once this is done, you can call the C program from Python:

# file: main.py
import qiskit
import ctypes
 
# Load the extension, ensuring the global interpreter lock (GIL) is acquired for function calls,
# which you need for the C->Python object conversion.
lib = ctypes.PyDLL("/path/to/qiskit_cextension.so")
lib.build_observable.argtypes = None  # set argument types to the function
lib.build_observable.restype = ctypes.py_object  # set return type
 
# now you can directly call the function
obs = lib.build_observable()
print("SparseObservable instance?", isinstance(obs, qiskit.quantum_info.SparseObservable))
print(obs)

Build

First, you have to build the Qiskit Python extension. This includes the C symbols so you can access both interfaces via the same shared library. This is important to ensure data can be passed correctly across C and Python.

python setup.py build_rust --inplace --release

The shared library is called _accelerate.<platform-specific-part>. Find its location and name as follows:

QKLIB=$(python -c "import os; import qiskit; print(os.path.dirname(qiskit._accelerate.__file__))")
QKNAME=$(python -c "import os; import qiskit; print(os.path.basename(qiskit._accelerate.__file__))")

You will need to know the location of the environments Python includes (Python.h) and libraries (libpython.<suffix>). These can, for example, be identified with

PYINCLUDE=$(python -c "import sysconfig; print(sysconfig.get_path('include'))")
PYLIB=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))")
PYNAME=$(find $PYLIB -maxdepth 1 -name "libpython*" | grep -oE "[^/]+$" | grep -oE "python[0-9]+\.[0-9]+" || echo "python")

(If you already know these locations and names, you can also just set them directly.)

Linking can differ among platforms and linkers. The following describes a solution for linkers supporting libraries with arbitrary names, using the -l: flag (such as GNU's ld linker). See below if your linker requires the library to be called lib<something> (such as the ldd linker common on MacOS).

You can build the extension specifying the full name of the _accelerate library:

gcc extension.c -fpic -shared -o cextension.so \
  -I/path/to/dist/c/include -L$QKLIB -l:$QKNAME \
  -I$PYINCLUDE -L$PYLIB -l$PYNAME

Then, simply enter python main.py to run the Python program.

An alternative to using the exact library name with -l: is to symlink the _accelerate library to the desired name. To include the _accelerate shared library, symlink it to the linker's expected format of lib<library name>.<suffix>:

ln -s $QKLIB/$QKNAME $QKLIB/libqiskit.<suffix>

where <suffix> is e.g. so on Linux or dylib on MacOS. This allows to use qiskit as library name:

gcc extension.c -fpic -shared -o qiskit_cextension.so \
  -I/path/to/dist/c/include -L$QKLIB -lqiskit \
  -I$PYINCLUDE -L$PYLIB -l$PYNAME

Then, simply enter python main.py to run the Python program.


Manual C extension

Instead of using ctypes, it is possible to manually build an extension for Python using Python's C API directly. This has the potential to be faster than using ctypes, though it requires more effort to implement. The following code is a brief example on how to achieve this.

// file: extension.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
 
#include <stdio.h>
#define QISKIT_C_PYTHON_INTERFACE
#include <qiskit.h>
 
QkObs *build_observable() {
    // build a 100-qubit empty observable
    u_int32_t num_qubits = 100;
    QkObs *obs = qk_obs_zero(num_qubits);
 
    // add the term 2 * (X0 Y1 Z2) to the observable
    complex double coeff = 2;  // the coefficient
    QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};  // bit terms: X Y Z
    uint32_t indices[3] = {0, 1, 2};  // indices: 0 1 2
    QkObsTerm term = {coeff, 3, bit_terms, indices, num_qubits};
    qk_obs_add_term(obs, &term);  // append the term
 
    return obs;
}
 
/// Define the Python function, which will internally build the QkObs using the
/// C function defined above, and then convert the C object to the Python equivalent:
/// a SparseObservable, handled as PyObject.
static PyObject *cextension_build_observable(PyObject *self, PyObject *args) {
    // At this point, ``args`` could be parsed for arguments. See PyArg_ParseTuple for details.
    QkObs *obs = build_observable();  // call the C function to build the observable
    PyObject *py_obs = qk_obs_to_python(obs);  // convert QkObs to the Python-equivalent
    return py_obs;
}
 
/// Define the module methods.
static PyMethodDef CExtMethods[] = {
    {"build_observable", cextension_build_observable, METH_VARARGS, "Build an observable."},
    {NULL, NULL, 0, NULL}, // sentinel
};
 
/// Define the module, which here is called ``cextension``.
static struct PyModuleDef cextension = {
    PyModuleDef_HEAD_INIT,
    "cextension", // module name
    NULL,         // docs
    -1,           // keep the module state in global variables
    CExtMethods,
};
 
PyMODINIT_FUNC PyInit_cextension(void) { return PyModule_Create(&cextension); }
 
int main(int argc, char *argv[]) {
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);
 
    // Add a built-in module, before Py_Initialize.
    if (PyImport_AppendInittab("cextension", PyInit_cextension) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }
 
    // Pass argv[0] to the Python interpreter.
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
 
    // Initialize the Python interpreter.
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);
 
    // Import the module.
    PyObject *pmodule = PyImport_ImportModule("cextension");
    if (!pmodule) {
        PyErr_Print();
        fprintf(stderr, "Error: could not import module 'cextension'\n");
    }
 
    return 0;
 
exception:
    PyConfig_Clear(&config);
    Py_ExitStatusException(status);
}

To compile a shared library, link both the Python and Qiskit libraries, as described in the Build section above. The Python script then does not need ctypes but can directly import cextension module (ensure it is on your Python path):

# file: main.py
import qiskit
import cextension
 
# directly call the function
obs = cextension.build_observable()
print("SparseObservable instance?", isinstance(obs, qiskit.quantum_info.SparseObservable))
print(obs)
Was this page helpful?
Report a bug or request content on GitHub.