클래식 피드포워드와 제어 흐름 (동적 Circuit)
패키지 버전
이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 아래 버전 이상을 사용하는 것을 권장합니다.
qiskit[all]~=2.4.0
동적 Circuit은 강력한 도구로, 양자 Circuit 실행 중에 Qubit을 측정하고 해당 중간 Circuit 측정 결과를 바탕으로 Circuit 내에서 클래식 논리 연산을 수행할 수 있습니다. 이 과정은 _클래식 피드포워드_라고도 알려져 있습니다. 동적 Circuit을 어떻게 활용할지에 대한 이해는 아직 초기 단계이지만, 양자 연구 커뮤니티는 이미 다음과 같은 여러 활용 사례를 발굴했습니다.
- GHZ 상태, W-상태 등 효율적인 양자 상태 준비 (W-상태에 대한 자세한 내용은 "피드포워드를 사용하는 얕은 Circuit에 의한 상태 준비"도 참조하세요), 및 광범위한 행렬 곱 상태
- 얕은 Circuit을 이용한 같은 칩 위의 qubit 간 효율적인 장거리 얽힘
- IQP 유사 Circuit의 효율적인 샘플링
Qiskit은 클래식 피드포워드를 위한 네 가지 제어 흐름 구조를 지원하며, 각각 QuantumCircuit의 메서드로 구현됩니다. 구조와 해당 메서드는 다음과 같습니다:
- If 문 -
QuantumCircuit.if_test - Switch 문 -
QuantumCircuit.switch - For 루프 -
QuantumCircuit.for_loop - While 루프 -
QuantumCircuit.while_loop
이 메서드들은 각각 컨텍스트 매니저를 반환하며, 일반적으로 with 문에서 사용됩니다. 이 가이드의 나머지 부분에서는 이러한 구조들과 사용 방법을 각각 설명합니다.
양자 하드웨어에서의 클래식 피드포워드 및 제어 흐름 연산에는 프로그램에 영향을 줄 수 있는 몇 가지 제한 사항이 있습니다. 자세한 내용은 동적 Circuit 실행을 참조하세요.
if 문
if 문은 클래식 비트 또는 레지스터의 값을 기반으로 조건부로 연산을 수행하는 데 사용됩니다.
아래 예제에서는 Qubit에 Hadamard Gate를 적용하고 측정합니다. 결과가 1이면 Qubit에 X Gate를 적용하여 0 상태로 되돌립니다. 그런 다음 Qubit을 다시 측정합니다. 최종 측정 결과는 100% 확률로 0이 됩니다.
# 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 블록을 만드는 데 사용할 수 있습니다. else 블록은 if 블록의 내용이 실행되지 않을 때마다 실행됩니다.
아래 예제에서는 두 Qubit과 두 클래식 비트로 레지스터를 초기화합니다. 첫 번째 Qubit에 Hadamard Gate를 적용하고 측정합니다. 결과가 1이면 두 번째 Qubit에 Hadamard Gate를 적용하고, 그렇지 않으면 두 번째 Qubit에 X Gate를 적용합니다. 마지막으로 두 번째 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에 Hadamard Gate를 적용하고 측정합니다. 결과가 01, 즉 첫 번째 Qubit이 1이고 두 번째 Qubit이 0이면, 세 번째 Qubit에 X Gate를 적용합니다. 마지막으로 세 번째 Qubit을 측정합니다. 명확성을 위해 세 번째 클래식 비트의 상태(0)를 if 조건에 명시적으로 지정했습니다. Circuit 다이어그램에서 조건은 조건이 걸린 클래식 비트의 원으로 표시됩니다. 실선 원은 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에 Hadamard Gate를 적용하고 측정합니다. 결과가 0이면 Qubit에 X Gate를 적용하고, 결과가 1이면 Z Gate를 적용합니다. 최종 측정 결과는 100% 확률로 1이 됩니다.
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 case는 주로 여러 비트로 구성된 클래식 레지스터의 값을 기반으로 분기할 때 유용합니다. 다음 예제는 앞의 어떤 경우에도 해당하지 않을 때 실행되는 default case를 구성하는 방법을 보여줍니다. Switch 문에서는 블록 중 하나만 실행됩니다. 폴스루(fallthrough)는 없습니다.
아래 예제는 두 Qubit에 Hadamard Gate를 적용하고 측정합니다. 결과가 00 또는 11이면 세 번째 Qubit에 Z Gate를 적용합니다. 결과가 01이면 Y Gate를 적용합니다. 앞의 경우 중 어느 것에도 해당하지 않으면 X Gate를 적용합니다. 마지막으로 세 번째 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 루프를 사용하여 Qubit에 X Gate를 5번 적용한 다음 측정합니다. 홀수 번 X Gate를 수행하므로 전체 효과는 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에 Hadamard Gate를 적용하고 측정합니다. 그런 다음 측정 결과가 11인 동안 이 절차를 반복하는 while 루프를 만듭니다. 결과적으로 최종 측정은 절대 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은 Circuit 실행 중 클래식 값에 대한 런타임 연산의 탐색적 표현을 포함합니다.
다음 예제는 패리티 계산을 사용하여 동적 Circuit으로 n-Qubit GHZ 상태를 생성하는 방법을 보여줍니다. 먼저 인접한 Qubit에 개의 Bell 쌍을 생성합니다. 그런 다음 쌍 사이에 CNOT Gate 레이어를 사용하여 이 쌍들을 연결합니다. 이전 CNOT Gate의 타겟 Qubit을 모두 측정하고 측정된 각 Qubit을 상태로 초기화합니다. 이전 모든 비트의 패리티가 홀수인 측정되지 않은 각 사이트에 를 적용합니다. 마지막으로 측정된 Qubit에 CNOT Gate를 적용하여 측정에서 손실된 얽힘을 재확립합니다.
패리티 계산에서 구성된 표현식의 첫 번째 요소는 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를 사용하여 정확한 동적 디커플링을 구현하는 방법을 알아보세요.
- Circuit 스케줄 시각화를 사용하여 동적 Circuit을 디버그하고 최적화하세요.
- 동적 Circuit 실행.