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.
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.
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.)
Link
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)