Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Rozšíření Qiskitu v Pythonu pomocí C

Qiskit C API lze použít v rámci rozšiřujících modulů Pythonu. Výkonnostně kritické části svých Qiskit rozšíření můžeš napsat v C a urychlit je, přičemž je pak bezpečně distribuuješ svým uživatelům.

Tento průvodce tě provede procesem definice kompletního rozšiřujícího modulu, nastavením jeho sestavovacího procesu a zpřístupněním pro uživatele Pythonu. Balíček poskytuje jednoduchý port AddSpectatorMeasures z Qiskit addons do C. Jde o skutečný vlastní průchod se skutečným případem použití v Qiskit addons.

tip

Mohou se ti hodit následující externí zdroje:

Qiskit C API je pro rozšiřující moduly Pythonu zpřístupněno velmi podobným způsobem jako NumPy C API. Pokud jsi již dříve programoval/a rozšíření pro NumPy, bude ti proces s Qiskitem povědomý.

varování

Qiskit C API je stále experimentální. Proto zatím neexistuje plně stabilní programovací nebo binární rozhraní a mezi vedlejšími verzemi mohou nastat nekompatibilní změny.

Například rozšiřující modul používající Qiskit v2.4.0 při sestavení bude zaručeně fungovat s Qiskitem v2.4.1 za běhu, ale může přestat fungovat při použití Qiskitu v2.5.0 za běhu.

Požadavky​

Dokonči pokyny v tématu Install k instalaci následujícího:

  • Standardní sadu nástrojů kompilátoru C pro tvou platformu

  • Verzi Pythonu, která obsahuje své hlavičkové soubory C API.

Měl/a bys také být obeznámen/a, nebo připraven/a je vyhledávat, s funkcemi a objekty dostupnými v Qiskit C API, a měl/a bys mít nějakou znalost programování v C.

Začni v čistém adresáři.

Vytvoření adresářové struktury​

Použijeme adresářovou strukturu založenou na src a jednoduchý sestavovací systém postavený na setuptools. Tyto instrukce by mělo být snadné přizpůsobit jakémukoli sestavovacímu systému, který umí sestavovat rozšiřující moduly.

Výsledná struktura bude vypadat takto:

extension-module
├── pyproject.toml
├── setup.py
└── src
└── spectator_measures
├── __init__.py
└── _coremodule.c

Stručně řečeno:

  • pyproject.toml definuje standardní statická metadata o vytvářeném Python balíčku, včetně jeho názvu, autora a závislostí při sestavení a za běhu.

  • setup.py obsahuje minimální dynamickou konfiguraci potřebnou k sestavení rozšiřujícího modulu.

  • src/spectator_measures/__init__.py definuje uživatelské rozhraní a poskytuje kód pro spolupráci s komponenty Qiskitu v Python prostoru.

  • src/spectator_measures/_coremodule.c definuje rozšiřující modul v C, který bude obsahovat veškerý výkonnostně kritický kód balíčku.

Každý soubor si podrobně probereme a postupně sestavíme balíček s jeho rozšiřujícím modulem.

Definice metadat balíčku​

Začni definicí souboru pyproject.toml. Toto je standardní postup pro projekt postavený na setuptools, přičemž qiskit je navíc požadavek v poli build-system.requires vedle 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"}

V project.dependencies nastav verzi Qiskitu pro běh tak, aby odpovídala vedlejší verzi použité při sestavení.

V mnoha čistě Pythonovských projektech postavených na setuptools by soubor pyproject.toml stačil. Náš modul však potřebuje přístup k hlavičkovým souborům Qiskit C API během procesu sestavení. Počínaje verzí v2.4 jsou tyto soubory součástí Python distribucí Qiskit SDK. Pro nalezení adresáře, který je obsahuje, spusť qiskit.capi.get_include(). Výsledný soubor setup.py vypadá takto:

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])

Většina informací o balíčku je definována v pyproject.toml a setuptools.setup() tento soubor také načte.

tip

Více informací o konfiguraci projektů postavených na setuptools najdeš v Uživatelské příručce setuptools.

Napsání Python-prostorového obalu​

Technicky je možné definovat vše v rozšíření pro Python přímo v C. V praxi je však snazší pracovat s jiným kódem v Python prostoru přímo z Pythonu.

Tento balíček definuje vlastní průchod transpileru, který vychází z Python-prostorové třídy qiskit.transpiler.TransformationPass, ale pro veškerou svou obchodní logiku využívá funkci z rozšiřujícího modulu v C. Vypadá to takto:

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

Přesné detaily tohoto průchodu nejsou pro tento průvodce podstatné. Pokud tě to zajímá, můžeš nahlédnout do dokumentace API AddSpectatorMeasures v qiskit-addon-utils. Tento průvodce vytváří jednoduchý port daného průchodu bez podpory pro operace řízení toku.

Napsání rozšiřujícího modulu v C​

Tato část se věnuje samotnému rozšíření v C. Jde o nejsložitější soubor v projektu, proto ho rozdělíme do etap.

Konfigurace hlavičkových souborů​

Při sestavování rozšiřujícího modulu Pythonu musíš vložit Python.h před jakýkoli jiný soubor. Abys mohl/a v rozšiřujícím modulu použít Qiskit C API, musíš před vložením qiskit.h definovat makro QISKIT_PYTHON_EXTENSION.

Naše vkládání hlavičkových souborů pak vypadá takto:

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>

Napsání čistého kódu C API​

Dále napiš veškerou obchodní logiku jako čistý kód Qiskit C API. Tuto logiku zpřístupníme v Python prostoru v následující části.

Tato část obsahuje pouze čistý kód Qiskit C API. Používá typy C API:

  • QkDag *, odpovídající DAGCircuit v Python prostoru.

  • QkTarget *, odpovídající Target v Python prostoru.

  • QkNeighbors, nativní typ C API reprezentující omezení spojení dvou qubitů.

  • QkCircuitInstruction, nativní typ C API pro dotazování na jednotlivé instrukce.

První dva tvoří součást naší interakce s Python prostorem, ale při práci s nimi stačí uvažovat pouze čisté C API. V tomto kódu nedochází k žádné interakci s interpretem Pythonu.

Všimni si, že všechny funkce a symboly definované v této části jsou deklarovány s vazbou static. Důvodem je, že interpret Pythonu se na tento rozšiřující modul nebude linkovat; podrobnosti o dostupných funkcích mu předáme v další části.

Na algoritmické detaily tohoto kódu se nebudeme zaměřovat; je užitečné použít smysluplný průchod transpileru pro demonstraci, ale přesná implementace algoritmu pro tento průvodce není důležitá.

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;
}

Napsání kódu pro interakci s Pythonem​

Veškerá obchodní logika je nyní definována v čistém C. Dále ji potřebujeme bezpečně zpřístupnit Pythonu.

Začni definicí jediné funkce, která bude zpřístupněna Pythonu. Tato funkce musí mít danou signaturu, vyjádřenou čistě pomocí Python typů, které vypadají jako metoda fn(self, *args, **kwargs). Musíme vrátit PyObject *, což je obecná forma jakéhokoli Python objektu.

Kompletní funkce vypadá takto:

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;
}

Stručně řečeno, funkce:

  1. Splňuje definovaný podpis pro přijímání libovolných Python argumentů.

  2. Definuje prostor pro uložení nativních C objektů extrahovaných z Python argumentů.

  3. Volá funkci pro parsování, která extrahuje nativní C objekty, nakonfigurovanou se seznamem očekávaných argumentů, klíčových argumentů a funkcí pro jejich konverzi. Pokud to selže, funkce chybu dále propaguje.

  4. Deleguje na nativní C obchodní logiku z předchozí části, která mění DAG na místě.

  5. Vrací Python-prostorový objekt None.

Nejsložitější logika se nachází uvnitř PyArg_ParseTupleAndKeywords. Tato funkce je dobře zdokumentována v dokumentaci CPythonu o parsování argumentů, na kterou se doporučujeme obrátit pro další informace.

Qiskit C API poskytuje několik funkcí s názvy jako qk_*_convert_from_python, které jsou navrženy jako „konverzní" funkce pro použití s funkcemi PyArg_Parse*. Odpovídají klíčům O& ve formátovacím řetězci; zde jsme použili qk_dag_convert_from_python a qk_target_convert_from_python. Tyto funkce vypůjčují nativní C objekt z Python argumentu, ze kterého jsou odvozeny. To znamená, že mutace se propagují do Python prostoru, ale také to, že bys měl/a dbát na to, abys neuvolnil/a svůj odkaz na Python objekt, který je podkládá, dokud výsledek používáš. Toto je standardní praxe při programování Python C API.

Dále definujeme informace o tomto modulu a funkci, kterou obsahuje, abychom je mohli předat Python prostoru:

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,
};

Tabulka metod a struktura definice modulu jsou podrobněji popsány v dokumentaci CPythonu o inicializaci modulu.

Nakonec Pythonu sdělíme, jak modul inicializovat. Toto je jediná exportovaná funkce v souboru C. Její název musí přesně odpovídat vzoru PyInit_<mod>, kde <mod> je (nekvalifikovaný) název modulu. V tomto případě je plně kvalifikovaný název modulu spectator_measures._core a nekvalifikovaný název je _core, takže naše funkce se musí jmenovat PyInit__core s dvojitým podtržítkem.

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 a PyModuleDef_Init jsou standardní součásti Python C API programování. Složkou specifickou pro Qiskit je qk_import(). Je nezbytně nutné tuto funkci volat během inicializační funkce modulu; dokud nebude úspěšně vykonána, nebude možné volat žádné funkce Qiskit C API.

Použití balíčku z Pythonu​

Toto je nyní kompletní balíček včetně rozšiřujícího modulu v C. Protože byly použity pouze standardní nástroje a při sestavení se nelinkují žádné nestandardní systémové knihovny, je proces sestavení jednoduchý.

Můžeš použít libovolný sestavovací nástroj kompatibilní s PEP-517. Jako minimální příklad můžeš spustit následující příkaz v kořeni repozitáře k instalaci balíčku.

pip install .

Tím se zkompiluje rozšiřující modul v C a nainstaluje se kompletní Python balíček do tvého prostředí.

Příklad použití tohoto vlastního průchodu transpileru je:

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()

Výsledkem je:

┌───┐ ░
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