Langkau ke kandungan utama

Lanjutkan Qiskit dalam Python dengan C

Qiskit C API boleh digunakan dalam modul sambungan Python. Anda boleh menulis bahagian kritikal prestasi sambungan Qiskit anda dalam C untuk mempercepatkannya, dan kemudian mengedarkannya dengan selamat kepada pengguna anda.

Panduan ini membawa anda melalui proses mentakrifkan modul sambungan yang lengkap, mengkonfigurasi proses pembinaannya, dan mendedahkannya kepada pengguna Python. Pakej ini menyediakan port mudah bagi AddSpectatorMeasures daripada tambahan Qiskit kepada C. Ini ialah laluan tersuai sebenar dengan kes penggunaan sebenar dalam tambahan Qiskit.

petua

Anda mungkin mendapati sumber luaran berikut berguna:

Qiskit C API didedahkan untuk modul sambungan Python dengan cara yang sangat serupa dengan NumPy C API. Jika anda pernah mengaturcara sambungan NumPy sebelum ini, anda akan mendapati proses Qiskit ini sudah biasa.

amaran

Qiskit C API masih dalam peringkat eksperimental. Oleh itu, belum ada antara muka pengaturcaraan atau binari yang sepenuhnya stabil, dan mungkin ada perubahan yang memecahkan antara versi kecil.

Sebagai contoh, modul sambungan yang menggunakan Qiskit v2.4.0 semasa pembinaan dijamin berfungsi dengan Qiskit v2.4.1 semasa masa jalan, tetapi mungkin gagal apabila menggunakan Qiskit v2.5.0 semasa masa jalan.

Keperluan​

Mulakan dari direktori yang bersih.

Anda mesti mempunyai rantaian alat pengkompil C standard yang tersedia untuk platform anda. Anda juga mesti mempunyai versi Python yang menyertakan pengepala C API-nya (ini adalah standard).

Anda seharusnya biasa dengan, atau bersedia untuk mencari, fungsi dan objek individu yang tersedia dalam Qiskit C API. Anda seharusnya mempunyai sedikit kebiasaan dengan pengaturcaraan C.

Cipta struktur direktori​

Kami akan menggunakan struktur direktori berasaskan src dan sistem pembinaan berasaskan setuptools yang mudah. Arahan ini seharusnya mudah disesuaikan dengan mana-mana sistem pembinaan yang boleh membina modul sambungan.

Struktur akhir akan kelihatan seperti:

extension-module
β”œβ”€β”€ pyproject.toml
β”œβ”€β”€ setup.py
└── src
└── spectator_measures
β”œβ”€β”€ __init__.py
└── _coremodule.c

Ringkasnya:

  • pyproject.toml mentakrifkan metadata statik standard tentang pakej Python yang kami cipta, termasuk nama, pengarang, dan kebergantungan masa pembinaan dan masa jalan.
  • setup.py mengandungi konfigurasi dinamik minimum yang kami perlukan untuk membina modul sambungan kami.
  • src/spectator_measures/__init__.py mentakrifkan antara muka yang berhadapan dengan pengguna dan menyediakan beberapa kod untuk berinteraksi dengan komponen ruang-Python Qiskit.
  • src/spectator_measures/_coremodule.c mentakrifkan modul sambungan C, yang akan mengandungi semua kod kritikal prestasi pakej kami.

Kami akan memeriksa setiap fail secara terperinci, membina pakej dengan modul sambungannya.

Takrifkan metadata pakej​

Mulakan dengan mentakrifkan fail pyproject.toml. Ini adalah standard untuk projek berasaskan setuptools, walaupun qiskit adalah keperluan tambahan dalam tatasusunan build-system.requires, sebagai tambahan kepada 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"}

Mulai Qiskit v2.4, C API belum lagi stabil di luar versi kecil (sebagai contoh, C API untuk v2.4.0 akan serasi dengan v2.4.1 tetapi bukan v2.5.0). Pada masa depan, kami berhasrat untuk melanjutkan kestabilan ini kepada versi utama. Buat masa ini, tetapkan versi masa jalan Qiskit dalam project.dependencies untuk sepadan dengan versi kecil yang digunakan semasa pembinaan.

Dalam banyak projek berasaskan setuptools Python tulen, fail pyproject.toml sudah mencukupi. Walau bagaimanapun, modul kami memerlukan akses kepada fail pengepala Qiskit C API semasa proses pembinaannya. Bermula dengan v2.4, ini disertakan dalam pengedaran Python SDK Qiskit. Untuk mencari direktori yang mengandunginya, jalankan qiskit.capi.get_include(). Ini menghasilkan fail setup.py yang kelihatan 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])

Kebanyakan maklumat pakej ditakrifkan dalam pyproject.toml, dan setuptools.setup() akan turut membaca fail tersebut.

petua

Lihat Panduan Pengguna setuptools untuk maklumat lanjut tentang mengkonfigurasi projek berasaskan setuptools.

Tulis pembalut ruang-Python​

Secara teknikalnya, adalah mungkin untuk mentakrifkan segalanya dalam sambungan Python daripada C. Dalam amalan, ia lebih mudah untuk berinteraksi dengan kod ruang-Python lain daripada Python itu sendiri.

Pakej ini mentakrifkan laluan Transpiler tersuai yang diterbitkan daripada kelas qiskit.transpiler.TransformationPass ruang-Python, tetapi menggunakan fungsi daripada modul sambungan C untuk semua logik perniagaannya. Ini kelihatan 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

Butiran tepat laluan ini tidak penting untuk panduan ini. Jika anda berminat, anda boleh merujuk dokumentasi API AddSpectatorMeasures dalam qiskit-addon-utils. Panduan ini menghasilkan port mudah bagi laluan tersebut, tanpa sokongan untuk operasi aliran kawalan.

Tulis modul sambungan C​

Bahagian ini berkaitan dengan sambungan C sebenar. Ini adalah fail paling kompleks dalam projek, jadi kami akan membahagikannya kepada peringkat-peringkat.

Konfigurasi fail pengepala​

Apabila membina modul sambungan Python, anda mesti menyertakan Python.h sebelum sebarang fail lain. Untuk menggunakan Qiskit C API dalam modul sambungan, anda mesti mentakrifkan makro QISKIT_PYTHON_EXTENSION sebelum menyertakan qiskit.h.

Include kami kemudiannya kelihatan 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>

Tulis kod C API tulen​

Seterusnya, tulis semua logik perniagaan sebagai kod Qiskit C API tulen. Kami akan mendedahkan logik ini kepada ruang Python dalam bahagian berikut.

Bahagian ini mengandungi hanya kod Qiskit C API tulen. Ia menggunakan jenis C API:

  • QkDag *, sepadan dengan DAGCircuit ruang-Python.
  • QkTarget *, sepadan dengan Target ruang-Python.
  • QkNeighbors, jenis C API natif yang mewakili kekangan gandingan dua qubit.
  • QkCircuitInstruction, jenis C API natif untuk menanya arahan individu.

Dua yang pertama membentuk sebahagian daripada interaksi kami dengan ruang Python, tetapi apabila bekerja dengannya, kami hanya perlu mempertimbangkan C API tulen. Tiada interaksi dengan jurubahasa Python dalam kod ini.

Perhatikan bahawa semua fungsi dan simbol yang ditakrifkan dalam bahagian ini dinyatakan dengan paut static. Ini kerana jurubahasa Python tidak akan paut terhadap modul sambungan ini; kami akan menyediakan jurubahasa dengan butiran fungsi yang tersedia dalam bahagian seterusnya.

Kami tidak akan memperincikan butiran algoritma kod ini; adalah instruktif untuk menggunakan laluan Transpiler yang bermakna untuk demonstrasi, tetapi pelaksanaan tepat algoritma tersebut 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;
}

Tulis kod interaksi Python​

Semua logik perniagaan kini ditakrifkan dalam C tulen. Seterusnya, ia perlu didedahkan dengan selamat kepada Python.

Untuk memulakan, takrifkan satu-satunya fungsi yang akan didedahkan kepada Python. Ini mesti mengikut tandatangan yang ditakrifkan, yang sepenuhnya dalam sebutan jenis Python yang kelihatan seperti kaedah fn(self, *args, **kwargs). Kami perlu mengembalikan PyObject *, yang merupakan bentuk generik bagi sebarang objek Python.

Fungsi lengkap kelihatan 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;
}

Ringkasnya, fungsi ini:

  1. Mengikuti tandatangan yang ditakrifkan untuk menerima argumen Python sewenang-wenangnya.
  2. Mentakrifkan ruang untuk menyimpan objek natif-C yang dihuraikan daripada argumen Python.
  3. Memanggil fungsi penghuraian untuk mengekstrak objek natif-C, dikonfigurasi dengan senarai argumen yang dijangka, argumen kata kunci, dan fungsi yang digunakan untuk menukarnya. Jika ini gagal, fungsi menyebarkan ralat.
  4. Mengagihkan kepada logik perniagaan natif-C bahagian sebelumnya, yang mengubah DAG di tempatnya.
  5. Mengembalikan objek None ruang-Python.

Logik paling kompleks semuanya berada dalam PyArg_ParseTupleAndKeywords. Ini didokumentasikan dengan baik dalam dokumentasi CPython tentang menghurai argumen, yang patut anda rujuk untuk maklumat lanjut.

Qiskit C API menyediakan beberapa fungsi dengan nama seperti qk_*_convert_from_python, yang direka sebagai fungsi "penukar" untuk digunakan dengan fungsi PyArg_Parse*. Ini sepadan dengan kunci O& dalam rentetan format; di sini, kami menggunakan qk_dag_convert_from_python dan qk_target_convert_from_python. Fungsi-fungsi ini meminjam objek natif-C daripada argumen Python yang ia diterbitkan. Ini bermakna mutasi akan disebarkan ke ruang Python, tetapi juga bahawa anda harus berhati-hati untuk tidak melepaskan rujukan anda kepada objek Python yang menyokongnya, semasa menggunakan hasilnya. Ini adalah standard untuk pengaturcaraan Python C API.

Seterusnya, kami mentakrifkan maklumat tentang modul ini dan fungsi yang dikandunginya, supaya kami boleh menghantar ia 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,
};

Jadual kaedah dan struktur takrifan-modul ini diterangkan dengan lebih terperinci dalam dokumentasi CPython tentang permulaan modul.

Akhirnya, beritahu Python cara memulakan modul. Ini adalah satu-satunya fungsi dalam fail C yang dieksport. Namanya mesti sepadan tepat dengan corak PyInit_<mod>, di mana <mod> adalah nama modul (tidak layak). Dalam kes ini, nama modul yang layak sepenuhnya ialah spectator_measures._core, dan nama yang tidak layak ialah _core, jadi fungsi kami mesti dipanggil PyInit__core, dengan garis bawah berganda.

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 kedua-duanya adalah pengaturcaraan Python C API standard. Komponen khusus Qiskit ialah qk_import(). Adalah kritikal bahawa anda memanggil fungsi ini semasa fungsi permulaan modul anda; anda tidak akan dapat memanggil sebarang fungsi Qiskit C API sehingga ini berjaya dilaksanakan.

Gunakan pakej daripada Python​

Ini kini merupakan pakej yang lengkap, termasuk modul sambungan C. Oleh sebab hanya alatan standard digunakan, dan tiada perpustakaan sistem bukan-standard yang dipaut semasa masa pembinaan, proses pembinaan adalah mudah.

Anda boleh menggunakan mana-mana alatan pembinaan serasi PEP-517. Sebagai contoh minimum, anda boleh menjalankan arahan berikut dalam akar repositori untuk memasang pakej.

pip install .

Ini mengkompil modul sambungan C dan memasang pakej Python yang lengkap dalam persekitaran anda.

Contoh penggunaan laluan Transpiler tersuai ini ialah:

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 ialah:

β”Œβ”€β”€β”€β” β–‘
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