Memperluas Qiskit di Python dengan C
Qiskit C API bisa digunakan di dalam modul ekstensi Python. Kamu bisa menulis bagian-bagian yang kritis terhadap performa dari ekstensi Qiskit kamu dalam C untuk mempercepatnya, lalu mendistribusikannya dengan aman ke pengguna kamu.
Panduan ini memandu kamu melalui proses mendefinisikan modul ekstensi yang lengkap, mengonfigurasi
proses build-nya, dan mengeksposnya ke pengguna Python. Paket ini menyediakan port sederhana dari
AddSpectatorMeasures dari Qiskit addons ke C. Ini adalah pass kustom nyata
dengan kasus penggunaan nyata di Qiskit addons.
Kamu mungkin merasa sumber daya eksternal berikut bermanfaat:
Qiskit C API diekspos untuk modul ekstensi Python dengan cara yang sangat mirip dengan NumPy C API. Jika kamu sebelumnya pernah memprogram ekstensi NumPy, kamu akan merasa proses Qiskit ini familier.
Qiskit C API masih bersifat eksperimental. Jadi, belum ada antarmuka pemrograman atau biner yang sepenuhnya stabil, dan mungkin ada perubahan yang merusak di antara versi minor.
Sebagai contoh, modul ekstensi yang menggunakan Qiskit v2.4.0 saat build dijamin bekerja dengan Qiskit v2.4.1 saat runtime, tetapi mungkin rusak saat menggunakan Qiskit v2.5.0 saat runtime.
Persyaratanβ
Mulai dari direktori yang bersih.
Kamu harus memiliki toolchain compiler C standar yang tersedia untuk platform kamu. Kamu juga harus memiliki versi Python yang menyertakan header C API-nya (ini standar).
Kamu sebaiknya familier dengan, atau siap untuk mencari, masing-masing fungsi dan objek yang tersedia di Qiskit C API. Kamu sebaiknya memiliki sedikit kemampuan dalam pemrograman C.
Membuat struktur direktoriβ
Kita akan menggunakan struktur direktori berbasis src dan sistem build sederhana berbasis setuptools. Instruksi
ini seharusnya mudah diadaptasi ke sistem build apa pun yang bisa membangun
modul ekstensi.
Struktur akhirnya akan terlihat seperti:
extension-module
βββ pyproject.toml
βββ setup.py
βββ src
βββ spectator_measures
βββ __init__.py
βββ _coremodule.c
Singkatnya:
pyproject.tomlmendefinisikan metadata statis standar tentang paket Python yang kita buat, termasuk nama, penulis, serta dependensi build- dan run-time-nya.setup.pyberisi konfigurasi dinamis minimal yang kita butuhkan untuk membangun modul ekstensi kita.src/spectator_measures/__init__.pymendefinisikan antarmuka yang dihadapkan ke pengguna dan menyediakan kode untuk berinteraksi dengan komponen ruang-Python milik Qiskit.src/spectator_measures/_coremodule.cmendefinisikan modul ekstensi C, yang akan berisi semua kode yang kritis terhadap performa dari paket kita.
Kita akan memeriksa setiap file secara detail, membangun paket beserta modul ekstensinya.
Mendefinisikan metadata paketβ
Mulailah dengan mendefinisikan file pyproject.toml. Ini standar untuk proyek berbasis setuptools,
meskipun qiskit merupakan persyaratan tambahan di dalam array build-system.requires,
selain setuptools.
pyproject.toml
[build-system]
requires = [
"setuptools",
"qiskit~=2.4.0",
]
build-backend = "setuptools.build_meta"
[project]
name = "spectator_measures"
authors = [
{ name = "Qiskit Developer" },
]
version = "0.0.1"
dependencies = [
"qiskit~=2.4.0",
]
# If you intend to release your package, you should
# also set the `license` information, and so on.
[tool.setuptools]
package-dir = {"" = "src"}
Pada Qiskit v2.4, C API belum stabil di luar versi minor (sebagai contoh, C API untuk v2.4.0 akan
kompatibel dengan v2.4.1 tetapi tidak dengan v2.5.0). Ke depannya, kita berniat memperluas
stabilitas ini hingga ke versi mayor. Untuk saat ini, atur versi runtime Qiskit di
project.dependencies agar cocok dengan versi minor yang digunakan saat build.
Di banyak proyek berbasis setuptools yang murni Python, cukup memiliki file
pyproject.toml saja. Namun, modul kita membutuhkan akses ke file header Qiskit C API selama
proses build-nya. Mulai dari v2.4, file-file ini disertakan dalam distribusi Python Qiskit SDK.
Untuk menemukan direktori yang berisinya, jalankan qiskit.capi.get_include().
Ini menghasilkan file setup.py yang terlihat seperti:
setup.py
import qiskit
from setuptools import setup, Extension
core_ext = Extension(
# The fully qualified module name of the extension.
name="spectator_measures._core",
# The C source files needed for the extension. The file
# name is conventionally `<mod>module.c`, where `<mod>`
# is the module name (`_core`, in this case).
sources=["src/spectator_measures/_coremodule.c"],
# Directories containing additional header files used in
# the build process.
include_dirs=[qiskit.capi.get_include()],
)
setup(ext_modules=[core_ext])
Sebagian besar informasi paket didefinisikan di pyproject.toml, dan setuptools.setup() juga
akan membaca file tersebut.
Lihat Panduan Pengguna setuptools untuk informasi
lebih lanjut tentang mengonfigurasi proyek berbasis setuptools.
Menulis wrapper ruang-Pythonβ
Secara teknis, dimungkinkan untuk mendefinisikan semua hal dalam ekstensi Python dari C. Dalam praktiknya, lebih mudah berinteraksi dengan kode ruang-Python lainnya dari Python itu sendiri.
Paket ini mendefinisikan pass Transpiler kustom yang diturunkan dari kelas ruang-Python
qiskit.transpiler.TransformationPass, tetapi menggunakan fungsi dari modul ekstensi C untuk
semua logika bisnisnya. Ini terlihat seperti:
src/spectator_measures/__init__.py
from qiskit.transpiler import TransformationPass, Target
from . import _core
__version__ = "0.0.1"
__all__ = ["AddSpectatorMeasures"]
class AddSpectatorMeasures(TransformationPass):
def __init__(
self,
target: Target,
*,
include_unmeasured: bool = False,
creg_name: str | None = None,
add_barrier: bool = True
):
super().__init__()
self.target = target
self.include_unmeasured = include_unmeasured
self.creg_name = creg_name
self.add_barrier = add_barrier
def run(self, dag):
# Delegate to our C extension module.
_core.add_spectator_measures(
dag,
self.target,
include_unmeasured=self.include_unmeasured,
creg_name=self.creg_name,
add_barrier=self.add_barrier,
)
return dag
Detail pasti dari pass ini tidak penting untuk panduan ini. Jika kamu tertarik, kamu bisa
melihat dokumentasi API AddSpectatorMeasures di
qiskit-addon-utils. Panduan ini menghasilkan port sederhana dari pass tersebut,
tanpa dukungan untuk operasi control-flow.
Menulis modul ekstensi Cβ
Kamu mungkin merasa sumber daya berikut bermanfaat:
Bagian ini berkaitan dengan ekstensi C yang sebenarnya. Ini adalah file paling kompleks dalam proyek, jadi kita akan membaginya menjadi beberapa tahap.
Mengonfigurasi file headerβ
Saat membangun modul ekstensi Python, kamu harus menyertakan Python.h sebelum file lainnya.
Untuk menggunakan Qiskit C API dalam modul ekstensi, kamu harus mendefinisikan makro
QISKIT_PYTHON_EXTENSION sebelum menyertakan qiskit.h.
Includes kita kemudian terlihat seperti:
src/spectator_measures/_coremodule.c
#define QISKIT_PYTHON_EXTENSION
#include <Python.h>
#include <qiskit.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
Menulis kode C API murniβ
Selanjutnya, tulis semua logika bisnis sebagai kode Qiskit C API murni. Kita akan mengekspos logika ini ke ruang-Python di bagian berikut.
Bagian ini hanya berisi kode Qiskit C API murni. Ia menggunakan tipe-tipe C API:
QkDag *, yang berkorespondensi denganDAGCircuitdi ruang-Python.QkTarget *, yang berkorespondensi denganTargetdi ruang-Python.QkNeighbors, tipe C API native yang merepresentasikan batasan coupling dua-Qubit.QkCircuitInstruction, tipe C API native untuk mengkueri masing-masing instruksi.
Dua yang pertama merupakan bagian dari interaksi kita dengan ruang-Python, tetapi saat bekerja dengannya, kita hanya perlu mempertimbangkan C API murni. Tidak ada interaksi dengan interpreter Python dalam kode ini.
Perhatikan bahwa semua fungsi dan simbol yang didefinisikan di bagian ini dideklarasikan dengan linkage static.
Ini karena interpreter Python tidak akan melakukan link terhadap modul ekstensi ini; kita akan menyediakan
detail fungsi-fungsi yang tersedia kepada interpreter di bagian berikutnya.
Kita tidak akan berkutat pada detail algoritmis dari kode ini; berguna menggunakan pass Transpiler yang bermakna untuk demonstrasi, tetapi implementasi persis dari algoritmanya tidak penting untuk panduan ini.
src/spectator_measures/_coremodule.c (appended)
/**
* The default name to use for `creg_name` if none is supplied.
*/
static char DEFAULT_CREG_NAME[] = "spec";
/**
* Is there a 2q link from the given qubit to any active qubit?
*/
static bool adjacent_to_active(QkNeighbors *adj, uint32_t qubit,
bool *active) {
for (uint32_t offset = adj->partition[qubit];
offset < adj->partition[qubit + 1]; offset++) {
if (active[adj->neighbors[offset]]) {
return true;
}
}
return false;
}
/**
* A transpiler pass that adds terminal measurements to all "spectator"
* qubits.
*/
static uint32_t add_spectator_measures(QkDag *dag,
const QkTarget *target,
bool include_unmeasured,
const char *creg_name,
bool add_barrier) {
uint32_t num_spectators = 0;
uint32_t num_qubits = qk_dag_num_qubits(dag);
uint32_t num_instructions = qk_dag_num_op_nodes(dag);
bool *active = calloc(num_qubits, sizeof(*active));
bool *is_additional_spectator =
calloc(num_qubits, sizeof(*is_additional_spectator));
uint32_t *spectators = malloc(num_qubits * sizeof(*spectators));
uint32_t *topological =
malloc(num_instructions * sizeof(*topological));
QkNeighbors neighbors;
QkCircuitInstruction instruction;
qk_neighbors_from_target(target, &neighbors);
qk_dag_topological_op_nodes(dag, topological);
for (uint32_t i = 0; i < num_instructions; i++) {
qk_dag_get_instruction(dag, topological[i], &instruction);
if (!strcmp(instruction.name, "barrier")) {
// Barriers don't count for the purposes of determining
// final measurements, either.
qk_circuit_instruction_clear(&instruction);
continue;
}
// If we're not adding measurements to "unmeasured" active
// qubits, then nothing counts as an additional "maybe
// spectator". If we are, then it's a maybe spectator if its
// last visited instruction was not a measure.
bool additional_spectator =
include_unmeasured && strcmp(instruction.name, "measure");
for (uint32_t *qarg = instruction.qubits;
qarg != instruction.qubits + instruction.num_qubits;
qarg++) {
active[*qarg] = true;
is_additional_spectator[*qarg] = additional_spectator;
}
qk_circuit_instruction_clear(&instruction);
}
for (uint32_t qubit = 0; qubit < num_qubits; qubit++) {
bool is_spectator =
!active[qubit] &&
adjacent_to_active(&neighbors, qubit, active);
is_spectator = is_spectator || is_additional_spectator[qubit];
if (is_spectator) {
spectators[num_spectators] = qubit;
num_spectators += 1;
}
}
if (num_spectators) {
uint32_t clbit = qk_dag_num_clbits(dag);
creg_name = creg_name ? creg_name : DEFAULT_CREG_NAME;
QkClassicalRegister *creg =
qk_classical_register_new(num_spectators, creg_name);
qk_dag_add_classical_register(dag, creg);
qk_classical_register_free(creg);
if (add_barrier) {
qk_dag_apply_barrier(dag, NULL, num_qubits, false);
}
for (uint32_t i = 0; i < num_spectators; i++) {
qk_dag_apply_measure(dag, spectators[i], clbit + i, false);
}
}
qk_neighbors_clear(&neighbors);
free(topological);
free(spectators);
free(is_additional_spectator);
free(active);
return num_spectators;
}
Menulis kode interaksi Pythonβ
Semua logika bisnis kini didefinisikan dalam C murni. Selanjutnya, ia perlu diekspos dengan aman ke Python.
Untuk memulai, definisikan satu-satunya fungsi yang akan diekspos ke Python. Ini harus
mengikuti signature tertentu, yang murni dalam bentuk tipe-tipe Python yang terlihat seperti
metode fn(self, *args, **kwargs). Kita harus mengembalikan sebuah PyObject *, yang merupakan bentuk generik dari
objek Python apa pun.
Fungsi lengkapnya terlihat seperti:
src/spectator_measures/_coremodule.c (appended)
static PyObject *py_add_spectator_measures(PyObject *self,
PyObject *args,
PyObject *kwargs) {
// Define space to hold the C-native handles we will parse out of the
// Python-space inputs.
QkDag *dag;
QkTarget *target;
const char *creg_name;
int include_unmeasured, add_barrier;
// This `kwlist` and `PyArg_Parse*` setup is standard Python C API
// programming for extension modules. We will examine the use of
// Qiskit C API functions within it afterwards.
static char *const kwlist[] = {
"dag", "target", "include_unmeasured",
"creg_name", "add_barrier", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&O&|pzp", kwlist,
qk_dag_convert_from_python, &dag,
qk_target_convert_from_python,
&target, &include_unmeasured,
&creg_name, &add_barrier)) {
// An error has occurred. The Python exception state will already
// be set, so we need to return the error indicator.
return NULL;
}
// Now we have C-native types, we can delegate to our C logic.
add_spectator_measures(dag, target, include_unmeasured, creg_name,
add_barrier);
Py_RETURN_NONE;
}
Singkatnya, fungsi ini:
- Mengikuti signature tertentu untuk menerima argumen Python sembarang.
- Mendefinisikan ruang untuk menyimpan objek C-native yang di-parse dari argumen Python.
- Memanggil fungsi parsing untuk mengekstrak objek C-native, dikonfigurasi dengan daftar argumen yang diharapkan, keyword argument, dan fungsi yang digunakan untuk mengonversinya. Jika ini gagal, fungsi tersebut mempropagasi error.
- Mendelegasikan ke logika bisnis C-native dari bagian sebelumnya, yang memutasi DAG di tempat.
- Mengembalikan objek
Nonedi ruang-Python.
Logika paling kompleks ada di dalam PyArg_ParseTupleAndKeywords. Ini terdokumentasi dengan baik di dokumentasi
CPython tentang parsing argumen, yang sebaiknya kamu
lihat untuk informasi lebih lanjut.
Qiskit C API menyediakan beberapa fungsi dengan nama seperti qk_*_convert_from_python, yang
dirancang sebagai fungsi "konverter" untuk digunakan dengan fungsi PyArg_Parse*. Fungsi-fungsi ini berkorespondensi dengan
kunci O& di string format; di sini, kita menggunakan qk_dag_convert_from_python dan
qk_target_convert_from_python. Fungsi-fungsi ini meminjam objek C-native dari argumen Python
asalnya. Ini berarti mutasi akan terpropagasi ke ruang-Python, tetapi juga berarti
kamu harus berhati-hati untuk tidak melepaskan referensi kamu ke objek Python yang menjadi penopangnya, saat menggunakan
hasilnya. Ini standar untuk pemrograman Python C API.
Selanjutnya, kita definisikan informasi tentang modul ini dan fungsi yang ada di dalamnya, sehingga kita bisa meneruskannya ke ruang-Python:
src/spectator_measures/_coremodule.c (appended)
static PyMethodDef core_methods[] = {
// This entry is our function, cast to the correct type.
{"add_spectator_measures",
(PyCFunction)(void (*)(void))py_add_spectator_measures,
METH_VARARGS | METH_KEYWORDS, ""},
// A sentinel marking the end of the list.
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef core_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "_core",
.m_methods = core_methods,
};
Tabel metode dan struktur definisi-modul ini dijelaskan lebih detail di dokumentasi CPython tentang inisialisasi modul.
Terakhir, beri tahu Python cara menginisialisasi modul. Ini adalah satu-satunya fungsi di file
C yang diekspor. Namanya harus sama persis dengan pola
PyInit_<mod>, di mana <mod> adalah nama modul (tanpa kualifikasi). Dalam kasus ini, nama modul
yang terkualifikasi penuh adalah spectator_measures._core, dan nama tanpa kualifikasinya adalah _core, jadi
fungsi kita harus dinamai PyInit__core, dengan garis bawah ganda.
src/spectator_measures/_coremodule.c (appended)
PyMODINIT_FUNC PyInit__core(void) {
// This line is critical to use the Qiskit C API. Your code will
// likely be immediately terminated by the operating system if you
// forget to do this.
if (qk_import() < 0) {
return NULL;
};
// The standard Python call to initialize a module.
return PyModuleDef_Init(&core_module);
}
Simbol PyMODINIT_FUNC dan PyModuleDef_Init keduanya merupakan pemrograman Python C API standar.
Komponen yang spesifik untuk Qiskit adalah qk_import(). Sangat penting kamu memanggil fungsi ini selama
fungsi inisialisasi modul kamu; kamu tidak akan bisa memanggil fungsi Qiskit C API apa pun
sampai ini berhasil dijalankan.
Menggunakan paket dari Pythonβ
Ini sekarang menjadi paket yang lengkap, termasuk modul ekstensi C. Karena hanya tooling standar yang digunakan, dan tidak ada library sistem non-standar yang di-link saat build time, proses build-nya sederhana.
Kamu bisa menggunakan alat build apa pun yang kompatibel dengan PEP-517. Sebagai contoh minimal, kamu bisa menjalankan perintah berikut di root repositori untuk menginstal paket.
pip install .
Ini mengkompilasi modul ekstensi C dan menginstal paket Python lengkap di environment kamu.
Contoh penggunaan pass Transpiler kustom ini adalah:
from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap, Target
from spectator_measures import AddSpectatorMeasures
num_qubits = 10
qc = QuantumCircuit(num_qubits)
qc.x(0)
qc.x(5)
target = Target.from_configuration(
basis_gates=["x", "sx", "rz", "cx"],
num_qubits=num_qubits,
coupling_map=CouplingMap.from_line(num_qubits),
)
pass_ = AddSpectatorMeasures(target)
pass_(qc).draw()
Hasilnya adalah:
βββββ β
q_0: β€ X βββββββββββββ
βββββ β βββ
q_1: βββββββββ€Mβββββββ
β ββ₯β
q_2: ββββββββββ«βββββββ
β β
q_3: ββββββββββ«βββββββ
β β βββ
q_4: ββββββββββ«ββ€Mββββ
βββββ β β ββ₯β
q_5: β€ X ββββββ«βββ«ββββ
βββββ β β β βββ
q_6: ββββββββββ«βββ«ββ€Mβ
β β β ββ₯β
q_7: ββββββββββ«βββ«βββ«β
β β β β
q_8: ββββββββββ«βββ«βββ«β
β β β β
q_9: ββββββββββ«βββ«βββ«β
β β β β
spec: 3/ββββββββββ©βββ©βββ©β
0 1 2