Extinde Qiskit în Python cu C
API-ul C Qiskit poate fi utilizat în modulele de extensie Python. Poți scrie secțiunile critice din punct de vedere al performanței ale extensiilor tale Qiskit în C pentru a le accelera și apoi le poți distribui în siguranță utilizatorilor tăi.
Acest ghid te ghidează prin procesul de definire a unui modul de extensie complet, configurarea
procesului de build și expunerea acestuia utilizatorilor Python. Pachetul oferă un port simplu al
AddSpectatorMeasures din addon-urile Qiskit în C. Acesta este un pas personalizat real
cu un caz de utilizare real în addon-urile Qiskit.
S-ar putea să găsești utile următoarele resurse externe:
- Documentația CPython despre scrierea modulelor de extensie.
- Documentația NumPy despre utilizarea API-ului C al acestuia.
API-ul C Qiskit este expus pentru modulele de extensie Python într-un mod foarte similar cu API-ul C NumPy. Dacă ai programat anterior o extensie NumPy, vei găsi procesul Qiskit familiar.
API-ul C Qiskit este încă experimental. Astfel, nu există încă o interfață de programare sau binară complet stabilă și pot exista modificări de rupere între versiuni minore.
De exemplu, un modul de extensie care utilizează Qiskit v2.4.0 la momentul build-ului are garanția că funcționează cu Qiskit v2.4.1 la execuție, dar s-ar putea să nu funcționeze cu Qiskit v2.5.0 la execuție.
Cerințe
Pornește dintr-un director curat.
Trebuie să ai disponibil toolchain-ul standard al compilatorului C pentru platforma ta. De asemenea, trebuie să ai o versiune de Python care include headerele API-ului C (aceasta este standard).
Ar trebui să fii familiarizat cu, sau pregătit să consulți, funcțiile și obiectele individuale disponibile în API-ul C Qiskit. Ar trebui să ai o oarecare familiaritate cu programarea în C.
Creează structura de directoare
Vom folosi o structură de directoare bazată pe src și un sistem de build simplu bazat pe setuptools. Aceste
instrucțiuni ar trebui să fie ușor de adaptat la orice sistem de build care poate construi
module de extensie.
Structura finală va arăta astfel:
extension-module
├── pyproject.toml
├── setup.py
└── src
└── spectator_measures
├── __init__.py
└── _coremodule.c
Pe scurt:
pyproject.tomldefinește metadatele statice standard despre pachetul Python pe care îl creăm, inclusiv numele, autorul și dependențele de build și de execuție.setup.pyconține configurația dinamică minimă de care avem nevoie pentru a construi modulul nostru de extensie.src/spectator_measures/__init__.pydefinește interfața orientată spre utilizator și oferă cod pentru a face interfața cu componentele Python ale Qiskit.src/spectator_measures/_coremodule.cdefinește modulul de extensie C, care va conține tot codul critic din punct de vedere al performanței al pachetului nostru.
Vom examina fiecare fișier în detaliu, construind pachetul împreună cu modulul său de extensie.
Definește metadatele pachetului
Începe prin definirea fișierului pyproject.toml. Acesta este standard pentru un proiect bazat pe setuptools,
deși qiskit este o cerință suplimentară în array-ul build-system.requires,
pe lângă 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"}
Începând cu Qiskit v2.4, API-ul C nu este încă stabil în afara versiunilor minore (de exemplu, API-ul C pentru v2.4.0 va fi
compatibil cu v2.4.1, dar nu cu v2.5.0). În viitor, intenționăm să extindem
această stabilitate la versiunile majore. Deocamdată, setează versiunea de execuție a Qiskit în
project.dependencies să corespundă versiunii minore folosite la momentul build-ului.
În multe proiecte bazate pe setuptools pure Python, ar fi suficient să ai fișierul
pyproject.toml. Cu toate acestea, modulul nostru are nevoie de acces la fișierele header ale API-ului C Qiskit în timpul
procesului de build. Începând cu v2.4, acestea sunt incluse în distribuțiile Python ale Qiskit SDK.
Pentru a localiza directorul care le conține, rulează qiskit.capi.get_include().
Aceasta duce la un fișier setup.py care arată astfel:
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])
Cea mai mare parte a informațiilor despre pachet este definită în pyproject.toml, iar setuptools.setup() va
citi și acel fișier.
Consultă Ghidul utilizatorului setuptools pentru mai multe
informații despre configurarea proiectelor bazate pe setuptools.
Scrie wrapper-ul Python
Este tehnic posibil să definești totul într-o extensie Python din C. În practică, este mai ușor să interacționezi cu alt cod Python din Python însuși.
Acest pachet definește un pas de transpilare personalizat care derivă din clasa Python
qiskit.transpiler.TransformationPass, dar utilizează o funcție din modulul de extensie C pentru
toată logica sa de business. Aceasta arată astfel:
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
Detaliile exacte ale acestui pas nu sunt importante pentru acest ghid. Dacă ești interesat, poți
consulta documentația API a AddSpectatorMeasures din
qiskit-addon-utils. Acest ghid produce un port simplu al acelui pas,
fără suport pentru operațiuni de control de flux.
Scrie modulul de extensie C
S-ar putea să găsești utile următoarele resurse:
Această secțiune se referă la extensia C propriu-zisă. Acesta este cel mai complex fișier din proiect, deci îl vom împărți în etape.
Configurează fișierele header
Când construiești un modul de extensie Python, trebuie să incluzi Python.h înaintea oricărui alt fișier.
Pentru a utiliza API-ul C Qiskit într-un modul de extensie, trebuie să definești macro-ul
QISKIT_PYTHON_EXTENSION înainte de a include qiskit.h.
Include-urile noastre arată astfel:
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>
Scrie codul pur al API-ului C
În continuare, scrie toată logica de business ca cod pur al API-ului C Qiskit. Vom expune această logică în spațiul Python în secțiunea următoare.
Această secțiune conține numai cod pur al API-ului C Qiskit. Folosește tipurile API-ului C:
QkDag *, corespunzândDAGCircuitdin spațiul Python.QkTarget *, corespunzândTargetdin spațiul Python.QkNeighbors, un tip nativ al API-ului C care reprezintă constrângerile de cuplare pe doi Qubiți.QkCircuitInstruction, un tip nativ al API-ului C pentru interogarea instrucțiunilor individuale.
Primele două fac parte din interacțiunea noastră cu spațiul Python, dar când lucrăm cu ele, trebuie să luăm în considerare doar API-ul C pur. Nu există nicio interacțiune cu interpretorul Python în acest cod.
Reține că toate funcțiile și simbolurile definite în această secțiune sunt declarate cu legătură static.
Aceasta deoarece interpretorul Python nu se va lega împotriva acestui modul de extensie; vom furniza
interpretorului detaliile funcțiilor disponibile în secțiunea următoare.
Nu vom insista asupra detaliilor algoritmice ale acestui cod; este instructiv să folosim un pas de transpilare semnificativ pentru demonstrație, dar implementarea precisă a algoritmului nu este importantă pentru acest ghid.
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;
}
Scrie codul de interacțiune cu Python
Toată logica de business este acum definită în C pur. În continuare, trebuie să fie expusă în siguranță Python-ului.
Pentru a începe, definește singura funcție care va fi expusă Python-ului. Aceasta trebuie
să urmeze o semnătură definită, care este exprimată în termeni de tipuri Python care arată ca o
metodă fn(self, *args, **kwargs). Trebuie să returnăm un PyObject *, care este forma generică a
oricărui obiect Python.
Funcția completă arată astfel:
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;
}
Pe scurt, funcția:
- Urmează o semnătură definită pentru a accepta argumente Python arbitrare.
- Definește spațiu pentru a stoca obiectele native C extrase din argumentele Python.
- Apelează o funcție de parsare pentru a extrage obiectele native C, configurată cu lista de argumente așteptate, argumente cuvânt cheie și funcțiile de conversie. Dacă aceasta eșuează, funcția propagă eroarea.
- Deleagă logica de business nativă C din secțiunea anterioară, care modifică DAG-ul în loc.
- Returnează obiectul Python-space
None.
Cea mai complexă logică se află în PyArg_ParseTupleAndKeywords. Aceasta este bine documentată în documentația
CPython despre parsarea argumentelor, pe care ar trebui
să o consulți pentru mai multe informații.
API-ul C Qiskit furnizează mai multe funcții cu nume precum qk_*_convert_from_python, care sunt
concepute ca funcții „converter" pentru utilizare cu funcțiile PyArg_Parse*. Acestea corespund
cheilor O& din șirul de format; aici am folosit qk_dag_convert_from_python și
qk_target_convert_from_python. Aceste funcții împrumută obiectul nativ C din argumentul Python
din care sunt derivate. Aceasta înseamnă că mutațiile se vor propaga în spațiul Python, dar și că
ar trebui să ai grijă să nu eliberezi referința ta la obiectul Python care le susține, în timp ce
utilizezi rezultatul. Aceasta este standard pentru programarea API-ului C Python.
În continuare, definim informațiile despre acest modul și funcția pe care o conține, astfel încât să o putem transmite spațiului 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,
};
Acest tabel de metode și structura de definiție a modulului sunt descrise mai detaliat în documentația CPython despre inițializarea modulului.
În final, spune Python-ului cum să inițializeze modulul. Aceasta este singura funcție din fișierul C
care este exportată. Numele ei trebuie să corespundă exact modelului
PyInit_<mod>, unde <mod> este numele (necalificat) al modulului. În acest caz, numele complet
calificat al modulului este spectator_measures._core, iar numele necalificat este _core, deci funcția noastră
trebuie să se numească PyInit__core, cu underscore dublu.
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);
}
PyMODINIT_FUNC și simbolurile PyModuleDef_Init sunt ambele programare standard a API-ului C Python. Componenta
specifică Qiskit este qk_import(). Este esențial să apelezi această funcție în timpul funcției de
inițializare a modulului tău; nu vei putea apela nicio funcție API C Qiskit
până când aceasta nu a fost executată cu succes.
Folosește pachetul din Python
Acesta este acum un pachet complet, inclusiv un modul de extensie C. Deoarece s-au folosit doar instrumente standard și nu se leagă nicio bibliotecă de sistem non-standard în timpul build-ului, procesul de build este simplu.
Poți folosi orice instrument de build compatibil cu PEP-517. Ca exemplu minimal, poți rula următoarea comandă în rădăcina depozitului pentru a instala pachetul.
pip install .
Aceasta compilează modulul de extensie C și instalează pachetul Python complet în mediul tău.
Un exemplu de utilizare a acestui pas de transpilare personalizat este:
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()
Rezultatul acestui cod este:
┌───┐ ░
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