อัลกอริทึมของ Grover
ประมาณการใช้งาน: ไม่ถึงหนึ่งนาทีบน Eagle r3 processor (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น ระยะเวลาที่ใช้จริงอาจแตกต่างกันไป)
ผลลัพธ์การเรียนรู้
หลังจากทำบทเรียนนี้แล้ว คุณจะสามารถเข้าใจข้อมูลต่อไปนี้:
- วิธีสร้าง Grover oracles ที่ทำเครื่องหมาย computational basis states หนึ่งรายการหรือมากกว่า
- วิธีใช้ฟังก์ชัน
grover_operator()จาก Qiskit circuit library - วิธีกำหนดจำนวนรอบ Grover ที่เหมาะสมที่สุดสำหรับปัญหาที่กำหนด
- วิธีรันอัลกอริทึมของ Grover ด้วย Qiskit Runtime Sampler primitive
ข้อกำหนดเบื้องต้น
แนะนำให้ทำความคุ้นเคยกับหัวข้อเหล่านี้:
พื้นหลัง
Amplitude amplification เป็นอัลกอริทึมหรือ subroutine เชิงควอนตัมอเนกประสงค์ที่สามารถใช้เพื่อให้ได้ความเร็วแบบ quadratic เหนืออัลกอริทึมคลาสสิกบางประเภท อัลกอริทึมของ Grover เป็นอัลกอริทึมแรกที่แสดงให้เห็นถึงความเร็วนี้สำหรับปัญหาการค้นหาแบบไม่มีโครงสร้าง การกำหนดปัญหาการค้นหาของ Grover ต้องการฟังก์ชัน oracle ที่ทำเครื่องหมาย computational basis states หนึ่งรายการขึ้นไปว่าเป็น states ที่เราต้องการค้นหา และ amplification circuit ที่เพิ่ม amplitude ของ states ที่ถูกทำเครื่องหมาย ส่งผลให้ states ที่เหลือถูกกดทับลง
ที่นี่เราจะสาธิตวิธีสร้าง Grover oracles และใช้ grover_operator() จาก Qiskit circuit library เพื่อตั้งค่า Grover's search instance ได้อย่างง่ายดาย Sampler primitive ของ runtime ช่วยให้รัน Grover circuits ได้อย่างราบรื่น
ความต้องการ
ก่อนเริ่มบทเรียนนี้ โปรดตรวจสอบให้แน่ใจว่าติดตั้งสิ่งต่อไปนี้แล้ว:
- Qiskit SDK v2.0 ขึ้นไป พร้อมรองรับ visualization
- Qiskit Runtime v0.22 ขึ้นไป (
pip install qiskit-ibm-runtime)
การตั้งค่า
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
# Built-in modules
import math
# Imports from Qiskit
from qiskit import QuantumCircuit
from qiskit.circuit.library import grover_operator, MCMTGate, ZGate
from qiskit.visualization import plot_distribution
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
# Imports from Qiskit Runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
def grover_oracle(marked_states):
"""Build a Grover oracle for multiple marked states
Here we assume all input marked states have the same number of bits
Parameters:
marked_states (str or list): Marked states of oracle
Returns:
QuantumCircuit: Quantum circuit representing Grover oracle
"""
if not isinstance(marked_states, list):
marked_states = [marked_states]
# Compute the number of qubits in circuit
num_qubits = len(marked_states[0])
qc = QuantumCircuit(num_qubits)
# Mark each target state in the input list
for target in marked_states:
# Flip target bit-string to match Qiskit bit-ordering
rev_target = target[::-1]
# Find the indices of all the '0' elements in bit-string
zero_inds = [
ind
for ind in range(num_qubits)
if rev_target.startswith("0", ind)
]
# Add a multi-controlled Z-gate with pre- and post-applied X-gates (open-controls)
# where the target bit-string has a '0' entry
if zero_inds:
qc.x(zero_inds)
qc.compose(MCMTGate(ZGate(), num_qubits - 1, 1), inplace=True)
if zero_inds:
qc.x(zero_inds)
return qc
ตัวอย่างด้วย simulator ขนาดเล็ก
ในส่วนนี้เราจะอธิบายแต่ละขั้นตอนของอัลกอริทึมของ Grover ในระดับเล็กโดยใช้ local simulator ก่อนที่จะรันปัญหาเดียวกันบน quantum hardware จริง
ขั้นตอนที่ 1: แปลง input คลาสสิกให้เป็นปัญหาควอนตัม
อัลกอริทึมของ Grover ต้องการ oracle ที่ระบุ computational basis states หนึ่งรายการขึ้นไปที่ถูกทำเครื่องหมาย โดย "ทำเครื่องหมาย" หมายถึง state ที่มี phase เป็น -1 controlled-Z gate หรือ multi-controlled generalization บน qubits จะทำเครื่องหมาย state ('1'* bit-string) การทำเครื่องหมาย basis states ที่มี '0' หนึ่งตัวขึ้นไปในการแทนแบบไบนารีต้องใช้ X-gates บน qubits ที่เกี่ยวข้องก่อนและหลัง controlled-Z gate ซึ่งเทียบเท่ากับการมี open-control บน qubit นั้น ในโค้ดต่อไปนี้เราจะกำหนด oracle ที่ทำเครื่องหมาย basis states หนึ่งรายการขึ้นไปตามที่กำหนดผ่าน bitstring representation ของพวกมัน MCMT gate ถูกใช้เพื่อสร้าง multi-controlled Z-gate
Grover's instance เฉพาะ
เมื่อมีฟังก์ชัน oracle แล้ว เราสามารถกำหนด Grover search instance เฉพาะได้ ในตัวอย่างนี้เราจะทำเครื่องหมาย computational states สองรายการจากทั้งหมดแปดรายการที่มีใน three-qubit computational space:
marked_states = ["011", "100"]
oracle = grover_oracle(marked_states)
oracle.draw(output="mpl", style="iqp")
Grover operator
grover_operator() ที่มีอยู่ใน Qiskit รับ oracle circuit แล้วคืน circuit ที่ประกอบด้วย oracle circuit เองและ circuit ที่ขยาย amplitude ของ states ที่ถูกทำเครื่องหมายโดย oracle ที่นี่เราใช้ method decompose() บน circuit เพื่อดู gates ภายใน operator:
grover_op = grover_operator(oracle)
grover_op.decompose().draw(output="mpl", style="iqp")
การนำ grover_op circuit นี้มาใช้ซ้ำหลายครั้งจะขยาย amplitude ของ states ที่ถูกทำเครื่องหมาย ทำให้กลายเป็น bit-strings ที่มีความน่าจะเป็นสูงสุดใน output distribution ของ circuit จำนวนครั้งที่ใช้ที่เหมาะสมที่สุดถูกกำหนดโดยอัตราส่วนของ states ที่ถูกทำเครื่องหมายต่อจำนวน computational states ทั้งหมดที่เป็นไปได้:
optimal_num_iterations = math.floor(
math.pi
/ (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)
Full Grover circuit
การทดลอง Grover ที่สมบูรณ์เริ่มต้นด้วย Hadamard gate บนแต่ละ qubit เพื่อสร้าง superposition ที่สม่ำเสมอของ computational basis states ทั้งหมด ตามด้วย Grover operator (grover_op) ที่ทำซ้ำตามจำนวนครั้งที่เหมาะสม ที่นี่เราใช้ method QuantumCircuit.power(INT) เพื่อนำ Grover operator มาใช้ซ้ำ
qc = QuantumCircuit(grover_op.num_qubits)
# Create even superposition of all basis states
qc.h(range(grover_op.num_qubits))
# Apply Grover operator the optimal number of times
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
# Measure all qubits
qc.measure_all()
qc.draw(output="mpl", style="iqp")
ขั้นตอนที่ 2: ปรับปัญหาให้เหมาะสมสำหรับการรันบน quantum hardware
สำหรับการจำลองขนาดเล็กนี้ เราจะ transpile circuit โดยไม่กำหนด hardware เฉพาะ
pm = generate_preset_pass_manager(optimization_level=3)
circuit_isa = pm.run(qc)
circuit_isa.draw(output="mpl", idle_wires=False, style="iqp")
ขั้นตอนที่ 3: รันด้วย Qiskit primitives
Amplitude amplification เป็นปัญหาแบบ sampling ที่เหมาะสำหรับการรันด้วย SamplerV2 primitive ที่นี่เราใช้ StatevectorSampler จาก qiskit.primitives สำหรับการจำลองในเครื่อง
from qiskit.primitives import StatevectorSampler
sampler = StatevectorSampler()
result = sampler.run([circuit_isa], shots=10_000).result()
dist = result[0].data.meas.get_counts()
ขั้นตอนที่ 4: ประมวลผลและแสดงผลลัพธ์ในรูปแบบคลาสสิกที่ต้องการ
plot_distribution(dist)
ตัวอย่างบน Hardware
ขั้นตอนที่ 1-4
อัลกอริทึมของ Grover เป็นอัลกอริทึมที่ต้องการ fault tolerance โดยพื้นฐาน — multi-controlled Z gates ที่เป็นหัวใจของ oracle และ diffusion operator นำไปสู่ความลึกของ two-qubit gate ที่เพิ่มขึ้นอย่างรวดเร็วมากตามจำนวน qubits (ดังที่เราจะแสดงในส่วนถัดไป) ซึ่งหมายความว่าอัลกอริทึมไม่สามารถขยายขนาดได้ดีบน hardware ที่มีสัญญาณรบกวนในปัจจุบัน ด้วยเหตุนี้เราจึงสาธิตการรันบน hardware ในขนาดเล็กเดียวกับตัวอย่าง simulator ข้างต้น แทนที่จะพยายามแก้ปัญหาขนาดใหญ่กว่า
# -------------------------Step 1-------------------------
marked_states = ["011", "100"]
oracle = grover_oracle(marked_states)
grover_op = grover_operator(oracle)
optimal_num_iterations = math.floor(
math.pi
/ (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)
qc = QuantumCircuit(grover_op.num_qubits)
qc.h(range(grover_op.num_qubits))
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
qc.measure_all()
# -------------------------Step 2-------------------------
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
circuit_isa = pm.run(qc)
# -------------------------Step 3-------------------------
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
sampler.options.environment.job_tags = ["TUT-GA"]
result = sampler.run([circuit_isa]).result()
dist = result[0].data.meas.get_counts()
# -------------------------Step 4-------------------------
plot_distribution(dist)
การอภิปราย: การขยายขนาดความลึกของ two-qubit gate
เหตุผลสำคัญที่อัลกอริทึมของ Grover ถือเป็นอัลกอริทึมที่ต้องการ fault tolerance คือการเติบโตอย่างรวดเร็วของความลึกของ two-qubit gate ของ circuit เมื่อจำนวน qubits เพิ่มขึ้น multi-controlled Z gate ที่เป็นแกนกลางของทั้ง oracle และ diffusion operator จะแตกตัวเป็น two-qubit gates จำนวนหนึ่งที่เพิ่มขึ้นแบบ exponential ตามจำนวน control qubits รวมกับความจริงที่ว่าจำนวนรอบ Grover ที่เหมาะสมที่สุดเองก็เติบโตในลำดับ ทำให้ความลึกของ two-qubit โดยรวมกลายเป็นสิ่งที่ไม่สามารถปฏิบัติได้สำหรับ hardware ที่มีสัญญาณรบกวนอย่างรวดเร็ว
ด้านล่างนี้เราสร้าง Grover circuits สำหรับจำนวน qubit ที่เพิ่มขึ้น ทำการ transpile และ plot ความลึกของ two-qubit gate ที่ได้เพื่อแสดงการขยายขนาดนี้
import matplotlib.pyplot as plt
num_qubits_list = list(range(3, 10))
two_q_depths = []
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
for n in num_qubits_list:
# Mark a single state for simplicity
marked = ["1" * n]
oracle_n = grover_oracle(marked)
grover_op_n = grover_operator(oracle_n)
# Optimal number of iterations
num_iters = math.floor(
math.pi / (4 * math.asin(math.sqrt(len(marked) / 2**n)))
)
# Build the full Grover circuit
qc_n = QuantumCircuit(n)
qc_n.h(range(n))
qc_n.compose(grover_op_n.power(num_iters), inplace=True)
qc_n.measure_all()
# Transpile to a basis gate set and count 2Q depth
pm_n = generate_preset_pass_manager(backend=backend, optimization_level=3)
qc_transpiled = pm_n.run(qc_n)
# Compute depth restricted to 2-qubit operations
depth_2q = qc_transpiled.depth(lambda x: x.operation.num_qubits == 2)
two_q_depths.append(depth_2q)
print(f"n={n}: optimal_iters={num_iters}, 2Q depth={depth_2q}")
# Plot
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(
num_qubits_list,
two_q_depths,
"o-",
linewidth=2,
markersize=8,
color="#6929C4",
)
ax.set_xlabel("Number of qubits", fontsize=13)
ax.set_ylabel("Two-qubit gate depth", fontsize=13)
ax.set_title("Grover's algorithm: 2Q depth scaling", fontsize=14)
ax.set_yscale("log")
ax.grid(True, alpha=0.3)
ax.set_xticks(num_qubits_list)
plt.tight_layout()
plt.show()
n=3: optimal_iters=2, 2Q depth=39
n=4: optimal_iters=3, 2Q depth=111
n=5: optimal_iters=4, 2Q depth=466
n=6: optimal_iters=6, 2Q depth=1646
n=7: optimal_iters=8, 2Q depth=3550
n=8: optimal_iters=12, 2Q depth=7989
n=9: optimal_iters=17, 2Q depth=14824
ดังที่แสดงในกราฟ ความลึกของ two-qubit gate เติบโตอย่างรวดเร็วมากตามจำนวน qubits — โดยประมาณแบบ exponential ทำให้อัลกอริทึมของ Grover ไม่สามารถใช้งานได้จริงบน quantum hardware ที่มีสัญญาณรบกวนในปัจจุบันสำหรับขนาดปัญหาที่ใหญ่กว่าขนาดเล็กมาก อัลกอริทึมนี้ยังคงเป็นเป้าหมายสำคัญสำหรับ quantum computer ที่ทนทานต่อความผิดพลาดในอนาคต ซึ่งการแก้ไขข้อผิดพลาดจะช่วยให้สามารถรัน circuits ที่ลึกได้อย่างน่าเชื่อถือ
ขั้นตอนถัดไป
หากคุณพบว่าเนื้อหานี้น่าสนใจ คุณอาจสนใจเนื้อหาต่อไปนี้:
- Qiskit circuit library:
grover_operator()API reference - บทเรียน QAOA และ บทเรียน utility-scale QAOA ให้ตัวอย่างการปรับแต่งปัญหาแบบ near-term ด้วย quantum computer
- สำหรับการศึกษาเพิ่มเติมเกี่ยวกับ near-term algorithms ดูที่คอร์ส Quantum computing in practice