Lumaktaw sa pangunahing nilalaman

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.

tip

Maaaring makapagtulong ang mga sumusunod na panlabas na mapagkukunan:

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.

babala

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.toml ang 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.py ng minimal na dynamic na configuration na kailangan natin para itayo ang ating extension module.
  • Tinutukoy ng src/spectator_measures/__init__.py ang 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.c ang 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.

tip

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​

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-space DAGCircuit.
  • QkTarget *, na tumutugma sa Python-space Target.
  • 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:

  1. Sumusunod sa isang tinukoy na signature para tanggapin ang mga arbitrary na Python argument.
  2. Nagtatukoy ng espasyo para iimbak ang mga C-native object na na-parse mula sa mga Python argument.
  3. 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.
  4. Ini-delegate sa C-native na business logic ng nakaraang seksyon, na nagbabago ng DAG sa lugar.
  5. Nagbabalik ng Python-space None object.

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