고전적 피드포워드 및 제어 흐름(동적 회로)
이 페이지의 코드는 다음 요구 사항을 사용하여 개발되었습니다. 다음 버전 이상을 사용하는 것이 좋습니다.
qiskit[all]~=2.5.2
동적 회로는 양자 회로 실행 중간에 큐비트를 측정한 다음 이러한 중간 회로 측정 결과를 기반으로 회로 내에서 고전적인 논리 연산을 수행할 수 있는 강력한 도구입니다. 이 프로세스는 고전적인 피드포워드라고도 합니다. 동적 회로를 가장 잘 활용하는 방법을 이해하는 초기 단계이지만, 양자 연구 커뮤니티에서는 이미 다음과 같은 여러 가지 사용 사례를 확인했습니다:
- 효율적인 양자 상태 준비, 예를 들어 GHZ 상태, W-상태 (W-상태에 대한 자세한 내용은 "피드 포워드를 이용한 얕은 회로에 의한 상태 준비" 도 참조), 그리고 광범위한 행렬 곱 상태들
- 얕은 회로를 사용하여 동일한 칩에서 큐비트 간 장거리 얽힘을 효율적으로 처리합니다
- IQP 유사 회로의 효율적인 샘플링
QuantumCircuitQiskit은 고전적인 피드포워드(feedforward)를 위한 네 가지 제어 흐름 구조를 지원하며, 각각은 의 메서드로 구현되어 있습니다. 구성과 이에 대응하는 메서드는 다음과 같습니다:
- if 문 -
QuantumCircuit.if_test - switch 문 -
QuantumCircuit.switch - for 루프 -
QuantumCircuit.for_loop - While 루프 -
QuantumCircuit.while_loop
with 이 방법들은 각각 컨텍스트 매니저 를 반환하며, 일반적으로 문에서 사용됩니다. 이 가이드의 나머지 부분에서는 이러한 각 구문과 그 사용법을 설명합니다.
양자 하드웨어에서 고전적인 피드포워드 및 제어 흐름 연산에는 프로그램에 영향을 미칠 수 있는 몇 가지 제한 사항이 있습니다. 자세한 내용은 ‘동적 회로 실행’을 참조하십시오.
if 명령문
if 문은 기존 비트 또는 레지스터 값에 따라 조건부로 연산을 수행하는 데 사용됩니다.
아래 예시에서는 큐비트에 하다마드 게이트를 적용하고 측정합니다. 결과가 1이면 큐비트에 X 게이트를 적용하여 0 상태로 다시 뒤집는 효과를 냅니다. 그런 다음 큐비트를 다시 측정합니다. 측정 결과는 100% 확률로 0이 되어야 합니다.
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}Output:
with 문에는 그 자체가 컨텍스트 관리자인 할당 대상을 지정할 수 있으며, 이 할당 대상은 저장된 후 if 블록의 내용이 실행되지 않을 때마다 실행되는 else 블록을 생성하는 데 사용될 수 있습니다.
아래 예제에서는 2개의 큐비트와 2개의 클래식 비트로 레지스터를 초기화합니다. 첫 번째 큐비트에 하다마드 게이트를 적용하고 측정합니다. 결과가 1이면 두 번째 큐비트에 Hadamard 게이트를 적용하고, 그렇지 않으면 두 번째 큐비트에 X 게이트를 적용합니다. 마지막으로 두 번째 큐비트도 측정합니다.
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}Output:
단일 클래식 비트에 대한 컨디셔닝 외에도 여러 비트로 구성된 클래식 레지스터의 값을 컨디셔닝할 수도 있습니다.
아래 예시에서는 두 큐비트에 하다마르 게이트를 적용하고 이를 측정합니다. 결과가 01, 즉 첫 번째 큐비트가 1이고 두 번째 큐비트가 0인 경우, 우리는 세 번째 큐비트에 X 게이트를 적용한다. 마지막으로, 우리는 세 번째 큐비트를 측정합니다. 명확성을 위해 조건에서 if 세 번째 클래식 비트(0)의 상태를 명시하기로 선택했음을 유의하십시오. 회로도에서 조건은 조건부로 설정된 클래식 비트 위의 원으로 표시됩니다. 실선 원은 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}Output:
Switch문
switch 문은 일반 비트나 레지스터의 값에 따라 수행할 작업을 선택하는 데 사용됩니다. if 문과 비슷하지만, 분기 논리에 더 많은 경우를 지정할 수 있습니다. 아래 예제에서는 큐비트에 하다마르 게이트를 적용한 후 이를 측정합니다. 결과가 0이면 큐비트에 X 게이트를 적용하고, 결과가 1이면 Z 게이트를 적용한다. 그 결과 측정값은 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}Output:
위의 예제에서는 하나의 고전적 비트를 사용했기 때문에 가능한 경우가 두 가지뿐이었으므로, if-else 문을 사용해도 동일한 결과를 얻을 수 있었을 것입니다. switch 문은 주로 여러 비트로 구성된 일반 레지스터의 값에 따라 분기할 때 유용합니다. 다음 예제는 앞선 모든 조건이 충족되지 않을 경우 실행되는 기본 조건을 구성하는 방법을 보여줍니다. switch 문에서는 블록 중 오직 하나만 실행된다는 점에 유의하십시오. 폴스루는 발생하지 않습니다.
아래 예제에서는 두 큐비트에 하다마르 게이트를 적용한 후 이를 측정합니다. 결과가 00이거나 11인 경우, 세 번째 큐비트에 Z 게이트를 적용한다. 결과가 01이면 Y 게이트를 적용하십시오. 앞서 언급한 경우 중 어느 것도 해당되지 않는다면, X 게이트를 적용하십시오. 마지막으로, 세 번째 큐비트를 측정합니다.
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}Output:
For 루프
A for 루프는 일련의 값을 순차적으로 처리하며, 각 반복마다 특정 연산을 수행하는 데 사용됩니다.
다음 예제에서는 for 루프를 사용하여 1개의 큐비트에 5개의 X 게이트를 적용한 다음, 그 상태를 측정합니다. 홀수 개의 X 게이트가 수행되기 때문에, 전체적인 효과는 큐비트를 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}Output:
While 루프
while 루프는 특정 조건이 충족되는 동안 명령어를 반복하는 데 사용됩니다.
아래 예제에서는 두 큐비트에 하다마르 게이트를 적용한 후 이를 측정합니다. 그런 다음 측정 결과가 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}Output:
고전적 표현들
Qiskit의 고전 표현 모듈에는 qiskit.circuit.classical 회로 실행 중 고전 값에 대한 런타임 연산을 탐색적으로 표현한 내용이 포함되어 있습니다.
다음 예는 패리티 계산을 사용하여 동적 회로를 사용하여 n-쿼비트 GHZ 상태를 생성하는 방법을 보여줍니다. 먼저, 인접한 큐비트에 벨 쌍을 생성합니다. 그런 다음 쌍 사이에 CNOT 게이트 층을 사용하여 이 쌍을 함께 붙입니다. 그런 다음 모든 이전 CNOT 게이트의 목표 큐비트를 측정하고 측정된 각 큐비트를 상태로 재설정합니다. 이전 모든 비트의 패리티가 홀수인 측정되지 않은 모든 사이트에 을 적용합니다. 마지막으로, 측정된 큐비트에 CNOT 게이트를 적용하여 측정에서 손실된 얽힘을 다시 설정합니다.
패리티 계산에서, 생성된 표현식의 첫 번째 요소는 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)Output:
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")Output:
다음 단계
- 스트레치를 사용하여 정확한 동적 디커플링을 구현하는 방법을 알아보세요.
- 회로 스케줄 시각화 기능을 활용하여 동적 회로를 디버깅하고 최적화하세요.
- 동적 회로를 실행합니다.