Vai al contenuto principale

Estendere Qiskit in Python con C

Per accelerare i tuoi programmi Python Qiskit con C, puoi usare l'estensione C di Qiskit per Python. Questo richiede alcuni passi aggiuntivi rispetto all'utilizzo standalone di C; per ulteriori dettagli, consulta la guida all'installazione dell'API C di Qiskit.

avviso

L'API C di Qiskit è ancora sperimentale e non garantisce ancora un'interfaccia di programmazione o binaria completamente stabile. I moduli di estensione compilati contro Qiskit sono garantiti per funzionare solo con la versione di Qiskit usata nella compilazione.

nota

Queste istruzioni sono state testate solo su sistemi di tipo UNIX. Le istruzioni per Windows sono in fase di elaborazione.

Requisiti​

Inizia assicurandoti di aver installato l'API C di Qiskit. Poi installa l'interfaccia Python di Qiskit, come segue:

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

Definire l'estensione C​

Esistono varie opzioni per scrivere un'estensione C per Python. Per semplicità, questa guida parte da un approccio che usa il modulo integrato di Python ctypes. Nella sezione successiva, la sezione Estensione C manuale fornisce un esempio di come costruire l'estensione C usando direttamente la C API di Python per ridurre l'overhead a runtime.

Come esempio, supponiamo che tu scriva una funzione C per costruire un osservabile e voglia restituirla a Python. Puoi convertire un QkObs* lato C in un oggetto SparseObservable lato Python, usando il convertitore fornito 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;
}

Il seguente esempio mostra come compilarlo in una libreria condivisa — per esempio, qiskit_cextension.so. Una volta fatto ciò, puoi chiamare il programma C da 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)

Compilazione​

Per prima cosa devi compilare l'estensione Python di Qiskit. Questo include i simboli C in modo da poter accedere a entrambe le interfacce tramite la stessa libreria condivisa. È importante per garantire che i dati possano essere passati correttamente tra C e Python.

python setup.py build_rust --inplace --release

La libreria condivisa si chiama _accelerate.<parte-specifica-della-piattaforma>. Trova la sua posizione e il suo nome come segue:

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

Dovrai conoscere la posizione degli header Python dell'ambiente (Python.h) e delle librerie (libpython.<suffisso>). Questi possono, ad esempio, essere individuati con:

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

(Se conosci già queste posizioni e questi nomi, puoi anche impostarli direttamente.)

Il linking può variare a seconda della piattaforma e del linker. Di seguito viene descritta una soluzione per i linker che supportano librerie con nomi arbitrari, usando il flag -l: (come il linker ld di GNU). Leggi più avanti se il tuo linker richiede che la libreria si chiami lib<qualcosa> (come il linker ldd comune su MacOS).

Puoi compilare l'estensione specificando il nome completo della libreria _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

Poi, digita semplicemente python main.py per eseguire il programma Python.

Un'alternativa all'uso del nome esatto della libreria con -l: è creare un symlink della libreria _accelerate con il nome desiderato. Per includere la libreria condivisa _accelerate, crea un symlink nel formato atteso dal linker lib<nome libreria>.<suffisso>:

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

dove <suffix> è, ad esempio, so su Linux o dylib su MacOS. Questo permette di usare qiskit come nome della libreria:

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

Poi, digita semplicemente python main.py per eseguire il programma Python.

Estensione C manuale​

Invece di usare ctypes, è possibile costruire manualmente un'estensione per Python usando direttamente la C API di Python. Questo ha il potenziale di essere più veloce rispetto all'uso di ctypes, anche se richiede più lavoro da implementare. Il seguente codice è un breve esempio su come ottenere questo risultato.

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

Per compilare una libreria condivisa, collega sia le librerie Python che quelle di Qiskit, come descritto nella sezione Compilazione precedente. Lo script Python non ha quindi bisogno di ctypes ma può importare direttamente il modulo cextension (assicurati che sia nel tuo 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)