Extender Qiskit en Python con C
Para acelerar tus programas Python de Qiskit con C, puedes usar la extensión C de Qiskit para Python. Esto requiere pasos adicionales al uso de C de manera independiente (standalone); para más detalles, consulta la Guía para instalar la API de C de Qiskit.
La API de C de Qiskit es todavía experimental, y aún no se ha comprometido con una interfaz de programación o binaria completamente estable. Los módulos de extensión compilados contra Qiskit solo se garantiza que funcionen contra la versión de Qiskit utilizada en la compilación.
Estas instrucciones solo han sido probadas en sistemas tipo UNIX. Las instrucciones para Windows están en progreso.
Requisitos
Comienza asegurándote de haber instalado la API de C de Qiskit. A continuación, instala la interfaz de Python de Qiskit, de la siguiente manera:
pip install -r requirements.txt -c constraints.txt
pip install .
Definir la extensión C
Existen varias opciones para escribir una extensión C para Python. Por simplicidad, esta guía comienza con un enfoque que usa el módulo integrado ctypes de Python. En la siguiente sección, la sección de Extensión C manual proporciona un ejemplo de cómo construir la extensión C usando la API de C de Python para reducir la sobrecarga en tiempo de ejecución (runtime overhead).
Como ejemplo, supongamos que escribes una función en C para construir un observable y te gustaría devolverlo
a Python. Puedes convertir un QkObs* del lado C en un objeto SparseObservable del lado Python, usando el convertidor proporcionado
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;
}
Lo siguiente demuestra cómo compilar esto en una biblioteca compartida (shared library) - por ejemplo, qiskit_cextension.so.
Una vez hecho esto, puedes invocar el programa C desde 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)
Compilación (Build)
Primero, tienes que compilar la extensión de Python de Qiskit. Esto incluye los símbolos C para que puedas acceder a ambas interfaces a través de la misma biblioteca compartida. Esto es importante para garantizar que los datos puedan pasarse correctamente entre C y Python.
python setup.py build_rust --inplace --release
La biblioteca compartida se llama _accelerate.<platform-specific-part>. Encuentra su ubicación y nombre de la siguiente manera:
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__))")
Necesitarás conocer la ubicación de los archivos de inclusión (includes) de Python en los entornos (Python.h) y de las bibliotecas (libpython.<suffix>).
Estos pueden, por ejemplo, identificarse 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")
(Si ya conoces estas ubicaciones y nombres, también puedes establecerlos directamente).
Vincular o enlazar (Link)
El enlazado (linking) puede diferir entre plataformas y vinculadores (linkers). Lo siguiente describe una solución para vinculadores
que soportan bibliotecas con nombres arbitrarios, usando la bandera (flag) -l: (como el vinculador ld de GNU).
Ve a continuación si tu vinculador requiere que la biblioteca se llame lib<something> (como el
vinculador ldd común en MacOS).
Puedes compilar la extensión especificando el nombre completo de la biblioteca _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
Luego, simplemente ingresa python main.py para ejecutar el programa de Python.
Una alternativa al uso del nombre exacto de la biblioteca con -l: es crear un enlace simbólico (symlink) de la biblioteca _accelerate
al nombre deseado.
Para incluir la biblioteca compartida _accelerate, crea un enlace simbólico al formato esperado por el vinculador (linker) de lib<library name>.<suffix>:
ln -s $QKLIB/$QKNAME $QKLIB/libqiskit.<suffix>
donde <suffix> es por ejemplo so en Linux o dylib en MacOS. Esto permite usar qiskit como nombre de la biblioteca:
gcc extension.c -fpic -shared -o qiskit_cextension.so \
-I/path/to/dist/c/include -L$QKLIB -lqiskit \
-I$PYINCLUDE -L$PYLIB -l$PYNAME
Luego, simplemente ingresa python main.py para ejecutar el programa de Python.
Extensión C manual
En lugar de usar ctypes, es posible compilar manualmente una extensión para Python usando la API de C
de Python directamente. Esto tiene el potencial de ser
más rápido que usar ctypes, aunque requiere más esfuerzo para implementarse.
El siguiente código es un breve ejemplo de cómo lograr esto.
// 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); }
Para compilar una biblioteca compartida, vincula tanto las bibliotecas de Python como las de Qiskit, tal y como se describe en
la sección Compilación (Build) de arriba. El script de Python entonces no necesita ctypes
sino que puede directamente importar el módulo cextension (asegúrate de que está en tu ruta/path de 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)