خوارزمية شور
تقدير الاستخدام: ثلاث ثوانٍ على معالج Eagle r3 (ملاحظة: هذا تقدير فحسب. قد يتفاوت وقت التشغيل الفعلي.)
خوارزمية شور، التي طوّرها Peter Shor عام 1994، هي خوارزمية كمية رائدة لتحليل الأعداد الصحيحة إلى عواملها في زمن متعدد الحدود. تكمن أهميتها في قدرتها على تحليل الأعداد الصحيحة الكبيرة بصورة أسرع بشكل أسي من أي خوارزمية كلاسيكية معروفة، مما يُشكّل تهديداً لأمن أنظمة التشفير المستخدمة على نطاق واسع كنظام RSA، الذي يعتمد على صعوبة تحليل الأعداد الكبيرة. وعبر حل هذه المسألة بكفاءة على حاسوب كمي قوي بما يكفي، يمكن لخوارزمية شور أن تُحدث ثورة في مجالات كالتشفير وأمن المعلومات والرياضيات الحسابية، مما يُبرز القوة التحويلية للحوسبة الكمية.
يركز هذا البرنامج التعليمي على توضيح خوارزمية شور عن طريق تحليل العدد 15 على حاسوب كمي.
أولاً، نُعرّف مسألة إيجاد الرتبة ونبني الدوائر المقابلة لها من بروتوكول تقدير الطور الكمي. بعد ذلك، نُشغّل دوائر إيجاد الرتبة على عتاد حقيقي باستخدام دوائر ذات أقصر عمق يمكن تحويلها. يُكمل القسم الأخير خوارزمية شور بربط مسألة إيجاد الرتبة بالتحليل إلى عوامل.
نختتم البرنامج التعليمي بمناقشة حول التوضيحات الأخرى لخوارزمية شور على عتاد حقيقي، مع التركيز على كل من التطبيقات العامة وتلك المصمَّمة خصيصاً لتحليل أعداد محددة كـ 15 و21. ملاحظة: يركز هذا البرنامج التعليمي أكثر على التطبيق والتوضيح العملي للدوائر المتعلقة بخوارزمية شور. للاطلاع على مرجع تعليمي متعمق حول المادة، يُرجى الرجوع إلى دورة أساسيات الخوارزميات الكمية للدكتور John Watrous، والأوراق البحثية في قسم المراجع.
المتطلبات
قبل البدء بهذا البرنامج التعليمي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار v2.0 أو أحدث، مع دعم التصور البياني
- Qiskit Runtime الإصدار v0.40 أو أحدث (
pip install qiskit-ibm-runtime)
الإعداد
# Added by doQumentation — required packages for this notebook
!pip install -q numpy pandas qiskit qiskit-ibm-runtime
import numpy as np
import pandas as pd
from fractions import Fraction
from math import floor, gcd, log
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.library import QFT, UnitaryGate
from qiskit.transpiler import CouplingMap, generate_preset_pass_manager
from qiskit.visualization import plot_histogram
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمية
الخلفية النظرية
تستخدم خوارزمية شور للتحليل إلى عوامل مسألةً وسيطةً تُعرف بمسألة إيجاد الرتبة. في هذا القسم، نُبيّن كيفية حل مسألة إيجاد الرتبة باستخدام تقدير الطور الكمي.
مسألة تقدير الطور
في مسألة تقدير الطور، تُعطى لنا حالة كمية من كيوبت، إلى جانب دائرة كمية أحادية الاتجاه (unitary) تعمل على كيوبت. ويُفترض أن هي متجه ذاتي للمصفوفة الأحادية التي تصف عمل الدائرة، وهدفنا هو حساب القيمة الذاتية أو تقديرها تقريبياً. بعبارة أخرى، يجب أن تُخرج الدائرة تقريباً للعدد الذي يحقق
الهدف من دائرة تقدير الطور هو تقري ب بدقة بت. رياضياً، نريد إيجاد بحيث ، حيث . تُوضّح الصورة التالية الدائرة الكمية التي تُقدّر بدقة بت عن طريق إجراء قياس على كيوبت.
في الدائرة أعلاه، تُهيَّأ الكيوبتات الـ العلوية في الحالة ، وتُهيَّأ الكيوبتات الـ السفلية في الحالة ، التي يُفترض أنها متجه ذاتي لـ . المكوّن الأول في دائرة تقدير الطور هو عمليات التحكم الأحادية (controlled-unitary) المسؤولة عن تنفيذ الرفس الطوري (phase kickback) إلى كيوبت التحكم المقابل. هذه العمليات الأحادية المتحكَّم بها تُرفع إلى قوى وفقاً لموضع كيوبت التحكم، بدءاً من البت الأقل أهمية حتى البت الأكثر أهمية. بما أن هي متجه ذاتي لـ ، لا تتأثر حالة الكيوبتات الـ السفلية بهذه العملية، لكن معلومات الطور للقيمة الذاتية تنتقل إلى الكيوبتات الـ العلوية.
اتضح أنه بعد عملية الرفس الطوري عبر العمليات الأحادية المتحكَّم بها، تكون جميع الحالات الممكنة للكيوبتات الـ العلوية متعامدة مع بعضها لكل متجه ذاتي للعملية الأحادية . لذلك، يمكن التمييز بين هذه الحالات بشكل مثالي، ويمكننا تدوير الأساس الذي تشكّله إلى الأساس الحسابي لإجراء القياس. يُظهر التحليل الرياضي أن مصفوفة التدوير هذه تقابل تحويل فورييه الكمي العكسي (QFT) في فضاء هيلبرت ذي الأبعاد . والحدس وراء ذلك هو أن البنية الدورية لمؤثرات الأس المعياري تكون مُرمَّزة في الحالة الكمية، ويُحوّل QFT هذه الدورية إلى ذرى قابلة للقياس في نطاق التردد.
لفهم أعمق لسبب توظيف دائرة QFT في خوارزمية شور، نُحيل القارئ إلى دورة أساسيات الخوارزميات الكمية. نحن الآن مستعدون لاستخدام دائرة تقدير الطور لإيجاد الرتبة.
مسألة إيجاد الرتبة
لتعريف مسألة إيجاد الرتبة، نبدأ ببعض مفاهيم نظرية الأعداد. أولاً، لأي عدد صحيح موجب ، نُعرّف المجموعة كما يلي: تُجرى جميع العمليات الحسابية في بشكل مودولار بالنسبة إلى . بشكل خاص، جميع العناصر الأولية نسبياً مع هي عناصر مميزة وتشكّل كما يلي: بالنسبة لعنصر ، يُعرَّف أصغر عدد صحيح موجب بحيث بوصفه رتبة مودولو . كما سنرى لاحقاً، فإن إيجاد رتبة سيُمكّننا من تحليل إلى عوامله. لبناء دائرة إيجاد الرتبة من دائرة تقدير الطور، نحتاج إلى أ مرين. أولاً، نحتاج إلى تعريف العملية الأحادية التي ستُمكّننا من إيجاد الرتبة ، وثانياً، نحتاج إلى تعريف متجه ذاتي لـ لتهيئة الحالة الابتدائية لدائرة تقدير الطور.
لربط مسألة إيجاد الرتبة بتقدير الطور، نأخذ في الاعتبار العملية المُعرَّفة على نظام تتوافق حالاته الكلاسيكية مع ، حيث نضرب بعنصر ثابت . بشكل محدد، نُعرّف مؤثر الضرب هذا بحيث لكل . لاحظ أنه من المفهوم ضمنياً أننا نأخذ الحاصل مودولو داخل الشعاع في الجانب الأيمن من المعادلة. يُظهر التحليل الرياضي أن هو مؤثر أحادي. علاوة على ذلك، اتضح أن لـ أزواجاً من المتجهات الذاتية والقيم الذاتية تُمكّننا من ربط الرتبة لـ بمسألة تقدير الطور. تحديداً، لأي اختيار ، لدينا أن هي متجه ذاتي لـ تقابله القيمة الذاتية ، حيث بالملاحظة، نرى أن زوجاً مناسباً من المتجه الذاتي والقيمة الذاتية هو الحالة مع . لذلك، لو أمكننا إيجاد المتجه الذاتي ، لاستطعنا تقدير الطور باستخدام دائرتنا الكمية وبالتالي الحصول على تقدير للرتبة . غير أن ذلك ليس سهلاً، ونحتاج إلى النظر في بديل.
لنفكر في ما ستُفضي إليه الدائرة لو هيّأنا الحالة الحسابية كحالة ابتدائية. ليست هذه حالة ذاتية لـ ، لكنها التراكب المتماثل للحالات الذاتية التي وصفناها للتو. بعبارة أخرى، تنطبق العلاقة التالية. تعني المعادلة أعلاه أنه لو حدّدنا الحالة الابتدائية إلى ، سنحصل على نفس نتيجة القياس تماماً كما لو اخترنا بشكل منتظم عشوائياً واستخدمنا كمتجه ذاتي في دائرة تقدير الطور. بعبارة أخرى، يُعطي قياس الكيوبتات الـ العلوية تقريباً للقيمة حيث يُختار بشكل منتظم عشوائياً. يُتيح لنا هذا تعلّم بدرجة عالية من الثقة بعد عدة تشغيلات مستقلة، وهو هدفنا.
مؤثرات الأس المعياري
حتى الآن، ربطنا مسألة تقدير الطور بمسألة إيجاد الرتبة بتعريف و في دائرتنا الكمية. لذلك، المكوّن الأخير المتبقي هو إيجاد طريقة فعّالة لتعريف الأسس المعيارية لـ كـ لـ . لتنفيذ هذه العملية الحسابية، نجد أنه لأي قوة نختارها، يمكننا إنشاء دائرة لـ ليس بتكرار دائرة عدد مرة، بل بدلاً من ذلك بحساب ثم استخدام دائرة . بما أننا نحتاج فقط إلى القوى التي هي بحد ذاتها قوى للعدد 2، يمكننا تنفيذ ذلك بكفاءة كلاسيكياً باستخدام التربيع التكراري.
الخطوة 2: تحسين المسألة للتنفيذ على عتاد كمي
مثال محدد بـ و
يمكننا التوقف هنا لمناقشة مثال محدد وبناء دائرة إيجاد الرتبة لـ . لاحظ أن القيم الغير تافهة الممكنة لـ عند هي . في هذا المثال، نختار . سنبني المؤثر ومؤثرات الأس المعياري . تصرف على حالات الأساس الحسابي كما يلي. بالملاحظة، يمكننا أن نرى أن حالات الأساس تُعاد ترتيبها، لذا لدينا مصفوفة تبادل. يمكننا بناء هذه العملية على أربعة كيوبتات باستخدام بوابات SWAP. في الكود التالي، نبني عمليتَي والـ controlled-.
def M2mod15():
"""
M2 (mod 15)
"""
b = 2
U = QuantumCircuit(4)
U.swap(2, 3)
U.swap(1, 2)
U.swap(0, 1)
U = U.to_gate()
U.name = f"M_{b}"
return U
# Get the M2 operator
M2 = M2mod15()
# Add it to a circuit and plot
circ = QuantumCircuit(4)
circ.compose(M2, inplace=True)
circ.decompose(reps=2).draw(output="mpl", fold=-1)
def controlled_M2mod15():
"""
Controlled M2 (mod 15)
"""
b = 2
U = QuantumCircuit(4)
U.swap(2, 3)
U.swap(1, 2)
U.swap(0, 1)
U = U.to_gate()
U.name = f"M_{b}"
c_U = U.control()
return c_U
# Get the controlled-M2 operator
controlled_M2 = controlled_M2mod15()
# Add it to a circuit and plot
circ = QuantumCircuit(5)
circ.compose(controlled_M2, inplace=True)
circ.decompose(reps=1).draw(output="mpl", fold=-1)
ستُفكَّك البوابات التي تعمل على أكثر من كيوبتين إلى بوابات ثنائية الكيوبت.
circ.decompose(reps=2).draw(output="mpl", fold=-1)

الآن نحتاج إلى بناء مؤثرات الأس المعياري. للحصول على دقة كافية في تقدير الطور، سنستخدم ثمانية كيوبتات لقياس التقدير. لذلك، نحتاج إلى بناء مع لكل .
def a2kmodN(a, k, N):
"""Compute a^{2^k} (mod N) by repeated squaring"""
for _ in range(k):
a = int(np.mod(a**2, N))
return a
k_list = range(8)
b_list = [a2kmodN(2, k, 15) for k in k_list]
print(b_list)
[2, 4, 1, 1, 1, 1, 1, 1]
كما يتضح من قائمة قيم ، بالإضافة إلى التي بنيناها سابقاً، نحتاج أيضاً إلى بناء و. لاحظ أن تعمل بشكل تافه على حالات الأساس الحسابي، لذا فهي ببساطة مؤثر الهوية.
يعمل على حالات الأساس الحسابي كما يلي.
لذلك، يمكن بناء هذا التبادل بعملية SWAP التالية.
def M4mod15():
"""
M4 (mod 15)
"""
b = 4
U = QuantumCircuit(4)
U.swap(1, 3)
U.swap(0, 2)
U = U.to_gate()
U.name = f"M_{b}"
return U
# Get the M4 operator
M4 = M4mod15()
# Add it to a circuit and plot
circ = QuantumCircuit(4)
circ.compose(M4, inplace=True)
circ.decompose(reps=2).draw(output="mpl", fold=-1)
def controlled_M4mod15():
"""
Controlled M4 (mod 15)
"""
b = 4
U = QuantumCircuit(4)
U.swap(1, 3)
U.swap(0, 2)
U = U.to_gate()
U.name = f"M_{b}"
c_U = U.control()
return c_U
# Get the controlled-M4 operator
controlled_M4 = controlled_M4mod15()
# Add it to a circuit and plot
circ = QuantumCircuit(5)
circ.compose(controlled_M4, inplace=True)
circ.decompose(reps=1).draw(output="mpl", fold=-1)
ستُفكَّك البوابات التي تعمل على أكثر من كيوبتين إلى بوابات ثنائية الكيوبت.
circ.decompose(reps=2).draw(output="mpl", fold=-1)

رأينا أن مؤثرات لقيمة معطاة هي عمليات تبادل. نظراً للحجم الصغير نسبياً لمسألة التبادل التي لدينا هنا، إذ يتطلب أربعة كيوبتات فحسب، تمكّنّا من تركيب هذه العمليات مباشرةً باستخدام بوابات SWAP بالفحص المباشر. بشكل عام، قد لا يكون هذا نهجاً قابلاً للتوسع. بدلاً من ذلك، قد نحتاج إلى بناء مصفوفة التبادل بشكل صريح، واستخدام فئة UnitaryGate في Qiskit وأساليب التحويل لتركيب مصفوفة التبادل هذه. غير أن ذلك قد يؤدي إلى دوائر أعمق بكثير. يلي ذلك مثال توضيحي.
def mod_mult_gate(b, N):
"""
Modular multiplication gate from permutation matrix.
"""
if gcd(b, N) > 1:
print(f"Error: gcd({b},{N}) > 1")
else:
n = floor(log(N - 1, 2)) + 1
U = np.full((2**n, 2**n), 0)
for x in range(N):
U[b * x % N][x] = 1
for x in range(N, 2**n):
U[x][x] = 1
G = UnitaryGate(U)
G.name = f"M_{b}"
return G
# Let's build M2 using the permutation matrix definition
M2_other = mod_mult_gate(2, 15)
# Add it to a circuit
circ = QuantumCircuit(4)
circ.compose(M2_other, inplace=True)
circ = circ.decompose()
# Transpile the circuit and get the depth
coupling_map = CouplingMap.from_line(4)
pm = generate_preset_pass_manager(coupling_map=coupling_map)
transpiled_circ = pm.run(circ)
print(f"qubits: {circ.num_qubits}")
print(
f"2q-depth: {transpiled_circ.depth(lambda x: x.operation.num_qubits==2)}"
)
print(f"2q-size: {transpiled_circ.size(lambda x: x.operation.num_qubits==2)}")
print(f"Operator counts: {transpiled_circ.count_ops()}")
transpiled_circ.decompose().draw(
output="mpl", fold=-1, style="clifford", idle_wires=False
)
qubits: 4
2q-depth: 94
2q-size: 96
Operator counts: OrderedDict({'cx': 45, 'swap': 32, 'u': 24, 'u1': 7, 'u3': 4, 'unitary': 3, 'circuit-335': 1, 'circuit-338': 1, 'circuit-341': 1, 'circuit-344': 1, 'circuit-347': 1, 'circuit-350': 1, 'circuit-353': 1, 'circuit-356': 1, 'circuit-359': 1, 'circuit-362': 1, 'circuit-365': 1, 'circuit-368': 1, 'circuit-371': 1, 'circuit-374': 1, 'circuit-377': 1, 'circuit-380': 1})

لنقارن هذه الأعداد مع عمق الدائرة المُجمَّعة لتطبيقنا اليدوي لبوابة .
# Get the M2 operator from our manual construction
M2 = M2mod15()
# Add it to a circuit
circ = QuantumCircuit(4)
circ.compose(M2, inplace=True)
circ = circ.decompose(reps=3)
# Transpile the circuit and get the depth
coupling_map = CouplingMap.from_line(4)
pm = generate_preset_pass_manager(coupling_map=coupling_map)
transpiled_circ = pm.run(circ)
print(f"qubits: {circ.num_qubits}")
print(
f"2q-depth: {transpiled_circ.depth(lambda x: x.operation.num_qubits==2)}"
)
print(f"2q-size: {transpiled_circ.size(lambda x: x.operation.num_qubits==2)}")
print(f"Operator counts: {transpiled_circ.count_ops()}")
transpiled_circ.draw(
output="mpl", fold=-1, style="clifford", idle_wires=False
)
qubits: 4
2q-depth: 9
2q-size: 9
Operator counts: OrderedDict({'cx': 9})
كما يتضح، أسفر نهج مصفوفة التبادل عن دائرة أعمق بشكل ملحوظ حتى لبوابة واحدة مقارنةً بتطبيقنا اليدوي لها. لذلك، سنواصل استخدام تطبيقنا السابق لعمليات . الآن، نحن مستعدون لبناء دائرة إيجاد الرتبة الكاملة باستخدام مؤثرات الأس المعياري المتحكَّم بها التي عرّفناها مسبقاً. في الكود التالي، نستورد أيضاً دائرة QFT من مكتبة Qiskit Circuit، التي تستخدم بوابات Hadamard على كل كيوبت، وسلسلة من بوابات controlled-U1 (أو Z، حسب الطور)، وطبقة من بوابات SWAP.
# Order finding problem for N = 15 with a = 2
N = 15
a = 2
# Number of qubits
num_target = floor(log(N - 1, 2)) + 1 # for modular exponentiation operators
num_control = 2 * num_target # for enough precision of estimation
# List of M_b operators in order
k_list = range(num_control)
b_list = [a2kmodN(2, k, 15) for k in k_list]
# Initialize the circuit
control = QuantumRegister(num_control, name="C")
target = QuantumRegister(num_target, name="T")
output = ClassicalRegister(num_control, name="out")
circuit = QuantumCircuit(control, target, output)
# Initialize the target register to the state |1>
circuit.x(num_control)
# Add the Hadamard gates and controlled versions of the
# multiplication gates
for k, qubit in enumerate(control):
circuit.h(k)
b = b_list[k]
if b == 2:
circuit.compose(
M2mod15().control(), qubits=[qubit] + list(target), inplace=True
)
elif b == 4:
circuit.compose(
M4mod15().control(), qubits=[qubit] + list(target), inplace=True
)
else:
continue # M1 is the identity operator
# Apply the inverse QFT to the control register
circuit.compose(QFT(num_control, inverse=True), qubits=control, inplace=True)
# Measure the control register
circuit.measure(control, output)
circuit.draw("mpl", fold=-1)
لاحظ أننا حذفنا عمليات الأس المعياري المتحكَّم بها من كيوبتات التحكم المتبقية لأن هو مؤثر الهوية.
لاحظ أننا في وقت لاحق من هذا البرنامج التعليمي سنُشغّل هذه الدائرة على الخلفية ibm_marrakesh. للقيام بذلك، نُحوّل الدائرة وفقاً لهذه الخلفية المحددة ونُرسل عمق الدائرة وأعداد البوابات.
service = QiskitRuntimeService()
backend = service.backend("ibm_marrakesh")
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
transpiled_circuit = pm.run(circuit)
print(
f"2q-depth: {transpiled_circuit.depth(lambda x: x.operation.num_qubits==2)}"
)
print(
f"2q-size: {transpiled_circuit.size(lambda x: x.operation.num_qubits==2)}"
)
print(f"Operator counts: {transpiled_circuit.count_ops()}")
transpiled_circuit.draw(
output="mpl", fold=-1, style="clifford", idle_wires=False
)
2q-depth: 187
2q-size: 260
Operator counts: OrderedDict({'sx': 521, 'rz': 354, 'cz': 260, 'measure': 8, 'x': 4})
