التغذية الراجعة الكلاسيكية وتدفق التحكم (الدوائر الديناميكية)
إصدارات الحزم
تم تطوير الكود في هذه الصفحة باستخدام المتطلبات التالية. ننصح باستخدام هذه الإصدارات أو ما هو أحدث منها.
qiskit[all]~=2.4.0
الدوائر الديناميكية أدوات قوية تتيح لك قياس الـ qubits في منتصف تنفيذ دائرة كمومية ثم إجراء عمليات منطقية كلاسيكية داخل الدائرة بناءً على نتائج قياسات منتصف الدائرة. تُعرف هذه العملية أيضاً بـ التغذية الراجعة الكلاسيكية. وعلى الرغم من أننا في بداية فهم كيفية الاستفادة القصوى من الدوائر الديناميكية، فقد رصد مجتمع البحث الكمي عدداً من حالات الاستخدام، منها:
- التحضير الكفء لحالات الكم، كـ حالة GHZ، وحالة W (لمزيد من المعلومات حول حالة W، راجع أيضاً "تحضير الحالة بدوائر ضحلة باستخدام التغذية الراجعة")، وفئة واسعة من حالات حاصل الضرب المصفوفي
- التشابك الكفء بعيد المدى بين الـ qubits على الشريحة ذاتها باستخدام دوائر ضحلة
- أخذ عينات كفء من دوائر شبيهة بـ IQP
يدعم Qiskit أربعة تراكيب لتدفق التحكم للتغذية الراجعة الكلاسيكية، كل منها مُطبَّق كميثود على QuantumCircuit. هذه التراكيب وميثوداتها المقابلة هي:
- جملة if -
QuantumCircuit.if_test - جملة switch -
QuantumCircuit.switch - حلقة for -
QuantumCircuit.for_loop - حلقة while -
QuantumCircuit.while_loop
تُعيد كل من هذه الميثودات مدير سياق وتُستخدم عادةً في جملة with. يشرح باقي هذا الدليل كل من هذه التراكيب وكيفية استخدامها.
ثمة بعض القيود على عمليات التغذية الراجعة الكلاسيكية وتدفق التحكم على الأجهزة الكمومية قد تؤثر على برنامجك. لمزيد من المعلومات، راجع تنفيذ الدوائر الديناميكية.
جملة if
تُستخدم جملة if لتنفيذ عمليات بشكل شرطي بناءً على قيمة بت كلاسيكي أو سجل كلاسيكي.
في المثال التالي، نطبّق بوابة هادامار على qubit ونقيسه. إذا كانت النتيجة 1، نطبّق بوابة X على الـ qubit مما يعيده إلى الحالة 0. ثم نقيس الـ qubit مرة أخرى. يجب أن تكون نتيجة القياس 0 باحتمالية 100%.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'0': 1024}
يمكن إعطاء جملة with هدف إسناد يكون بحد ذاته مدير سياق يمكن تخزينه واستخدامه لاحقاً لإنشاء كتلة else، التي تُنفَّذ في كل مرة لا تُنفَّذ فيها محتويات كتلة if.
في المثال التالي، نُهيئ سجلات تحتوي على Qubitين وبتّين كلاسيكيين. نطبّق بوابة هادامار على الـ qubit الأول ونقيسه. إذا كانت النتيجة 1، نطبّق بوابة هادامار على الـ qubit الثاني؛ وإلا نطبّق بوابة X عليه. وأخيراً، نقيس الـ qubit الثاني أيضاً.
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)
circuit.draw("mpl")
# example output counts: {'01': 260, '11': 272, '10': 492}
بالإضافة إلى الاشتراط على بت كلاسيكي واحد، يمكن أيضاً الاشتراط على قيمة سجل كلاسيكي مؤلف من عدة بتات.
في المثال التالي، نطبّق بوابات هادامار على Qubitين ونقيسهما. إذا كانت النتيجة 01، أي أن الـ qubit الأول هو 1 والـ qubit الثاني هو 0، نطبّق بوابة X على qubit ثالث. وأخيراً، نقيس الـ qubit الثالث. لاحظ أننا اخترنا لوضوح الكود تحديد حالة البت الكلاسيكي الثالث وهي 0 في شرط if. في رسم الدائرة، يُشار إلى الشرط بالدوائر على البتات الكلاسيكية المشروط عليها. تُشير الدائرة المصمتة إلى الاشتراط على 1، بينما تُشير الدائرة المفرغة إلى الاشتراط على 0.
qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.if_test((clbits, 0b001)):
circuit.x(q2)
circuit.measure(q2, c2)
circuit.draw("mpl")
# example output counts: {'101': 269, '011': 260, '000': 252, '010': 243}
جملة Switch
تُستخدم جملة switch لاختيار الإجراءات بناءً على قيمة بت كلاسيكي أو سجل كلاسيكي. وهي مشابهة لجملة if، غير أنه يمكنك تحديد حالات أكثر لمنطق التفرع. يطبّق المثال التالي بوابة هادامار على qubit ويقيسه. إذا كانت النتيجة 0، يُطبَّق بوابة X على الـ qubit، وإذا كانت النتيجة 1، يُطبَّق بوابة Z. يجب أن تكون نتيجة القياس 1 باحتمالية 100%.
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.switch(c0) as case:
with case(0):
circuit.x(q0)
with case(1):
circuit.z(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'1': 1024}
نظراً لاستخدام المثال أعلاه بتاً كلاسيكياً واحداً فحسب، لم يكن ثمة إلا حالتان محتملتان، وكان بالإمكان تحقيق النتيجة ذاتها باستخدام جملة if-else. تكون حالة switch مفيدة بشكل رئيسي عند التفرع بناءً على قيمة سجل كلاسيكي مؤلف من عدة بتات. يوضح المثال التالي كيفية إنشاء حالة افتراضية (default)، التي تُنفَّذ إذا لم تتطابق أي من الحالات السابقة. لاحظ أنه في جملة switch، لا يُنفَّذ سوى كتلة واحدة فحسب. لا يوجد تسلسل تنفيذي (fallthrough).
يطبّق المثال التالي بوابات هادامار على Qubitين ويقيسهما. إذا كانت النتيجة 00 أو 11، يُطبَّق بوابة Z على الـ qubit الثالث. إذا كانت النتيجة 01، يُطبَّق بوابة Y. وإذا لم تتطابق أي من الحالات السابقة، يُطبَّق بوابة X. وأخيراً، يُقاس الـ qubit الثالث.
qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.switch(clbits) as case:
with case(0b000, 0b011):
circuit.z(q2)
with case(0b001):
circuit.y(q2)
with case(case.DEFAULT):
circuit.x(q2)
circuit.measure(q2, c2)
circuit.draw("mpl")
# example output counts: {'101': 267, '110': 249, '011': 265, '000': 243}
حلقة For
تُستخدم حلقة for للتكرار عبر تسلسل من القيم الكلاسيكية وتنفيذ بعض العمليات في كل تكرار.
يستخدم المثال التالي حلقة for لتطبيق 5 بوابات X على qubit ثم يقيسه. ونظراً لتنفيذ عدد فردي من بوابات X، يكون التأثير الإجمالي هو قلب الـ qubit من الحالة 0 إلى الحالة 1.
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
with circuit.for_loop(range(5)) as _:
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'1': 1024}
حلقة While
تُستخدم حلقة while لتكرار التعليمات طالما تحقق شرط معين.
يطبّق المثال التالي بوابات هادامار على Qubitين ويقيسهما. ثم ينشئ حلقة while تكرر هذا الإجراء ما دامت نتيجة القياس 11. ونتيجةً لذلك، يجب ألا تكون نتيجة القياس النهائية 11 أبداً، مع ظهور الاحتمالات المتبقية بتكرار متقارب.
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
q0, q1 = qubits
c0, c1 = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.while_loop((clbits, 0b11)):
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
circuit.draw("mpl")
# example output counts: {'01': 334, '10': 368, '00': 322}
التعبيرات الكلاسيكية
تحتوي وحدة التعبيرات الكلاسيكية في Qiskit qiskit.circuit.classical على تمثيل استكشافي للعمليات التي تُنفَّذ في وقت التشغيل على القيم الكلاسيكية أثناء تنفيذ الدائرة.
يُظهر المثال التالي كيف يمكنك استخدام حساب التكافؤ لإنشاء حالة GHZ بـ n qubit باستخدام الدوائر الديناميكية. أولاً، أنشئ زوج من أزواج Bell على الـ qubits المتجاورة. ثم ألصق هذه الأزواج ببعضها باستخدام طبقة من بوابات CNOT بين الأزواج. بعد ذلك تقيس الـ qubit الهدف لجميع بوابات CNOT السابقة وتُعيد تعيين كل qubit مقيس إلى الحالة . تطبّق على كل موقع غير مقيس حيث يكون تكافؤ جميع البتات السابقة فردياً. وأخيراً، تُطبَّق بوابات CNOT على الـ qubits المقيسة لاستعادة التشابك الضائع بسبب القياس.
في حساب التكافؤ، يتضمن العنصر الأول للتعبير المُنشأ رفع كائن Python mr[0] إلى عقدة Value (يُستخدم lift لتحويل الكائنات التعسفية إلى تعبيرات كلاسيكية). هذا غير ضروري لـ mr[1] والسجل الكلاسيكي التالي المحتمل، إذ أنهما مدخلات لـ expr.bit_xor، ويُجرى أي رفع ضروري تلقائياً في هذه الحالات. يمكن بناء مثل هذه التعبيرات في حلقات وهياكل أخرى.
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
num_qubits = 8
if num_qubits % 2 or num_qubits < 4:
raise ValueError("num_qubits must be an even integer ≥ 4")
meas_qubits = list(range(2, num_qubits, 2)) # qubits to measure and reset
qr = QuantumRegister(num_qubits, "qr")
mr = ClassicalRegister(len(meas_qubits), "m")
qc = QuantumCircuit(qr, mr)
# Create local Bell pairs
qc.reset(qr)
qc.h(qr[::2])
for ctrl in range(0, num_qubits, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Glue neighboring pairs
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Measure boundary qubits between pairs,reset to 0
for k, q in enumerate(meas_qubits):
qc.measure(qr[q], mr[k])
qc.reset(qr[q])
# Parity-conditioned X corrections
# Each non-measured qubit gets flipped iff the parity (XOR) of all
# preceding measurement bits is 1
for tgt in range(num_qubits):
if tgt in meas_qubits: # skip measured qubits
continue
# all measurement registers whose physical qubit index < tgt
left_bits = [k for k, q in enumerate(meas_qubits) if q < tgt]
if not left_bits: # skip if list empty
continue
# build XOR-parity expression
parity = expr.lift(
mr[left_bits[0]]
) # lift the first bit to Value so it will be treated like a boolean.
for k in left_bits[1:]:
parity = expr.bit_xor(
mr[k], parity
) # calculate parity with all other bits
with qc.if_test(parity): # Add X if parity is 1
qc.x(qr[tgt])
# Re-entangle measured qubits
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
qc.draw(output="mpl", style="iqp", idle_wires=False, fold=-1)
Store
يمكنك استخدام تعليمة store لحفظ نتيجة تعبير كلاسيكي، إذا كان سيُستخدم هذا التعبير بشكل متكرر. يتم توازي العمليات تلقائيًا، مما يجعل الكود أكثر كفاءة بشكل ملحوظ في وقت التشغيل.
على سبيل المثال، من الأكثر طبيعية والأكثر كفاءة في وقت التشغيل كتابة ، حيث ، بدلاً من . يحسب التعبير الأول النفي في خطوة موازية واحدة قبل سلسلة XOR، بدلاً من تقييم كل نفي بشكل تسلسلي داخل التعبير.
مثال كامل:
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
qregs = QuantumRegister(4, "q")
creg = ClassicalRegister(3, "c")
# temp is a plain ClassicalRegister used as the store target
temp = ClassicalRegister(3, "temp")
qc = QuantumCircuit(qregs, creg, temp)
qc.h([0, 1, 2])
qc.measure([0, 1, 2], creg)
# Store bit-NOT of the full 3-bit register into temp
qc.store(temp, expr.bit_not(creg))
# Compute parity of temp using bit-indexed XOR
parity = expr.bit_xor(
expr.bit_xor(expr.index(temp, 0), expr.index(temp, 1)),
expr.index(temp, 2),
)
# Flip q3 if parity of ~creg is 1
with qc.if_test(parity):
qc.x(3)
qc.measure([0, 1, 2], creg)
qc.draw("mpl")
الخطوات التالية
- تعلّم كيفية تطبيق فصل ديناميكي دقيق باستخدام stretch.
- استخدم تصور جدول الدائرة الزمني لتصحيح الأخطاء وتحسين دوائرك الديناميكية.
- نفّذ الدوائر الديناميكية.