メインコンテンツへスキップ

CでQiskitをPython向けに拡張する

Qiskit PythonプログラムをCで高速化するには、Python向けQiskit C拡張モジュールを使用します。 これはスタンドアロンのC利用に対して追加の手順が必要です。詳細はQiskit C APIインストールガイドを参照してください。

警告

Qiskit C APIはまだ実験的なものであり、安定したプログラミングインターフェースやバイナリインターフェースへのコミットは完了していません。Qiskitに対してビルドされた拡張モジュールは、ビルド時に使用したQiskitのバージョンでのみ動作が保証されます。

備考

これらの手順はUNIX系システムでのみテスト済みです。Windowsの手順は現在作成中です。

必要条件

まずQiskit C APIがインストール済みであることを確認してください。次に、以下の手順でQiskit Pythonインターフェースをインストールします:

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

C拡張モジュールの定義

PythonのC拡張モジュールを記述するにはさまざまな方法があります。このガイドでは簡単のため、Pythonに組み込みのctypesモジュールを使うアプローチから始めます。次のセクション「手動C拡張モジュール」では、PythonのC APIを使ってランタイムオーバーヘッドを削減するC拡張モジュールのビルド例を紹介します。

例として、オブザーバブルを構築するC関数を作成し、それをPythonに返したいとします。C側のQkObs*をPython側のSparseObservableオブジェクトに変換するには、提供されているコンバーター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;
}

以下は、これを共有ライブラリ(例:qiskit_cextension.so)にコンパイルする方法を示しています。 完了後、PythonからCプログラムを呼び出すことができます:

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

ビルド

まず、Qiskit Python拡張モジュールをビルドします。これにはCのシンボルも含まれており、同一の共有ライブラリを通じて両方のインターフェースにアクセスできます。CとPython間でデータが正しく受け渡されるようにするために重要です。

python setup.py build_rust --inplace --release

共有ライブラリは_accelerate.<プラットフォーム固有の部分>という名前になっています。以下のコマンドでその場所と名前を確認できます:

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

環境のPythonインクルードファイル(Python.h)とライブラリ(libpython.<suffix>)の場所も確認する必要があります。 たとえば、以下のコマンドで特定できます:

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

(これらの場所や名前が既にわかっている場合は、直接設定することもできます。)

リンクの方法はプラットフォームやリンカーによって異なります。以下は、-l:フラグを使って任意の名前のライブラリをサポートするリンカー(GNU のldリンカーなど)向けの方法です。 MacOSでよく使われるlddリンカーのようにライブラリ名がlib<something>形式でなければならないリンカーを使用している場合は、以下を参照してください。

_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

その後、python main.pyと入力するだけでPythonプログラムを実行できます。

-l:による正確なライブラリ名指定の代替手段として、_accelerateライブラリをシンボリックリンクで任意の名前に変換する方法もあります。 _accelerate共有ライブラリを含めるには、リンカーが期待するlib<ライブラリ名>.<suffix>の形式でシンボリックリンクを作成します:

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

ここで<suffix>はLinuxではso、MacOSではdylibなどです。これによりqiskitをライブラリ名として使用できます:

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

その後、python main.pyと入力するだけでPythonプログラムを実行できます。

手動C拡張モジュール

ctypesを使用する代わりに、PythonのC APIを直接使って手動でPython拡張モジュールをビルドすることも可能です。これはctypesを使う場合より高速になる可能性がありますが、実装にはより多くの手間がかかります。 以下のコードはその実現方法の簡単な例です。

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

共有ライブラリをコンパイルするには、上記のビルドセクションで説明したとおり、PythonとQiskitの両ライブラリをリンクします。Pythonスクリプトではctypesは不要で、cextensionモジュールを直接インポートできます(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)