그로버 알고리즘
예상 소요 시간: Eagle r3 프로세서 기준 1분 미만 (참고: 이는 예상치에 불과합니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 하나 이상의 계산 기저 상태를 표시하는 그로버 오라클을 구성하는 방법
- Qiskit 회로 라이브러리의 함수
grover_operator()사용 방법 - 주어진 문제에 대해 Grover 반복 횟수의 최적값을 결정하는 방법
- IBM Quantum 샘플러 프리미티브를 사용하여 그로버 알고리즘을 실행하는 방법
전제조건
배경
진폭 증폭(Amplitude amplification)은 몇 가지 고전 알고리즘에 비해 2차적인 속도 향상을 얻을 수 있는 범용 양자 알고리즘, 즉 서브루틴입니다. 그로버 알고리즘은 비정형 검색 문제에서 이러한 속도 향상을 최초로 입증한 알고리즘이다. 그로버 검색 문제를 구성하려면, 하나 이상의 계산 기저 상태를 우리가 찾고자 하는 상태로 표시하는 오라클 함수와, 표시된 상태의 진폭을 증가시켜 나머지 상태의 신호를 억제하는 증폭 회로가 필요합니다.
여기서는 Grover 오라클을 구성하고 키스킷 회로 라이브러리의 grover_operator() 를 사용하여 Grover의 검색 인스턴스를 쉽게 설정하는 방법을 보여드리겠습니다. 런타임 Sampler 프리미티브는 그로버 회로를 원활하게 실행할 수 있게 해줍니다.
요구사항
이 튜토리얼을 시작하기 전에 다음 항목이 설치되어 있는지 확인하십시오:
- Qiskit SDK v2.0 또는 그 이후 버전, 시각화 기능 지원
- Qiskit Runtime v0.22 또는 그 이후 (
pip install 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-ibm-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소규모 시뮬레이터 예시
이 섹션에서는 실제 양자 하드웨어에서 동일한 문제를 실행하기 전에, 로컬 시뮬레이터를 사용하여 소규모로 그로버 알고리즘의 각 단계를 단계별로 살펴보겠습니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
그로버 알고리즘을 사용하려면 하나 이상의 ‘표시된’ 계산 기저 상태를 지정하는 오라클 이 필요하며, 여기서 ‘표시된’이란 위상이 -1 인 상태를 의미한다. 제어된 Z 게이트, 또는 큐비트에 대한 다중 제어 일반화는 상태('1'* 비트열)를 나타냅니다. 이진 표현에서 기저 상태를 하나 이상의 '0' 로 표시하려면, 제어 Z 게이트 전후에 해당 큐비트에 X-게이트를 적용해야 하며, 이는 해당 큐비트에 오픈-컨트롤을 적용하는 것과 동일하다. 다음 코드에서는 비트열 표현을 통해 정의된 하나 이상의 입력 기저 상태를 식별하는 오라클을 정의합니다. 이 MCMT 게이트는 다중 제어 Z-게이트를 구현하는 데 사용됩니다.
특정 그로버 인스턴스
이제 오라클 함수가 생겼으므로 Grover 검색의 특정 인스턴스를 정의할 수 있습니다. 이 예제에서는 3큐비트 계산 공간에서 사용 가능한 8개의 계산 상태 중 2개의 계산 상태를 표시합니다:
marked_states = ["011", "100"]
oracle = grover_oracle(marked_states)
oracle.draw(output="mpl", style="iqp")Output:
그로버 연산자
내장된 키스킷( grover_operator() )은 오라클 회로를 가져와서 오라클 회로 자체와 오라클이 표시한 상태를 증폭하는 회로로 구성된 회로를 반환합니다. 여기서는 decompose() 메서드를 사용하여 회로를 통해 오퍼레이터 내의 게이트를 확인합니다:
grover_op = grover_operator(oracle)
grover_op.decompose().draw(output="mpl", style="iqp")Output:
이 grover_op 회로를 반복적으로 적용하면 표시된 상태가 증폭되어 회로의 출력 분포에서 가장 가능성이 높은 비트 문자열이 됩니다. 이러한 애플리케이션의 최적 수는 가능한 총 계산 상태 수에 대한 표시된 상태의 비율에 따라 결정됩니다:
optimal_num_iterations = math.floor(
math.pi
/ (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)풀 그로버 회로
완전한 Grover 실험은 각 큐비트에 Hadamard 게이트로 시작하여 모든 계산 기준 상태를 균등하게 중첩한 다음 Grover 연산자(grover_op)가 최적의 횟수만큼 반복하는 것으로 시작됩니다. 여기서는 QuantumCircuit.power(INT) 메서드를 사용하여 Grover 연산자를 반복적으로 적용합니다.
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")Output:
2단계: 양자 하드웨어 실행을 위한 문제 최적화
소규모 시뮬레이션을 위해, 특정 하드웨어를 대상으로 하지 않고 회로를 트랜스파일합니다.
pm = generate_preset_pass_manager(optimization_level=3)
circuit_isa = pm.run(qc)
circuit_isa.draw(output="mpl", idle_wires=False, style="iqp")Output:
3단계: Qiskit primitives 명령어로 실행합니다
진폭 증폭은 프라이머리(primitive)를 SamplerV2 사용하여 실행하기에 적합한 샘플링 문제입니다. 여기서는 지역 시뮬레이션을 위해 에서 qiskit.primitives 를 StatevectorSampler 사용합니다.
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)Output:
하드웨어 예시
1~4단계
그로버 알고리즘은 근본적으로 내결함성 알고리즘입니다. 오라클과 확산 연산자의 핵심을 이루는 다중 제어 Z 게이트는 2큐비트 게이트의 깊이가 큐비트 수에 따라 매우 빠르게 증가하게 만듭니다(다음 절에서 보여드리겠지만). 즉, 이 알고리즘은 오늘날의 불안정한 하드웨어 환경에서는 확장성이 좋지 않다는 뜻입니다. 이러한 이유로, 우리는 더 큰 규모의 문제를 다루기보다는 앞서 소개한 시뮬레이터 예제와 동일한 소규모 수준에서 하드웨어 실행을 시연합니다.
# -------------------------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)Output:
토론: 2-큐비트 게이트의 깊이 확장
그로버 알고리즘이 내결함성 알고리즘으로 간주되는 주요 이유는 큐비트 수가 증가함에 따라 회로의 2-큐비트 게이트 깊이가 급격히 증가하기 때문이다. 오라클과 확산 연산자의 핵심을 이루는 다중 제어 Z 게이트는 제어 큐비트의 수에 따라 기하급수적으로 증가하는 여러 개의 2-큐비트 게이트로 분해된다. 그로버 반복 횟수의 최적값 자체가 의 비율로 증가한다는 점을 고려할 때, 전체 2-큐비트 연산 깊이는 잡음이 있는 하드웨어 환경에서는 금세 실용성을 잃게 된다.
아래에서는 큐비트 수가 증가함에 따라 그로버 회로를 구성하고, 이를 트랜스파일링한 뒤, 그 결과로 얻어진 2-큐비트 게이트 깊이를 그래프로 표시하여 이러한 확장성을 보여줍니다.
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()Output:
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
그래프에서 볼 수 있듯이, 2큐비트 게이트의 깊이는 큐비트 수에 따라 극도로 빠르게 증가하며, 대략 지수적으로 증가합니다. 이로 인해 그로버 알고리즘은 문제 규모가 매우 작지 않은 한, 현재의 잡음이 많은 양자 하드웨어에서는 실용적이지 못합니다. 이 알고리즘은 오류 정정을 통해 심층 회로를 안정적으로 실행할 수 있게 될 미래의 내결함성 양자 컴퓨터를 위한 중요한 연구 과제로 남아 있다.
다음 단계
이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:
- Qiskit 회로 라이브러리:
grover_operator()API 참조 - QAOA 튜 토리얼과 대규모 QAOA 강의에서는 양자 컴퓨터를 활용한 최적화의 최근 사례를 다룹니다
- 단기 알고리즘에 대해 더 자세히 알아보려면 ‘실전 양자 컴퓨팅 ’ 강좌를 참고하세요