Rozszerzanie Qiskit w Pythonie za pomocą C
Aby przyspieszyć swoje programy Qiskit w Pythonie za pomocą C, możesz użyć rozszerzenia C Qiskit dla Pythona. Wymaga to dodatkowych kroków w porównaniu do samodzielnego użycia C; więcej szczegółów znajdziesz w przewodniku instalacji Qiskit C API.
Qiskit C API jest nadal eksperymentalne i nie zobowiązało się jeszcze do w pełni stabilnego interfejsu programistycznego ani binarnego. Moduły rozszerzeń zbudowane na podstawie Qiskit są gwarantowane do działania wyłącznie z wersją Qiskit użytą podczas kompilacji.
Te instrukcje były testowane wyłącznie na systemach uniksopodobnych. Instrukcje dla systemu Windows są w trakcie przygotowania.
Wymagania
Zacznij od upewnienia się, że masz zainstalowane Qiskit C API. Następnie zainstaluj interfejs Python Qiskit w następujący sposób:
pip install -r requirements.txt -c constraints.txt
pip install .
Definiowanie rozszerzenia C
Istnieje wiele sposobów na napisanie rozszerzenia C dla Pythona. Dla uproszczenia, ten przewodnik zaczyna od podejścia korzystającego z wbudowanego modułu Pythona ctypes. W następnej sekcji, sekcja Ręczne rozszerzenie C, zawiera przykład budowania rozszerzenia C przy użyciu C API Pythona w celu zmniejszenia narzutu czasu działania.
Jako przykład załóżmy, że piszesz funkcję C do budowania obserwabli i chcesz ją zwrócić do Pythona. Możesz przekonwertować QkObs* po stronie C na obiekt SparseObservable po stronie Pythona, używając dostarczonego konwertera 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;
}
Poniżej przedstawiono, jak skompilować to do biblioteki współdzielonej — na przykład qiskit_cextension.so.
Po wykonaniu tej operacji możesz wywołać program C z Pythona:
# 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)
Kompilacja
Najpierw musisz zbudować rozszerzenie Pythona Qiskit. Zawiera ono symbole C, dzięki czemu możesz uzyskać dostęp do obu interfejsów za pośrednictwem tej samej biblioteki współdzielonej. Jest to ważne, aby zapewnić prawidłowe przekazywanie danych między C i Pythonem.
python setup.py build_rust --inplace --release
Biblioteka współdzielona nosi nazwę _accelerate.<część-specyficzna-dla-platformy>. Znajdź jej lokalizację i nazwę w następujący sposób:
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__))")
Będziesz potrzebować informacji o lokalizacji plików nagłówkowych Pythona środowiska (Python.h) i bibliotek (libpython.<suffix>).
Można je na przykład zidentyfikować za pomocą:
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")
(Jeśli znasz już te lokalizacje i nazwy, możesz je ustawić bezpośrednio.)
Linkowanie
Linkowanie może się różnić w zależności od platformy i linkera. Poniżej opisano rozwiązanie dla linkerów obsługujących biblioteki o dowolnych nazwach, używających flagi -l: (np. linker ld GNU). Zapoznaj się z poniższymi informacjami, jeśli twój linker wymaga, aby biblioteka była nazwana lib<coś> (np. linker ldd powszechnie stosowany w systemie MacOS).
Możesz zbudować rozszerzenie, podając pełną nazwę biblioteki _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
Następnie wystarczy wpisać python main.py, aby uruchomić program Python.
Alternatywą dla użycia dokładnej nazwy biblioteki z -l: jest utworzenie dowiązania symbolicznego biblioteki _accelerate do żądanej nazwy.
Aby uwzględnić bibliotekę współdzieloną _accelerate, utwórz dowiązanie symboliczne do oczekiwanego przez linker formatu lib<nazwa biblioteki>.<suffix>:
ln -s $QKLIB/$QKNAME $QKLIB/libqiskit.<suffix>
gdzie <suffix> to np. so na Linuksie lub dylib na MacOS. Pozwala to używać qiskit jako nazwy biblioteki:
gcc extension.c -fpic -shared -o qiskit_cextension.so \
-I/path/to/dist/c/include -L$QKLIB -lqiskit \
-I$PYINCLUDE -L$PYLIB -l$PYNAME
Następnie wystarczy wpisać python main.py, aby uruchomić program Python.
Ręczne rozszerzenie C
Zamiast używać ctypes, można ręcznie zbudować rozszerzenie dla Pythona bezpośrednio przy użyciu C API Pythona. Może to być szybsze niż użycie ctypes, choć wymaga więcej wysiłku w implementacji.
Poniższy kod jest krótkim przykładem, jak to osiągnąć.
// 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); }
Aby skompilować bibliotekę współdzieloną, zlinkuj zarówno biblioteki Python, jak i Qiskit, zgodnie z opisem w sekcji Kompilacja powyżej. Skrypt Python nie potrzebuje wtedy ctypes, ale może bezpośrednio importować moduł cextension (upewnij się, że znajduje się on na twojej ścieżce 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)