Palawakin ang Qiskit sa Python gamit ang C
Ang Qiskit C API ay maaaring gamitin sa loob ng mga Python extension module. Maaari kang sumulat ng mga performance-critical na seksyon ng iyong mga Qiskit extension sa C para mapabilis ang mga ito, at pagkatapos ay ligtas na ipamahagi ang mga ito sa iyong mga gumagamit.
Ginagabayan ka ng gabay na ito sa proseso ng pagtatakda ng isang kumpletong extension module, pag-configure ng
proseso ng pagbuo nito, at pag-expose nito sa mga gumagamit ng Python. Nagbibigay ang package ng isang simpleng port ng
AddSpectatorMeasures mula sa Qiskit addons sa C. Ito ay isang tunay na custom
pass na may tunay na kaso ng paggamit sa Qiskit addons.
Maaaring makapagtulong ang mga sumusunod na panlabas na mapagkukunan:
- Ang dokumentasyon ng CPython sa pagsulat ng mga extension module.
- Ang dokumentasyon ng NumPy sa paggamit ng C API nito.
Ang Qiskit C API ay ini-expose para sa mga Python extension module sa paraang katulad ng NumPy C API. Kung nakapag-program ka na ng NumPy extension dati, makikita mong pamilyar ang proseso ng Qiskit.
Ang Qiskit C API ay eksperimental pa rin. Kaya naman, wala pa nitong ganap na matatag na programming o binary interface, at maaaring magkaroon ng mga breaking change sa pagitan ng mga minor na bersyon.
Halimbawa, ang isang extension module na gumagamit ng Qiskit v2.4.0 sa oras ng pagbuo ay garantisadong gagana sa Qiskit v2.4.1 sa oras ng runtime, ngunit maaaring masira kapag gumagamit ng Qiskit v2.5.0 sa oras ng runtime.
Mga Kinakailanganβ
Magsimula mula sa isang malinis na direktoryo.
Kailangan mong magkaroon ng karaniwang C compiler toolchain na available para sa iyong platform. Kailangan mo ring magkaroon ng bersyon ng Python na kasama ang mga C API header nito (ito ay standard).
Dapat kang pamilyar sa, o handa na hanapin, ang mga indibidwal na function at object na available sa Qiskit C API. Dapat kang may kaunting kaalaman sa C programming.
Gumawa ng istruktura ng direktoryoβ
Gagamit tayo ng src-based na istruktura ng direktoryo at isang simpleng setuptools-based na build system. Ang mga
instruksyong ito ay dapat madaling iaangkop sa anumang build system na makakapagtayo ng
mga extension module.
Ang panghuling istruktura ay magiging ganito:
extension-module
βββ pyproject.toml
βββ setup.py
βββ src
βββ spectator_measures
βββ __init__.py
βββ _coremodule.c
Sa buod:
- Tinutukoy ng
pyproject.tomlang karaniwang static na metadata tungkol sa Python package na ginagawa natin, kasama ang pangalan, may-akda, at mga dependency sa oras ng pagbuo at runtime. - Naglalaman ang
setup.pyng minimal na dynamic na configuration na kailangan natin para itayo ang ating extension module. - Tinutukoy ng
src/spectator_measures/__init__.pyang interface na nakaharap sa gumagamit at nagbibigay ng ilang code para makipag-ugnayan sa mga component ng Qiskit sa Python-space. - Tinutukoy ng
src/spectator_measures/_coremodule.cang C extension module, na maglalaman ng lahat ng performance-critical na code ng ating package.
Susuriin natin ang bawat file nang detalyado, binubuo ang package kasama ang extension module nito.
Tukuyin ang metadata ng packageβ
Magsimula sa pamamagitan ng pagtukoy sa file na pyproject.toml. Ito ay standard para sa isang setuptools-based
na proyekto, bagaman ang qiskit ay isang karagdagang kinakailangan sa array na build-system.requires,
bilang karagdagan sa 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"}
Simula sa Qiskit v2.4, ang C API ay hindi pa matatag sa labas ng mga minor na bersyon (halimbawa, ang C API para sa v2.4.0 ay
compatible sa v2.4.1 ngunit hindi sa v2.5.0). Sa hinaharap, nilalayon naming palawakin ang
katatagan na ito sa mga major na bersyon. Sa ngayon, itakda ang runtime na bersyon ng Qiskit sa
project.dependencies upang tumugma sa minor na bersyon na ginamit sa oras ng pagbuo.
Sa maraming purong Python na setuptools-based na proyekto, sapat na ang magkaroon ng
pyproject.toml file. Gayunpaman, ang aming module ay nangangailangan ng access sa mga Qiskit C API header file sa panahon ng
proseso ng pagbuo nito. Simula sa v2.4, kasama na ang mga ito sa mga Python distribution ng Qiskit SDK.
Para mahanap ang direktoryo na naglalaman ng mga ito, patakbuhin ang qiskit.capi.get_include().
Nagreresulta ito sa isang setup.py file na ganito ang hitsura:
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])
Karamihan ng impormasyon ng package ay tinukoy sa pyproject.toml, at ang setuptools.setup() ay
magbabasa rin ng file na iyon.
Tingnan ang setuptools User Guide para sa karagdagang
impormasyon sa pag-configure ng mga setuptools-based na proyekto.
Isulat ang Python-space wrapperβ
Teknikal na posibleng tukuyin ang lahat sa isang Python extension mula sa C. Sa pagsasagawa, mas madaling makipag-ugnayan sa ibang Python-space code mula sa Python mismo.
Tinutukoy ng package na ito ang isang custom transpiler pass na nagmumula sa Python-space
qiskit.transpiler.TransformationPass class, ngunit gumagamit ng function mula sa C extension module para sa
lahat ng business logic nito. Ganito ang hitsura nito:
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
Ang eksaktong mga detalye ng pass na ito ay hindi mahalaga para sa gabay na ito. Kung interesado ka, maaari mong
tingnan ang AddSpectatorMeasures API documentation sa
qiskit-addon-utils. Gumagawa ang gabay na ito ng isang simpleng port ng pass na iyon,
nang walang suporta para sa mga operasyon ng control-flow.
Isulat ang C extension moduleβ
Maaaring makapagtulong ang mga sumusunod na mapagkukunan:
Ang seksyong ito ay tungkol sa aktwal na C extension. Ito ang pinaka-kumplikadong file sa proyekto, kaya hahati-hatiin natin ito sa mga yugto.
I-configure ang mga header fileβ
Kapag nagtatayo ng Python extension module, kailangan mong isama ang Python.h bago ang anumang ibang file.
Para gamitin ang Qiskit C API sa isang extension module, kailangan mong tukuyin ang macro na
QISKIT_PYTHON_EXTENSION bago isama ang qiskit.h.
Ang aming mga include ay magiging ganito:
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>
Isulat ang purong C API codeβ
Susunod, isulat ang lahat ng business logic bilang purong Qiskit C API code. Ie-expose natin ang logic na ito sa Python space sa sumusunod na seksyon.
Ang seksyong ito ay naglalaman lamang ng purong Qiskit C API code. Gumagamit ito ng mga uri ng C API:
QkDag *, na tumutugma sa Python-spaceDAGCircuit.QkTarget *, na tumutugma sa Python-spaceTarget.QkNeighbors, isang native C API type na kumakatawan sa mga two-qubit coupling constraint.QkCircuitInstruction, isang native C API type para sa pag-query ng mga indibidwal na instruksyon.
Ang unang dalawa ay bumubuo ng aming pakikipag-ugnayan sa Python space, ngunit kapag nagtatrabaho sa mga ito, kailangan nating isaalang-alang lamang ang purong C API. Walang pakikipag-ugnayan sa Python interpreter sa code na ito.
Tandaan na ang lahat ng function at symbol na tinukoy sa seksyong ito ay idineklara gamit ang static linkage.
Ito ay dahil ang Python interpreter ay hindi mag-li-link laban sa extension module na ito; ibibigay natin
sa interpreter ang mga detalye ng mga available na function sa susunod na seksyon.
Hindi na tayo magtatagal sa mga detalye ng algorithm ng code na ito; kapaki-pakinabang na gumamit ng makabuluhang transpiler pass para sa demonstrasyon, ngunit ang tiyak na pagpapatupad ng algorithm ay hindi mahalaga sa gabay na ito.
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;
}
Isulat ang Python interaction codeβ
Ang lahat ng business logic ay natukoy na ngayon sa purong C. Susunod, kailangan itong ligtas na i-expose sa Python.
Para magsimula, tukuyin ang tanging function na ie-expose sa Python. Dapat itong
sumunod sa isang tinukoy na signature, na purong nasa mga uri ng Python na mukhang isang
fn(self, *args, **kwargs) na method. Kailangan nating magbalik ng PyObject *, na siyang generic na anyo ng
anumang Python object.
Ang kumpletong function ay ganito ang hitsura:
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;
}
Sa maikling salita, ang function ay:
- Sumusunod sa isang tinukoy na signature para tanggapin ang mga arbitrary na Python argument.
- Nagtatukoy ng espasyo para iimbak ang mga C-native object na na-parse mula sa mga Python argument.
- Tumatawag ng parsing function para kunin ang mga C-native object, na naka-configure gamit ang listahan ng mga inaasahang argument, keyword argument, at mga function na gagamitin para ma-convert ang mga ito. Kung nabigo ito, ang function ay nagpapalaganap ng error.
- Ini-delegate sa C-native na business logic ng nakaraang seksyon, na nagbabago ng DAG sa lugar.
- Nagbabalik ng Python-space
Noneobject.
Ang pinaka-kumplikadong logic ay nasa loob ng PyArg_ParseTupleAndKeywords. Ito ay mahusay na dokumentado sa the
CPython documentation on parsing arguments, na dapat mong
tingnan para sa karagdagang impormasyon.
Ang Qiskit C API ay nagbibigay ng ilang function na may mga pangalan tulad ng qk_*_convert_from_python, na dinisenyo
bilang mga "converter" function para gamitin sa PyArg_Parse* na mga function. Ang mga ito ay tumutugma sa
mga O& key sa format string; dito, gumamit tayo ng qk_dag_convert_from_python at
qk_target_convert_from_python. Ang mga function na ito ay nangongoha ng C-native object mula sa Python
argument na pinagmulan nito. Ibig sabihin, ang mga pagbabago ay magpapalaganap sa Python space, ngunit pati na rin na
dapat kang mag-ingat na huwag palayain ang iyong reference sa Python object na nagsasuporth sa kanila, habang ginagamit
ang resulta. Ito ay standard para sa Python C API programming.
Susunod, tinutukoy natin ang impormasyon tungkol sa module na ito at ang function na naglalaman nito, para maipasa natin ito sa Python space:
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,
};
Ang method table at module-definition structure na ito ay inilarawan nang mas detalyado sa the CPython documentation on module initialization.
Sa wakas, sabihin sa Python kung paano i-initialize ang module. Ito ang tanging function sa C
file na ine-export. Ang pangalan nito ay dapat na eksaktong tumugma sa pattern na
PyInit_<mod>, kung saan ang <mod> ay ang (unqualified) na pangalan ng module. Sa kasong ito, ang fully
qualified na pangalan ng module ay spectator_measures._core, at ang unqualified na pangalan ay _core, kaya ang aming
function ay dapat tawaging PyInit__core, na may double underscore.
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);
}
Ang PyMODINIT_FUNC at PyModuleDef_Init na mga symbol ay parehong standard Python C API programming. Ang
Qiskit-specific na component ay qk_import(). Kritikal na tawagin mo ang function na ito sa panahon ng initialization
function ng iyong module; hindi ka makakatawag ng anumang Qiskit C API function
hanggang sa matagumpay na mapatupad ito.
Gamitin ang package mula sa Pythonβ
Ito ay isang kumpletong package na ngayon, kasama ang isang C extension module. Dahil standard lamang ang mga tooling na ginamit, at walang non-standard na system library na naka-link sa oras ng pagbuo, ang proseso ng pagbuo ay simple.
Maaari kang gumamit ng anumang PEP-517-compatible na build tool. Bilang minimal na halimbawa, maaari mong patakbuhin ang sumusunod na command sa repository root para i-install ang package.
pip install .
Kino-compile nito ang C extension module at ini-install ang kumpletong Python package sa iyong environment.
Isang halimbawa ng paggamit ng custom transpiler pass na ito ay:
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()
Ang resulta nito ay:
βββββ β
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