Aller au contenu principal

Étendre Qiskit en Python avec C

Pour accélérer tes programmes Qiskit Python avec C, tu peux utiliser l'extension C de Qiskit pour Python. Cela nécessite des étapes supplémentaires par rapport à l'utilisation autonome de C ; pour plus de détails, consulte le guide d'installation de l'API C Qiskit.

avertissement

L'API C de Qiskit est encore expérimentale et n'a pas encore adopté une interface de programmation ou binaire pleinement stable. Les modules d'extension compilés contre Qiskit ne sont garantis de fonctionner qu'avec la version de Qiskit utilisée lors de la compilation.

remarque

Ces instructions n'ont été testées que sur des systèmes de type UNIX. Les instructions pour Windows sont en cours d'élaboration.

Prérequis

Commence par t'assurer que tu as installé l'API C Qiskit. Ensuite, installe l'interface Python de Qiskit comme suit :

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

Définir l'extension C

Il existe plusieurs façons d'écrire une extension C pour Python. Pour simplifier, ce guide commence par une approche utilisant le module intégré ctypes de Python. Dans la section suivante, la section Extension C manuelle fournit un exemple de construction de l'extension C en utilisant l'API C de Python pour réduire la surcharge à l'exécution.

À titre d'exemple, suppose que tu écris une fonction C pour construire un observable et que tu souhaites le retourner à Python. Tu peux convertir un QkObs* côté C en un objet Python SparseObservable côté Python, en utilisant le convertisseur fourni 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;
}

Ce qui suit montre comment compiler cela en une bibliothèque partagée — par exemple, qiskit_cextension.so. Une fois cela fait, tu peux appeler le programme C depuis 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)

Compilation

Tout d'abord, tu dois compiler l'extension Python de Qiskit. Cela inclut les symboles C afin que tu puisses accéder aux deux interfaces via la même bibliothèque partagée. C'est important pour garantir que les données peuvent être transmises correctement entre C et Python.

python setup.py build_rust --inplace --release

La bibliothèque partagée s'appelle _accelerate.<partie-spécifique-à-la-plateforme>. Trouve son emplacement et son nom comme suit :

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__))")

Tu auras besoin de connaître l'emplacement des en-têtes Python (Python.h) et des bibliothèques (libpython.<suffix>) de l'environnement. Ces informations peuvent par exemple être identifiées avec :

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

(Si tu connais déjà ces emplacements et ces noms, tu peux aussi les définir directement.)

La liaison peut différer selon les plateformes et les éditeurs de liens. Ce qui suit décrit une solution pour les éditeurs de liens prenant en charge les bibliothèques avec des noms arbitraires, en utilisant le drapeau -l: (comme l'éditeur de liens ld de GNU). Consulte la section ci-dessous si ton éditeur de liens exige que la bibliothèque s'appelle lib<quelque-chose> (comme l'éditeur de liens ldd courant sur MacOS).

Tu peux compiler l'extension en spécifiant le nom complet de la bibliothèque _accelerate :

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

Ensuite, il te suffit de saisir python main.py pour exécuter le programme Python.

Une alternative à l'utilisation du nom exact de la bibliothèque avec -l: consiste à créer un lien symbolique de la bibliothèque _accelerate vers le nom souhaité. Pour inclure la bibliothèque partagée _accelerate, crée un lien symbolique vers le format attendu par l'éditeur de liens, à savoir lib<nom-de-la-bibliothèque>.<suffix> :

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

<suffix> est par exemple so sous Linux ou dylib sous MacOS. Cela permet d'utiliser qiskit comme nom de bibliothèque :

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

Ensuite, il te suffit de saisir python main.py pour exécuter le programme Python.

Extension C manuelle

Au lieu d'utiliser ctypes, il est possible de construire manuellement une extension pour Python en utilisant directement l'API C de Python. Cela peut être plus rapide que d'utiliser ctypes, bien que cela demande plus d'effort à mettre en œuvre. Le code suivant est un bref exemple de la façon d'y parvenir.

// 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); }

Pour compiler une bibliothèque partagée, lie les bibliothèques Python et Qiskit, comme décrit dans la section Compilation ci-dessus. Le script Python n'a alors pas besoin de ctypes mais peut importer directement le module cextension (assure-toi qu'il est dans ton chemin Python) :

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