انتقل إلى المحتوى الرئيسي

اكتب تمريرة Transpiler مخصصة

إصدارات الحزم

تم تطوير الكود في هذه الصفحة باستخدام المتطلبات التالية. نوصي باستخدام هذه الإصدارات أو ما هو أحدث منها.

qiskit[all]~=2.3.0

يتيح لك Qiskit SDK إنشاء تمريرات Transpilation مخصصة وتشغيلها داخل كائن PassManager أو إضافتها إلى StagedPassManager. سنوضح هنا كيفية كتابة تمريرة Transpiler، مع التركيز على بناء تمريرة تُجري Pauli twirling على البوابات الكمية المشوشة في الدائرة الكمية. يعتمد هذا المثال على DAG، وهو الكائن الذي تتعامل معه تمريرات من نوع TransformationPass.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit

خلفية: تمثيل DAG

قبل بناء تمريرة، من المهم التعرّف على التمثيل الداخلي للدوائر الكمية في Qiskit، وهو الرسم البياني غير الدوري الموجّه (DAG) (اطّلع على هذا الدرس التعليمي للاطلاع على نظرة عامة). لمتابعة هذه الخطوات، ثبّت مكتبة graphviz لوظائف رسم DAG.

في Qiskit، وضمن مراحل الـ Transpilation، تُمثَّل الدوائر باستخدام DAG. بشكل عام، يتكوّن الـ DAG من رؤوس (تُعرف أيضاً بـ "العقد") وحواف موجّهة تربط أزواجاً من الرؤوس باتجاه معين. يُخزَّن هذا التمثيل باستخدام كائنات qiskit.dagcircuit.DAGCircuit المؤلفة من كائنات DagNode فردية. تتمثل ميزة هذا التمثيل على قائمة البوابات الخالصة (أي netlist) في أن تدفق المعلومات بين العمليات يكون صريحاً، مما يُسهّل اتخاذ قرارات التحويل.

يوضح هذا المثال الـ DAG عبر إنشاء دائرة بسيطة تُهيئ حالة Bell وتُطبّق دوراناً RZR_Z، تبعاً لنتيجة القياس.

  from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np

qr = QuantumRegister(3, 'qr')
cr = ClassicalRegister(3, 'cr')
qc = QuantumCircuit(qr, cr)

qc.h(qr[0])
qc.cx(qr[0], qr[1])
qc.measure(qr[0], cr[0])
qc.rz(np.pi/2, qr[1]).c_if(cr, 2)
qc.draw(output='mpl')

دائرة تُهيئ حالة Bell وتُطبّق دوراناً R_Z تبعاً لنتيجة القياس.

استخدم الدالة qiskit.tools.visualization.dag_drawer() لعرض الـ DAG الخاص بهذه الدائرة. هناك ثلاثة أنواع من عقد الرسم البياني: عقد الـ Qubit/clbit (باللون الأخضر)، وعقد العمليات (باللون الأزرق)، وعقد الإخراج (باللون الأحمر). يُشير كل حافة إلى تدفق البيانات (أو التبعية) بين عقدتين.

from qiskit.converters import circuit_to_dag
from qiskit.tools.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

يتكون DAG الخاص بالدائرة من عقد مترابطة بحواف ذات اتجاهات. إنه طريقة مرئية لتمثيل الـ Qubits أو البتات الكلاسيكية والعمليات وطريقة تدفق البيانات.

تمريرات Transpiler

تُصنَّف تمريرات Transpiler إما كـ AnalysisPass أو TransformationPass. تعمل التمريرات بشكل عام مع DAG وproperty_set، وهو كائن يشبه القاموس لتخزين الخصائص التي تحددها تمريرات التحليل. تعمل تمريرات التحليل مع كل من الـ DAG وproperty_set الخاص به. لا يمكنها تعديل الـ DAG، لكنها تستطيع تعديل property_set. يختلف ذلك عن تمريرات التحويل التي تُعدّل الـ DAG فعلاً، ويمكنها قراءة property_set (لكن لا يمكنها الكتابة إليه). على سبيل المثال، تُترجم تمريرات التحويل الدائرة إلى ISA الخاص بها أو تُجري تمريرات توجيه لإدراج بوابات SWAP حيثما تطلّب الأمر.

إنشاء تمريرة Transpiler من نوع PauliTwirl

يبني المثال التالي تمريرة Transpiler تُضيف Pauli twirls. يُعدّ Pauli twirling استراتيجية لقمع الأخطاء تُعشوش الطريقة التي تواجه بها الـ Qubits القنوات المشوشة، والتي نفترض أنها بوابات ثنائية الـ Qubit في هذا المثال (لأنها أكثر عرضة للخطأ بكثير من البوابات أحادية الـ Qubit). لا تؤثر Pauli twirls على عمل البوابات ثنائية الـ Qubit. تُختار بحيث تُعاكس تلك المُطبَّقة قبل البوابة ثنائية الـ Qubit (على اليسار) تلك المُطبَّقة بعد البوابة ثنائية الـ Qubit (على اليمين). بهذا المعنى، العمليات ثنائية الـ Qubit متطابقة، لكن طريقة تنفيذها مختلفة. من فوائد Pauli twirling أنه يحوّل الأخطاء المترابطة إلى أخطاء عشوائية، مما يتيح تحسينها عبر الحساب المتوسط.

تعمل تمريرات Transpiler على DAG، لذا فإن الطريقة المهمة التي يجب تجاوزها هي .run()، التي تأخذ الـ DAG كمدخل. يُحافظ تهيئة أزواج Paulis كما هو موضح على عمل كل بوابة ثنائية الـ Qubit. يتم ذلك بالطريقة المساعدة build_twirl_set، التي تمر عبر كل Pauli ثنائي الـ Qubit (كما يتم الحصول عليه من pauli_basis(2)) وتجد الـ Pauli الآخر الذي يُحافظ على العملية.

من الـ DAG، استخدم طريقة op_nodes() لإرجاع جميع عقده. يمكن أيضاً استخدام الـ DAG لتجميع التشغيلات، وهي تسلسلات من العقد التي تعمل بدون انقطاع على Qubit. يمكن جمعها كتشغيلات أحادية الـ Qubit بـ collect_1q_runs، وتشغيلات ثنائية الـ Qubit بـ collect_2q_runs، وتشغيلات العقد التي تكون فيها أسماء التعليمات في قائمة أسماء بـ collect_runs. يحتوي DAGCircuit على طرق عديدة للبحث والانتقال عبر الرسم البياني. إحدى الطرق الشائعة الاستخدام هي topological_op_nodes، التي توفر العقد بترتيب يعتمد على التبعية. تُستخدم طرق أخرى مثل bfs_successors أساساً لتحديد كيفية تفاعل العقد مع العمليات اللاحقة في DAG.

في المثال، نريد استبدال كل عقدة تمثل تعليمة بدائرة فرعية مبنية كـ DAG مصغّر. يحتوي الـ DAG المصغّر على سجل كمي ثنائي الـ Qubit مُضاف إليه. تُضاف العمليات إلى الـ DAG المصغّر باستخدام apply_operation_back، التي تضع Instruction على مخرجات الـ DAG المصغّر (في حين تضعه apply_operation_front على مدخلات الـ DAG المصغّر). ثم تُستبدل العقدة بالـ DAG المصغّر باستخدام substitute_node_with_dag، وتستمر العملية على كل نموذج من CXGate وECRGate في الـ DAG (وهي تمثل البوابات الأساسية ثنائية الـ Qubit على أجهزة IBM®).

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis

import numpy as np

from typing import Iterable, Optional
class PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""

def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()

def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}

# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []

# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))

self.twirl_set[twirl_gate.name] = twirl_list

def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue

# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]

# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)

# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)

# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)

return dag

استخدام تمريرة Transpiler من نوع PauliTwirl

يستخدم الكود التالي التمريرة التي أنشأناها أعلاه لعملية Transpilation على دائرة. خذ دائرة بسيطة تحتوي على بوابات cx وecr.

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")

Output of the previous code cell

لتطبيق التمريرة المخصصة، ابنِ مدير تمريرات باستخدام تمريرة PauliTwirl وشغّله على 50 دائرة.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

أصبحت كل بوابة ثنائية الـ Qubit الآن محاطة بـ Paulis من الجانبين.

twirled_qcs[-1].draw("mpl")

Output of the previous code cell

تكون المؤثرات متطابقة إذا استُخدم Operator من qiskit.quantum_info:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_

الخطوات التالية

توصيات