הרחבת Qiskit בפייתון עם C
ניתן להשתמש ב-Qiskit C API בתוך מודולי תוסף של פייתון. תוכל לכתוב בשפת C את החלקים הקריטיים לביצועים בתוספי Qiskit שלך כדי להאיצם, ולאחר מכן להפיץ אותם בבטחה למשתמשים שלך.
מדריך זה מוביל אותך בתהליך הגדרת מודול תוסף מלא, הגדרת תהליך הבנייה שלו,
וחשיפתו למשתמשי פייתון. החבילה מספקת פורט פשוט של
AddSpectatorMeasures מתוספי Qiskit ל-C. זהו pass מותאם אמיתי
עם מקרה שימוש אמיתי בתוספי Qiskit.
ייתכן שתמצא את המשאבים החיצוניים הבאים מועילים:
ה-Qiskit C API נחשף למודולי תוסף פייתון באופן דומה מאוד ל-NumPy C API. אם תכנתת בעבר תוסף NumPy, תמצא את תהליך Qiskit מוכר.
ה-Qiskit C API עדיין ניסיוני. לפיכך, עדיין אין ממשק תכנות או בינארי יציב לחלוטין, ועשויים להיות שינויים מנוגדים בין גרסאות משניות.
לדוגמה, מודול תוסף שמשתמש ב-Qiskit v2.4.0 בזמן הבנייה מובטח לעבוד עם Qiskit v2.4.1 בזמן הריצה, אך עלול להיכשל בשימוש עם Qiskit v2.5.0 בזמן הריצה.
דרישות
התחל מתיקייה נקייה.
עליך שיהיה זמין שרשרת כלי הידור C הסטנדרטי עבור הפלטפורמה שלך. כמו כן, עליך שיהיה גרסת פייתון הכוללת את כותרות C API שלה (זה סטנדרטי).
כדאי שתהיה מוכר, או מוכן לחפש, את הפונקציות והאובייקטים הנפרדים הזמינים ב-Qiskit C API. כדאי שתהיה לך היכרות עם תכנות ב-C.
יצירת מבנה התיקיות
נשתמש במבנה תיקיות מבוסס-src ומערכת בנייה פשוטה מבוססת-setuptools. הוראות אלו
אמורות להיות קלות להתאמה לכל מערכת בנייה שיכולה לבנות מודולי תוסף.
המבנה הסופי יראה כך:
extension-module
├── pyproject.toml
├── setup.py
└── src
└── spectator_measures
├── __init__.py
└── _coremodule.c
בסיכום:
pyproject.tomlמגדיר את המטא-נתונים הסטטיים הסטנדרטיים על חבילת הפייתון שאנו יוצרים, כולל שמה, מחברה, ותלויות בזמן הבנייה והריצה.setup.pyמכיל את ההגדרה הדינמית המינימלית שאנחנו צריכים לבניית מודול התוסף שלנו.src/spectator_measures/__init__.pyמגדיר את הממשק הגלוי למשתמש ומספק קוד לממשק עם רכיבי Python-space של Qiskit.src/spectator_measures/_coremodule.cמגדיר את מודול תוסף ה-C, שיכיל את כל הקוד הקריטי לביצועים של החבילה שלנו.
נבחן כל קובץ בפירוט, ונבנה את החבילה עם מודול התוסף שלה.
הגדרת מטא-נתוני החבילה
התחל בהגדרת קובץ pyproject.toml. זה סטנדרטי לפרויקט מבוסס-setuptools,
אם כי qiskit הוא דרישה נוספת במערך build-system.requires,
בנוסף ל-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"}
החל מ-Qiskit v2.4, ה-C API עדיין אינו יציב מחוץ לגרסאות משניות (לדוגמה, ה-C API עבור v2.4.0 יהיה
תואם ל-v2.4.1 אך לא ל-v2.5.0). בעתיד, אנו מתכוונים להרחיב
את היציבות הזו לגרסאות ראשיות. לעת עתה, הגדר את גרסת הריצה של Qiskit ב-
project.dependencies כך שתתאים לגרסה המשנית שבה השתמשת בזמן הבנייה.
בפרויקטים רבים של setuptools מבוססי-פייתון טהור, קובץ pyproject.toml יספיק.
אולם, המודול שלנו צריך גישה לקבצי כותרת Qiskit C API במהלך תהליך הבנייה שלו.
החל מ-v2.4, אלה כלולים בהפצות Python של Qiskit SDK.
כדי לאתר את התיקייה שמכילה אותם, הרץ qiskit.capi.get_include().
הדבר מביא לקובץ setup.py שנראה כך:
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])
רוב פרטי החבילה מוגדרים ב-pyproject.toml, ו-setuptools.setup() גם יקרא את הקובץ הזה.
ראה את מדריך המשתמש של setuptools למידע נוסף
על הגדרת פרויקטים מבוססי-setuptools.
כתיבת עטיפת Python-space
מבחינה טכנית, ניתן להגדיר הכל בתוסף פייתון מ-C. בפועל, קל יותר לממשק עם קוד Python-space אחר מפייתון עצמה.
חבילה זו מגדירה pass של Transpiler מותאם שנגזר מהמחלקה qiskit.transpiler.TransformationPass
של Python-space, אך משתמש בפונקציה ממודול תוסף ה-C לכל
לוגיקת העסקים שלו. זה נראה כך:
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
הפרטים המדויקים של pass זה אינם חשובים למדריך זה. אם אתה מעוניין, תוכל
לעיין בתיעוד ה-API של AddSpectatorMeasures ב-
qiskit-addon-utils. מדריך זה מייצר פורט פשוט של ה-pass הז ה,
ללא תמיכה בפעולות control-flow.
כתיבת מודול תוסף ה-C
ייתכן שתמצא את המשאבים הבאים מועילים:
חלק זה עוסק בתוסף ה-C בפועל. זהו הקובץ המורכב ביותר בפרויקט, לכן נפצל אותו לשלבים.
הגדרת קבצי הכותרת
בעת בניית מודול תוסף פייתון, עליך לכלול את Python.h לפני כל קובץ אחר.
כדי להשתמש ב-Qiskit C API במודול תוסף, עליך להגדיר את המאקרו
QISKIT_PYTHON_EXTENSION לפני הכללת qiskit.h.
ה-includes שלנו נראים אז כך:
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>
כתיבת קוד C API טהור
לאחר מכן, כתוב את כל לוגיקת העסקים כקוד Qiskit C API טהור. נחשוף לוגיקה זו ל-Python-space בחלק הבא.
חלק זה מכיל רק קוד Qiskit C API טהור. הוא משתמש בסוגי ה-C API:
QkDag *, המתאים ל-DAGCircuitשל Python-space.QkTarget *, המתאים ל-Targetשל Python-space.QkNeighbors, סוג C API מקורי המייצג אילוצי זיווג של שני Qubits.QkCircuitInstruction, סוג C API מקורי לשאילת הוראות בודדות.
השניים הראשונים מהווים חלק מהאינטראקציה שלנו עם Python-space, אך בעת עבודה איתם, אנחנו צריכים לשקול רק את ה-C API הטהור. אין אינטראקציה עם מפרש פייתון בקוד זה.
שים לב שכל הפונקציות והסמלים המוגדרים בחלק זה מוצהרים עם קישור static.
הסיבה לכך היא שמפרש פייתון לא יקשר מול מודול תוסף זה; נספק
למפרש פרטים על הפונקציות הזמינות בחלק הבא.
לא נתעמק בפרטים האלגוריתמיים של קוד זה; מועיל להשתמש ב-pass של Transpiler משמעותי להדגמה, אך היישום המדויק של האלגוריתם אינו חשוב למדריך זה.
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;
}
כתיבת קוד אינטראקציה עם פייתון
כל לוגיקת העסקים מוגדרת כעת ב-C טהור. לאחר מכן, יש לחשוף אותה בבטחה לפייתון.
כדי להתחיל, הגדר את הפונקציה היחידה שתיחשף לפייתון. פונקציה זו חייבת
לפעול לפי חתימה מוגדרת, שהיא אך ורק במונחים של סוגי פייתון שנראים כמו
מתודה fn(self, *args, **kwargs). עלינו להחזיר PyObject *, שהוא הצורה הגנרית של
כל אובייקט פייתון.
הפונקציה המלאה נראית כך:
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;
}
בקצרה, הפונקציה:
- פועלת לפי חתימה מוגדרת לקבלת ארגומנטים שרירותיים של פייתון.
- מגדי רה מקום לאחסון אובייקטים C-native שנחלצו מארגומנטי פייתון.
- קוראת לפונקציית ניתוח לחילוץ האובייקטים ה-C-native, מוגדרת עם רשימת הארגומנטים, ארגומנטי מילת המפתח, והפונקציות לשימוש להמרתם. אם הדבר נכשל, הפונקציה מעבירה את השגיאה.
- מאצילה ללוגיקה ה-C-native של החלק הקודם, שמשנה את ה-DAG במקומו.
- מחזירה את אובייקט ה-
Noneשל Python-space.
הלוגיקה המורכבת ביותר נמצאת בתוך PyArg_ParseTupleAndKeywords. דבר זה מתועד היטב ב
תיעוד CPython על ניתוח ארגומנטים, שכדאי
שתעיין בו לקבלת מידע נוסף.
ה-Qiskit C API מספק מספר פונקציות עם שמות כגון qk_*_convert_from_python, שמתוכננות
כפונקציות "ממיר" לשימוש עם פונקציות PyArg_Parse*. אלה מתאימות למפתחות O&
במחרוזת הפורמט; כאן, השתמשנו ב-qk_dag_convert_from_python ו-
qk_target_convert_from_python. פונקציות אלה שואלות את האובייקט ה-C-native מארגומנט
הפייתון שממנו הן נגזרות. המשמעות היא שמוטציות יתפשטו ל-Python-space, אך גם שעליך
להיזהר שלא לשחרר את ההפניה שלך לאובייקט הפייתון שתומך בהם, בזמן השימוש
בתוצאה. זה סטנדרטי לתכנות Python C API.
לאחר מכן, נגדיר מידע על המודול הזה והפונקציה שהוא מכיל, כדי שנוכל להעבירה ל- 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,
};
טבלת מתודות זו ומבנה הגדרת המודול מתוארים בפירוט רב יותר בתיעוד CPython על אתחול מודול.
לבסוף, ספר לפייתון כיצד לאתחל את המודול. זוהי הפונקציה היחידה בקובץ ה-C
שמיוצאת. שמה חייב להתאים בדיוק לתבנית
PyInit_<mod>, כאשר <mod> הוא שם המודול (הלא מוסמך). במקרה זה, שם המודול המוסמך
המלא הוא spectator_measures._core, והשם הלא מוסמך הוא _core, ולכן הפונקציה שלנו
חייבת להיקרא PyInit__core, עם קו תחתון כפול.
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 ו-PyModuleDef_Init הם שניהם תכנות Python C API סטנדרטי.
הרכיב הספציפי ל-Qiskit הוא qk_import(). חיוני שתקרא לפונקציה זו במהלך
פונקציית האתחול של המודול שלך; לא תוכל לקרוא לאף פונקציה ב-Qiskit C API
עד שזה יבוצע בהצלחה.
שימוש בחבילה מפייתון
זוהי כעת חבילה מלאה, כולל מודול תוסף C. מכיוון שנעשה שימוש רק ב- כלים סטנדרטיים, ואין ספריות מערכת לא סטנדרטיות המקושרות בזמן הבנייה, תהליך הבנייה פשוט.
ניתן להשתמש בכל כלי בנייה תואם-PEP-517. כדוגמה מינימלית, ניתן להריץ את הפקודה הבאה בשורש המאגר כדי להתקין את החבילה.
pip install .
פקודה זו מקמפלת את מודול תוסף ה-C ומתקינה את חבילת פייתון המלאה בסביבה שלך.
דוגמה לשימוש ב-pass מותאם זה של Transpiler היא:
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()
התוצאה של זה היא:
┌───┐ ░
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