트랜스파일러
이 generate_boxing_pass_manager() 함수는 회로 명령어를 주석이 달린 상자로 묶을 수 있는 qiskit.transpiler.PassManager 를 만드는 데 유용하고 편리한 도구입니다. 이는 회로를 수동으로 그룹화하고 주석을 달아야 하는 번거로움을 덜어주는 간편한 대안이며, 워크플로우를 자동화하거나 다양한 회로 박스링 전략을 테스트하는 데 유용합니다.
이 안내서는 및 generate_boxing_pass_manager() 그 인수를 사용하는 방법을 설명합니다. 각 함수 인자의 효과를 명확히 설명하기 위해, 다음 섹션에서는 주로 아래의 회로를 참고할 것입니다.
from qiskit.circuit import Parameter, QuantumCircuit
circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
circuit.rz(Parameter(f"th_{qubit}"), qubit)
circuit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))
circuit.draw("mpl", scale=0.8)
작업들을 상자로 묶기
이 인수는 True 또는 로 설정하여 2-큐비트 게이트를 상자로 묶을지 여부를 지정할 False 수 enable_gates 있습니다. 마찬가지로, 측정값을 그룹화할지 여부를 enable_measures 지정합니다. 다음 코드 조각은 게이트와 측정값이 모두 그룹화된 예시를 보여줍니다.
from samplomatic.transpiler import generate_boxing_pass_manager
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
그림에서 볼 수 있듯이, 패스 매니저는 두 가지 유형의 박스를 생성합니다. 하나는 2-큐비트 게이트로 구성된 단일 층을 포함하는 박스이고, 다른 하나는 측정으로 구성된 단일 층을 포함하는 박스입니다. 이러한 분리는 일반적으로 동질적인 연산 레이어를 대상으로 하는 노이즈 학습 및 완화 프로토콜의 표준 관행을 반영한 것입니다. 2-큐비트 게이트와 측정 연산은 이를 수용할 수 있는 가장 왼쪽 상자에 배치되며, 모든 1-큐비트 게이트는 그 앞에 오는 2-큐비트 게이트나 측정 연산과 같은 상자에 배치된다.
다음 코드는 enable_gates 가 로 설정된 예시를 보여줍니다 False. 보시다시피, 2큐비트 게이트는 상자로 묶여 있지 않으며, 그 앞에 오는 단일 큐비트 게이트도 마찬가지입니다.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=False,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
상자에 주석을 달 방법을 선택하세요
반환된 회로에 포함된 모든 2-큐비트 게이트와 측정 박스에는 왼쪽 방향 주석이 지정되어 있습니다. 특히, 2-큐비트 게이트가 포함된 모든 상자에는 기호가 표시되어 있습니다 Twirl. 측정 상자의 경우, 사용자는 Twirl, BasisTranform (이 로 mode 설정된 경우 "measure") 또는 둘 다 중에서 선택할 수 있습니다. 다음 코드는 모든 상자가 회전된 회로를 생성하며, 측정 상자에는 추가로 표시가 붙습니다 BasisTranform.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)노이즈 주입을 위해 회로를 준비하십시오
이 기능을 사용하면 inject_noise_targets 어떤 상자에 주석을 InjectNoise 달지 지정할 수 있습니다. 예를 들어, 다음 코드는 두 큐비트 게이트 상자에는 InjectNoise 주석이 있지만 측정 상자에는 주석이 없는 회로를 생성합니다.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)회로에 단일 큐비트 게이트 수준에서 동등한 두 개 이상의 박스가 포함되어 있다면, 이들 모두에는 동일한 InjectNoise 주석이 달립니다 ref. 따라서, 반환된 회로에서 고유한 ref‘s’의 개수는 고유한 상자의 개수와 같다.
의 값은 InjectNoise 주석에 영향을 inject_noise_strategy 미칩니다. 가능한 값은 다음과 같습니다.
modifier_ref='': 노이즈 맵에서 샘플링을 수행하기 전에 맵을 수정할 필요가 없는 경우에 권장됩니다.modifier_ref=ref: 모든 노이즈 맵을 동일한 배율로 균일하게 확대·축소해야 할 때 권장됩니다.- :의 고유한 값
modifier_ref: 모든 노이즈 맵을 서로 다른 배율로 조정해야 할 때 권장됩니다.
다음 코드는 두 큐비트 게이트 상자에 고유한 값을 modifier_ref 가진 주석이 InjectNoise 부여된 회로를 생성합니다.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)회전 전략 선택하기
복싱 패스 관리자는 먼저 어떤 얽힘 게이트와 측정 지침이 동일한 상자에 표시되어야 하는지 선택하는 것으로 시작합니다. 이 작업이 완료되면, 박스는 일반적으로 전체 너비를 차지하지 않게 되며, 회로에서 활성 상태이거나 백엔드 대상에 존재하는 큐비트 중 일부만 포함하게 될 것입니다. 복싱 패스 매니저의 일반적인 구성에서, 박스 명령어가 작용하는 모든 큐비트는 트위링 처리되므로(박스 내부의 명령어가 작용하지 않는 큐비트조차도 포함), 부분 폭 박스를 사용하면 트위링의 노이즈 조정 효과에 영향을 미칩니다. 유휴 상태의 큐비트는 회전하지 않으며, 예를 들어 박스 효과가 적용되는 동안 코히어런트 오류가 누적될 수 있다. 이 기능은 각 상자가 보유한 큐비트를 확장할 수 있는 옵션을 제공하여 twirling_strategy , 유휴 큐비트의 수를 조정할 수 있게 합니다:
"active"(기본값)은 유휴 큐비트까지 박스를 확장하지 않습니다."active_accum"회로 내의 이전 명령에 의해 이미 연산이 수행된 큐비트들로 모든 상자를 확장합니다. 이는 회로 시작 부분의 큐비트를 활성화될 때까지 회전시키지 않음으로써, 가능한 한 오랫동안 기저 상태에 머물게 하고 싶을 때 유용합니다."active_circuit"회로 내의 어떤 명령어에 의해 연산이 수행되는 큐비트들까지 모든 상자를 확장합니다. 이는 반복되는 얽힘 층으로 구성된 회로에서, 각 박스마다 노이즈를 학습해야 하므로 고유한 박스의 수를 최소화하고자 할 때 유용합니다."all"회로의 모든 양자 레지스터에 있는 모든 큐비트에 대해 모든 박스를 확장합니다. 이는 QPU 내의 모든 큐비트가 항상 트위얼링 상태를 유지하도록 하고자 할 때"active_circuit", 이에 대한 확장으로 유용합니다.
다음 예제에서는 각 전략을 기본 회로에 적용한 후, 그 결과를 도식으로 나타냅니다.
circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])
circuit.draw("mpl", scale=0.6)
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
ax = plt.subplot(2, 2, idx + 1)
pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
transpiled_circuit = pm.run(circuit)
fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
ax.set_title(f"twirling_strategy='{strategy}'")
plt.tight_layout()
장벽을 어떻게 처리할지 지정하십시오
기본적으로, 모든 얽힘 게이트와 측정 명령이 지시에 따라 상자에 담긴 후( twirling_strategy 선택으로 인한 확장 부분 포함), 가능한 경우 바로 오른쪽에 있는 단일 큐비트 게이트가 상자에 추가되기 전에 회로에서 장벽이 제거됩니다.
예를 들어, 다음 회로에는 배리어가 포함되어 있습니다:
circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()
circuit_with_barrier.draw("mpl", scale=0.8)
기본 동작 방식은 두 개의 얽힘 게이트가 같은 상자에 들어가는 것을 방지하지만, 단일 큐비트 게이트의 경우 장벽이 있음에도 불구하고 다음에 사용 가능한 상자로 흡수되도록 허용합니다.
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
를 로 remove_barriers 설정하면 장벽이 'never' 유지되어 단일 큐비트 게이트에 제약이 가해집니다.
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
마지막으로, 장벽을 즉시 제거하면 얽힌 입자들은 더 이상 서로 다른 상자에 들어가야만 하는 제약을 받지 않게 됩니다.
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
계층화의 원리
복싱 패스 관리자는 두 가지 주요 패스를 순차적으로 실행합니다:
GroupGatesIntoBoxes— 얽혀 있는 게이트를 상자로 분류합니다. 이 단계에서는 단일 큐비트 게이트는 고려하지 않고, 오직 2큐비트 게이트만 그룹화합니다.AbsorbSingleQubitGates— 각 단일 큐비트 게이트를 그 오른쪽에서 가장 가까운 적격 상자로 흡수합니다.
계층화 과정에서, 회로 DAG를 위상 순서대로 GroupGatesIntoBoxes 순회하며, 각 2-큐비트 게이트를 큐비트가 아직 할당되지 않은 가장 이른 그룹에 배치한다. 다음과 같은 세 가지 종류의 명령어는 큐비트에 대한 현재 그룹을 비우는 구분자 역할을 하여, 해당 큐비트에 적용되는 후속 게이트들을 새로운 그룹으로 강제 이동시킵니다:
- 장벽
- 기존 상자 사용 설명서
- 수치
단일 큐비트 게이트는 구분자가 아닙니다. 즉, 그룹화에 아무런 영향을 미치지 않으며, 그룹화 AbsorbSingleQubitGates 처리는 에 맡겨집니다. 현재는 교환법칙 분석이 수행되지 않으며, 그룹화는 회로의 위상적 순서를 있는 그대로 따릅니다.
왜 권장되는 remove_barriers="after_stratification" 기본값이
기본 모드는 입니다. 이는 두 가지 장점을 모두 갖추고 있기 때문입니다 "after_stratification" :
- 장벽은 얽힘 게이트가 어떻게 그룹화되는지를 제한합니다(이 장벽들은 계층화 과정에서 구분선 역할을 합니다).
- 실행
AbsorbSingleQubitGates전에 장벽이 제거되므로, 단일 큐비트 게이트는 여전히 이전 장벽 위치들을 통과하여 가장 가까운 상자로 흡수될 수 있습니다.
대조적으로,
"never"전체 회로에 장벽을 유지함으로써, 단일 큐비트 게이트를 장벽과 상자 사이에 가두어 흡수되는 것을 막고, 회로 내에 꼬이지 않은 회전 상태를 남기게 됩니다."immediately"계층화 전에 장벽을 제거하므로, 그룹핑에 아무런 영향을 미치지 않습니다."finally"이는 출력 회로에 장벽을 설정하고자 할 때(예: 시각화를 위해) 유용하지만, 장벽과 박스 사이의 단일 큐비트 게이트는 흡수되지 않습니다.
배리어형 박스 ISA 회로
반복적인 구조를 가진 회로 — 예를 들어 트로터화 해밀토니안 시뮬레이션, 변분적 가정, 오차 증폭 시퀀스 등 — 은 일반적으로 프로그래밍 방식으로 구축된 후 ISA 형식으로 변환됩니다. 트랜스파일된 회로에 상자를 수동으로 추가하는 것은 번거로우므로, 일반적인 작업 흐름은 다음과 같습니다:
- 상자 경계가 되도록 원하는 층 경계에 장벽을 삽입하여 논리 회로를 구성하십시오.
- ISA 회로로 변환합니다. Qiskit의 트랜스파일러는 라우팅 및 최적화 과정을 거치면서도 배리어를 유지합니다.
- ISA 서킷에서
generate_boxing_pass_manager()경주를 펼치다.
한 층의 끝 부분이 모든 큐비트를 덮지 못할 때는 배리어가 필요합니다. 그렇지 않으면 탐욕적 알고리즘은 다음 계층의 게이트를—그 “여유” 큐비트들에—같은 그룹에 배치하게 될 것입니다. 구체적인 예로, 5-큐비트 링에서 수행되는 2단계 하이젠베르크 ZZ 시뮬레이션을 생각해 보자. 트로터 단계마다 세 가지 결합 층이 있습니다: 짝수 결합 (0,1), (2,3), 홀수 결합 (1,2), (3,4), 그리고 랩 결합입니다 (4,0). 홀수 결합 층이 지나간 후, 큐비트 0과 4는 모두 자유 상태가 된다. 장벽이 없으면, 랩 본드는 해당 자유 큐비트를 사용하는 다음 트로터 단계의 결합들과 탐욕적 그룹화(greedy grouping)를 (4,0) 형성하게 되어, 같은 상자 안에 서로 다른 단계의 혼합 게이트가 섞이게 됩니다.
from qiskit.circuit import Parameter, QuantumCircuit
n = 5 # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)
for step in range(2):
# Layer 1: even bonds (0,1) and (2,3)
for idx in range(0, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
for idx in range(1, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
heisenberg.rzz(dt, n - 1, 0)
if step < 1:
heisenberg.barrier() # separates the two Trotter steps
heisenberg.measure_all()
heisenberg.draw("mpl", scale=0.7)
다음 코드는 기본 장치의 기본 얽힘 생성기가 CX라고 가정하고, 회로를 ISA 형식으로 변환합니다. 이 장벽들은 트랜스필레이션 과정을 거친 후에도 유지되며, 출력 회로에서 계속 표시된다는 점에 유의하십시오.
from qiskit.transpiler import generate_preset_pass_manager
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)
다음으로, ISA 회로에 복싱 패스 관리자를 적용합니다. 세 개의 본드 층은 각각 트로터 단계마다 별도의 상자로 처리됩니다.
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
Samplomatic의 패스 관리자를 Qiskit 프리셋 패스 관리자에 통합합니다
아래 코드는 Qiskit 프리셋 패스 매니저에 generate_boxing_pass_manager() 를 통합하는 방법을 보여줍니다. 트랜스파일된 회로는 ISA이며 박스들을 포함하고 있습니다.
from qiskit.transpiler import generate_preset_pass_manager
preset_pass_manager = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2]],
optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()
# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()
transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
이 패턴은 장벽 기반의 ISA 박싱 워크플로우와도 자연스럽게 결합됩니다. 복싱 패스 관리자는 원래 논리 회로의 배리어가 트랜스필레이션을 성공적으로 통과한 후, 스케줄링 후 단계로 실행되므로, 예상대로 계층화를 안내합니다.
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()
boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)
주석 확인 및 수정
자동 박스 지정 후에는 특정 박스에 적용된 주석을 확인하거나 조정하고 싶을 수 있습니다. 예를 들어, 노이즈 주입을 추가하거나, 주석을 제거하거나, 회전 그룹을 변경하는 경우 등입니다. Samplomatic은 이러한 목적을 위해 samplomatic.utils 일련의 유틸리티 함수를 제공합니다. 이들 모두 원본을 수정하지 않고 새로운 회로를 반환합니다.
extend_annotations() 회로 내의 모든 박스에 하나 이상의 주석을 추가합니다.
from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations
boxed = generate_boxing_pass_manager().run(circuit_with_barrier)
# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)
filter_annotations() 지정된 유형의 주석만 남기고 나머지는 제거합니다. replace_annotations() 각 어노테이션에 호출 가능 객체를 적용합니다. 이 호출 가능 객체는 해당 위치에 대체할 어노테이션 목록을 반환합니다(제거하려면 [] return을, 대체하려면 단일 요소 목록을, 확장하려면 다중 요소 목록을 반환합니다).
from samplomatic.utils import filter_annotations, replace_annotations
# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)
# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)이 범용 map_annotations() 함수는 주석 목록을 새로운 목록으로 매핑하는 모든 호출 가능 객체를 받아들이며, 이를 통해 상자별 변환을 완전히 제어할 수 있습니다. strip_annotations() 모든 상자에서 모든 어노테이션을 제거하는 편리한 래퍼입니다.
모든 게이트 박스에 InjectNoise 를 추가하는 것과 같은 대량 변경의 경우, 의 매개변수가 inject_noise_targets 가장 간결한 방법입니다 generate_boxing_pass_manager() . 이러한 유틸리티는 패스 관리자 매개변수로 처리할 수 없는, 박스별 선택적 편집 작업에 유용합니다.
회로 만들기
에서 생성된 모든 패스 매니저는 에 의해 템플릿/샘플렉스 쌍으로 성공적으로 변환될 수 있는 회로들을 반환합니다 generate_boxing_pass_manager()build() . 예를 들어, 다음 코드는 복싱 패스 관리자가 생성한 회로에 대해 해당 함수를 build() 호출합니다.
from samplomatic import build
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
template, samplex = build(transpiled_circuit)트랜스파일된 모든 회로가 성공적으로 빌드될 수 있도록, 패스 관리자는 필요할 때 추가 박스를 포함시키는 방법을 알고 있습니다. 예를 들어, 측정되지 않은 큐비트로 끝나는 아래 회로를 생각해 보자 0.
circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))
circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)
게이트와 측정값 주위에 왼쪽 방향 상자를 그릴 경우 0, 큐비트에 수집되지 않은 가상 게이트가 포함된 회로가 생성되며, 이 회로에 대해 build() 를 호출하면 오류가 발생합니다. 이를 방지하기 위해, 에서 반환된 패스 관리자들은 수집기 역할을 하는 오른쪽 정렬 상자를 추가할 generate_boxing_pass_manager() 수 있습니다. 예를 들어, 다음 코드에서 qubit은 수집되지 않은 가상 게이트를 포착하는 오른쪽 방향 상자로 종료됩니다 0 . 결과 회로의 깊이를 최소화하기 위해, 큐비트 에 작용하는 단일 큐비트 게이트들도 상자 안에 배치됩니다 0 .
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)
또 다른 예로, 박스화되지 않은 측정값으로 인해 수집되지 않을 수 있는 가상 게이트를 수집하기 위해 ‘right-dressed’ 박스가 추가됩니다.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)