동적 회로를 이용한 킥 이징 해밀토니안 시뮬레이션
사용량 추정: Heron r3 프로세서 기준 7.5 분 소요. (참고: 이는 추정치일 뿐입니다.) 실행 시간은 다를 수 있습니다.)
동적 회로는 고전적 피드포워드 특성을 지닌 회로로, 즉 중간 회로 측정을 수행한 후 고전적 논리 연산을 통해 고전적 출력에 조건부로 양자 연산을 결정하는 회로를 의미한다. 이 튜토리얼에서는 육각 격자 위의 스핀에 대해 킥드 아이징 모델을 시뮬레이션하고, 하드웨어의 물리적 연결성을 넘어선 상호작용을 구현하기 위해 동적 회로를 사용합니다.
이징 모델은 물리학의 다양한 분야에서 광범위하게 연구되어 왔다. 이 모델은 격자 사이트 간 이징 상호작용을 겪는 스핀과 각 사이트의 국소 자기장에 의한 충격력을 모두 고려합니다. 본 튜토리얼에서 고려된 스핀의 트로터화 시간 진화는 [1] 에서 인용한 바와 같이 다음 단위 행렬로 주어진다:
스핀 역학을 탐구하기 위해, 우리는 각 사이트에서 스핀의 평균 자화도를 트로터 단계의 함수로 연구한다. 따라서 우리는 다음과 같은 관측량을 구성한다:
격자 사이트 간 ZZ 상호작용을 구현하기 위해, 동적 회로 기능을 활용한 해법을 제시한다. 이는 SWAP 게이트를 사용하는 표준 라우팅 방식에 비해 두 큐비트 깊이를 현저히 단축시킨다. 반면, 동적 회로에서 고전적인 피드포워드 연산은 일반적으로 양자 게이트보다 실행 시간이 더 길다. 따라서 동적 회로는 한계와 장단점이 존재한다. 또한 스트레치 지속 시간을 활용하여 클래식 피드포워드 연산 중 유휴 큐비트에 동적 디커플링 시퀀스를 추가하는 방법을 제시한다.
요구사항
이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하십시오:
- Qiskit SDK v2.0 또는 이후 버전에서 시각화 지원이 제공됩니다
- Qiskit Runtime v0.37 또는 이후 버전에서 시각화 지원 기능이 제공됩니다 (
pip install 'qiskit-ibm-runtime[visualization]') - Rustworkx 그래프 라이브러리 (
pip install rustworkx) - Qiskit Aer (
pip install qiskit-aer)
설정
import numpy as np
from typing import List
import rustworkx as rx
import matplotlib.pyplot as plt
from rustworkx.visualization import mpl_draw
from qiskit.circuit import (
Parameter,
QuantumCircuit,
QuantumRegister,
ClassicalRegister,
)
from qiskit.transpiler import CouplingMap
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.classical import expr
from qiskit.transpiler.preset_passmanagers import (
generate_preset_pass_manager,
)
from qiskit.transpiler import PassManager
from qiskit.circuit.library import RZGate, XGate
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
)
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.circuit.measure import Measure
from qiskit.transpiler.passes.utils.remove_final_measurements import (
calc_final_ops,
)
from qiskit.circuit import Instruction
from qiskit.visualization import plot_circuit_layout
from qiskit.circuit.tools import pi_check
from qiskit_aer import AerSimulator
from qiskit_aer.primitives import SamplerV2 as Aer_Sampler
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit_ibm_runtime.visualization import (
draw_circuit_schedule_timing,
)1단계: 고전적 입력을 양자 회로에 매핑하기
시뮬레이션할 격자를 정의하는 것으로 시작합니다. 우리는 정육각형 격자(벌집 격자라고도 함)를 선택하여 작업합니다. 이는 각 정점의 차수가 3인 평면 그래프입니다. 여기서 우리는 격자의 크기와 Trotterized 역학에서 관심 있는 관련 회로 매개변수를 지정합니다. 우리는 이징 모델 하에서 세 가지 서로 다른 국소 자기장 값( )에 대해 트로터화 시간 진화를 시뮬레이션한다.
hex_rows = 3 # specify lattice size
hex_cols = 5
depths = range(9) # specify Trotter steps
zz_angle = np.pi / 8 # parameter for ZZ interaction
max_angle = np.pi / 2 # max theta angle
points = 3 # number of theta parameters
θ = Parameter("θ")
params = np.linspace(0, max_angle, points)def make_hex_lattice(hex_rows=1, hex_cols=1):
"""Define hexagon lattice."""
hex_cmap = CouplingMap.from_hexagonal_lattice(
hex_rows, hex_cols, bidirectional=False
)
data = list(hex_cmap.physical_qubits)
graph = hex_cmap.graph.to_undirected(multigraph=False)
edge_colors = rx.graph_misra_gries_edge_color(graph)
layer_edges = {color: [] for color in edge_colors.values()}
for edge_index, color in edge_colors.items():
layer_edges[color].append(graph.edge_list()[edge_index])
return data, layer_edges, hex_cmap, graph작은 테스트 예제로 시작해 보겠습니다:
hex_rows_test = 1
hex_cols_test = 2
data_test, layer_edges_test, hex_cmap_test, graph_test = make_hex_lattice(
hex_rows=hex_rows_test, hex_cols=hex_cols_test
)
# display a small example for illustration
node_colors_test = ["lightblue"] * len(graph_test.node_indices())
pos = rx.graph_spring_layout(
graph_test,
k=5 / np.sqrt(len(graph_test.nodes())),
repulsive_exponent=1,
num_iter=150,
)
mpl_draw(graph_test, node_color=node_colors_test, pos=pos)Output:
우리는 설명과 시뮬레이션을 위해 작은 예시를 사용할 것입니다. 아래에서는 워크플로우가 대규모로 확장될 수 있음을 보여주기 위해 대규모 예제도 구성합니다.
data, layer_edges, hex_cmap, graph = make_hex_lattice(
hex_rows=hex_rows, hex_cols=hex_cols
)
num_qubits = len(data)
print(f"num_qubits = {num_qubits}")
# display the honeycomb lattice to simulate
node_colors = ["lightblue"] * len(graph.node_indices())
pos = rx.graph_spring_layout(
graph,
k=5 / np.sqrt(num_qubits),
repulsive_exponent=1,
num_iter=150,
)
mpl_draw(graph, node_color=node_colors, pos=pos)
plt.show()Output:
num_qubits = 46
단일 회로 구축
문제 규모와 매개변수가 지정되었으므로, 이제 인수를 depth 통해 지정된 다양한 Trotter 단계로 의 Trotter화된 시간 진화를 시뮬레이션하는 매개변수화된 회로를 구축할 준비가 되었습니다. 우리가 구축하는 회로는 ( Rx ) 게이트와 Rzz 게이트가 교대로 쌓인 층으로 구성됩니다. 게이트는 Rzz 결합된 스핀들 간의 ZZ 상호작용을 구현하며, 이는 인자로 layer_edges 지정된 각 격자 사이트 사이에 배치될 것이다.
def gen_hex_unitary(
num_qubits=6,
zz_angle=np.pi / 8,
layer_edges=[
[(0, 1), (2, 3), (4, 5)],
[(1, 2), (3, 4), (5, 0)],
],
θ=Parameter("θ"),
depth=1,
measure=False,
final_rot=True,
):
"""Build unitary circuit."""
circuit = QuantumCircuit(num_qubits)
# Build trotter layers
for _ in range(depth):
for i in range(num_qubits):
circuit.rx(θ, i)
circuit.barrier()
for coloring in layer_edges.keys():
for e in layer_edges[coloring]:
circuit.rzz(zz_angle, e[0], e[1])
circuit.barrier()
# Optional final rotation, set True to be consistent with Ref. [1]
if final_rot:
for i in range(num_qubits):
circuit.rx(θ, i)
if measure:
circuit.measure_all()
return circuit작은 테스트 회로를 시각화하십시오:
circ_unitary_test = gen_hex_unitary(
num_qubits=len(data_test),
layer_edges=layer_edges_test,
θ=Parameter("θ"),
depth=1,
measure=True,
)
circ_unitary_test.draw(output="mpl", fold=-1)Output:
마찬가지로, 대규모 예제의 단일 회로를 서로 다른 Trotter 단계에서 구성하고, 기대값을 추정하기 위한 관측량을 생성하십시오.
circuits_unitary = []
for depth in depths:
circ = gen_hex_unitary(
num_qubits=num_qubits,
layer_edges=layer_edges,
θ=Parameter("θ"),
depth=depth,
measure=True,
)
circuits_unitary.append(circ)observables_unitary = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits=num_qubits,
)동적 회로 구현 구축
이 섹션에서는 동일한 Trotter화된 시간 진화를 시뮬레이션하기 위한 주요 동적 회로 구현을 보여줍니다. 우리가 시뮬레이션하고자 하는 벌집 격자는 하드웨어 큐비트의 중격자와 일치하지 않음을 유의하십시오. 회로를 하드웨어에 매핑하는 간단한 방법 중 하나는 상호작용하는 큐비트를 서로 인접하게 배치하기 위해 일련의 SWAP 연산을 도입하여 ZZ 상호작용을 구현하는 것이다. 여기서 우리는 동적 회로를 활용한 대안적 접근법을 제시하며, Qiskit 내 회로에서 양자 및 실시간 고전적 계산을 결합하여 근접 이웃을 넘어선 상호작용을 구현할 수 있음을 보여줍니다.
동적 회로 구현에서 ZZ 상호작용은 보조 큐비트, 중간 회로 측정 및 피드포워드를 사용하여 효과적으로 구현된다. 이를 이해하려면, ZZ 회전이 상태의 패리티에 따라 위상 인자 를 적용한다는 점을 유의하십시오. 2큐비트의 경우, 계산 기저 상태는 다음과 같다: , , , . ZZ 회전 게이트는 상태 및 에 위상 인자를 적용하는데, 이 상태들의 패리티(상태 내 1의 개수)가 홀수일 때만 적용되며, 짝수 패리티 상태는 그대로 유지된다. 다음은 동적 회로를 사용하여 두 큐비트에 대한 ZZ 상호작용을 효과적으로 구현하는 방법을 설명합니다.
-
패리티를 보조 큐비트로 계산: 두 큐비트에 직접 ZZ 연산을 적용하는 대신, 세 번째 큐비트인 보조 큐비트를 도입하여 두 데이터 큐비트의 패리티 정보를 저장합니다. 데이터 큐비트에서 보조 큐비트로 CX 게이트를 사용하여 각 데이터 큐비트와 보조 큐비트를 얽힌다.
-
보조 큐비트에 단일 큐비트 Z 회전을 적용한다: 이는 보조 큐비트가 두 데이터 큐비트의 패리티 정보를 가지며, 이는 데이터 큐비트에 ZZ 회전을 효과적으로 구현하기 때문이다.
-
X 기저에서 보조 큐비트를 측정한다: 이는 보조 큐비트의 상태를 붕괴시키는 핵심 단계이며, 측정 결과는 무슨 일이 일어났는지 알려준다:
-
측정 0: 0 결과가 관측될 때, 우리는 실제로 데이터 큐비트에 0-1 회전( )을 올바르게 적용한 것이다.
-
조치 1: 결과 1이 관찰될 경우, 우리는 대신 를 적용했습니다.
-
-
측정 시 보정 게이트 적용: 1을 측정했을 경우, 데이터 큐비트에 Z 게이트를 적용하여 추가적인 위상을 "수정"합니다.
결과 회로는 다음과 같습니다:
이 접근법을 채택하여 벌집 격자를 시뮬레이션할 때, 결과 회로는 중헥스 격자(heavy-hex lattice)를 가진 하드웨어에 완벽하게 내장됩니다: 모든 데이터 큐비트는 격자의 6중점( degree-3 ) 사이트에 위치하며, 이는 육각형 격자를 형성합니다. 모든 데이터 큐비트 쌍은 보조 큐비트를 공유하며, 이 보조 큐비트는 보조 큐비트 사이트( degree-2 )에 위치한다. 아래에서는 동적 회로 구현을 위한 큐비트 격자를 구성하며, 보조 큐비트(더 진한 보라색 원으로 표시)를 도입합니다.
def make_lattice(hex_rows=1, hex_cols=1):
"""Define heavy-hex lattice and corresponding lists of data and ancilla nodes."""
hex_cmap = CouplingMap.from_hexagonal_lattice(
hex_rows, hex_cols, bidirectional=False
)
data = list(hex_cmap.physical_qubits)
heavyhex_cmap = CouplingMap()
for d in data:
heavyhex_cmap.add_physical_qubit(d)
# make coupling map
a = len(data)
for edge in hex_cmap.get_edges():
heavyhex_cmap.add_physical_qubit(a)
heavyhex_cmap.add_edge(edge[0], a)
heavyhex_cmap.add_edge(edge[1], a)
a += 1
ancilla = list(range(len(data), a))
qubits = data + ancilla
# color edges
graph = heavyhex_cmap.graph.to_undirected(multigraph=False)
edge_colors = rx.graph_misra_gries_edge_color(graph)
layer_edges = {color: [] for color in edge_colors.values()}
for edge_index, color in edge_colors.items():
layer_edges[color].append(graph.edge_list()[edge_index])
# construct observable
obs_hex = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / len(data)) for i in data],
num_qubits=len(qubits),
)
return (data, qubits, ancilla, layer_edges, heavyhex_cmap, graph, obs_hex)데이터 큐비트와 보조 큐비트를 위한 중육각 격자를 소규모로 시각화하십시오:
(data, qubits, ancilla, layer_edges, heavyhex_cmap, graph, obs_hex) = (
make_lattice(hex_rows=hex_rows, hex_cols=hex_cols)
)
print(f"number of data qubits = {len(data)}")
print(f"number of ancilla qubits = {len(ancilla)}")
node_colors = []
for node in graph.node_indices():
if node in ancilla:
node_colors.append("purple")
else:
node_colors.append("lightblue")
pos = rx.graph_spring_layout(
graph,
k=1 / np.sqrt(len(qubits)),
repulsive_exponent=2,
num_iter=200,
)
# Visualize the graph, blue circles are data qubits and purple circles are ancillas
mpl_draw(graph, node_color=node_colors, pos=pos)
plt.show()Output:
number of data qubits = 46
number of ancilla qubits = 60
아래에서, 우리는 Trotterized 시간 진화를 위한 동적 회로를 구성한다. 위에서 설명한 단계를 사용하여 RZZ 게이트를 동적 회로 구현으로 대체합니다.
def gen_hex_dynamic(
depth=1,
zz_angle=np.pi / 8,
θ=Parameter("θ"),
hex_rows=1,
hex_cols=1,
measure=False,
add_dd=True,
):
"""Build dynamic circuits."""
(data, qubits, ancilla, layer_edges, heavyhex_cmap, graph, obs_hex) = (
make_lattice(hex_rows=hex_rows, hex_cols=hex_cols)
)
# Initialize circuit
qr = QuantumRegister(len(qubits), "qr")
cr = ClassicalRegister(len(ancilla), "cr")
circuit = QuantumCircuit(qr, cr)
for k in range(depth):
# Single-qubit Rx layer
for d in data:
circuit.rx(θ, d)
circuit.barrier()
# CX gates from data qubits to ancilla qubits
for same_color_edges in layer_edges.values():
for e in same_color_edges:
circuit.cx(e[0], e[1])
circuit.barrier()
# Apply Rz rotation on ancilla qubits and rotate into X basis
for a in ancilla:
circuit.rz(zz_angle, a)
circuit.h(a)
# Add barrier to align terminal measurement
circuit.barrier()
# Measure ancilla qubits
for i, a in enumerate(ancilla):
circuit.measure(a, i)
d2ros = {}
a2ro = {}
# Retrieve ancilla measurement outcomes
for a in ancilla:
a2ro[a] = cr[ancilla.index(a)]
# For each data qubit, retrieve measurement outcomes of neighboring
# ancilla qubits
for d in data:
ros = [a2ro[a] for a in heavyhex_cmap.neighbors(d)]
d2ros[d] = ros
# Build classical feedforward operations (optionally add DD on idling
# data qubits)
for d in data:
if add_dd:
circuit = add_stretch_dd(circuit, d, f"data_{d}_depth_{k}")
# # XOR the neighboring readouts of the data qubit;
# if True, apply Z to it
ros = d2ros[d]
parity = ros[0]
for ro in ros[1:]:
parity = expr.bit_xor(parity, ro)
with circuit.if_test(expr.equal(parity, True)):
circuit.z(d)
# Reset the ancilla if its readout is 1
for a in ancilla:
with circuit.if_test(expr.equal(a2ro[a], True)):
circuit.x(a)
circuit.barrier()
# Final single-qubit Rx layer to match the unitary circuits
for d in data:
circuit.rx(θ, d)
if measure:
circuit.measure_all()
return circuit, obs_hex
def add_stretch_dd(qc, q, name):
"""Add XpXm DD sequence."""
s = qc.add_stretch(name)
qc.delay(s, q)
qc.x(q)
qc.delay(s, q)
qc.delay(s, q)
qc.rz(np.pi, q)
qc.x(q)
qc.rz(-np.pi, q)
qc.delay(s, q)
return qc동적 분리(DD) 및 지속 시간 stretch 지원
동적 회로 구현을 사용하여 ZZ 상호작용을 구현할 때 한 가지 주의할 점은 중간 회로 측정과 고전적 피드포워드 연산이 일반적으로 양자 게이트보다 실행 시간이 더 오래 걸린다는 것이다. 클래식 연산이 수행되는 대기 시간 동안 큐비트의 디코히런스를 억제하기 위해, 보조 큐비트에 대한 측정 연산 이후, 데이터 큐비트에 대한 조건부 Z 연산 이전, 즉 if_test 명령어 앞에 동적 디커플링 (DD) 시퀀스를 추가하였다.
DD 시퀀스는 함수를 통해 추가되며 add_stretch_dd(), 이 함수는 지속 stretch 시간을 사용하여 DD 게이트 간의 시간 간격을 결정합니다. ‘지속 stretch 시간’이란, 지연 시간이 큐비트의 유휴 시간을 모두 채울 수 있도록 delay 작업에 대해 유연하게 조정 가능한 시간 구간을 지정하는 방법입니다. 에 의해 지정된 지속 시간 변수들은 컴파일 시점에 특정 제약 조건을 충족하는 원하는 지속 시간으로 변환됩니다 stretch . 이는 DD 시퀀스의 타이밍이 우수한 오차 억제 성능을 달성하는 데 필수적인 경우에 매우 유용합니다. 이 stretch 타입에 대한 자세한 내용은 OpenQASM 문서를 참조하십시오. 현재 이 stretch 유형에 대한 지원은 실험적 단계입니다. 사용 제한 사항에 대한 자세한 내용은 stretch 문서의 ‘제한 사항’ 섹션을 참조하십시오.
위에서 정의된 함수를 사용하여, DD가 포함된 경우와 포함되지 않은 경우의 Trotterized 시간 진화 회로와 이에 대응하는 관측량을 구축한다.
우리는 작은 예제의 동적 회로를 시각화하는 것으로 시작합니다:
hex_rows_test = 1
hex_cols_test = 1
(
data_test,
qubits_test,
ancilla_test,
layer_edges_test,
heavyhex_cmap_test,
graph_test,
obs_hex_test,
) = make_lattice(hex_rows=hex_rows_test, hex_cols=hex_cols_test)
node_colors = []
for node in graph_test.node_indices():
if node in ancilla_test:
node_colors.append("purple")
else:
node_colors.append("lightblue")
pos = rx.graph_spring_layout(
graph_test,
k=5 / np.sqrt(len(qubits_test)),
repulsive_exponent=2,
num_iter=150,
)
# display a small example for illustration
node_colors_test = ["lightblue"] * len(graph_test.node_indices())
mpl_draw(graph_test, node_color=node_colors, pos=pos)Output:
circuit_dynamic_test, obs_dynamic_test = gen_hex_dynamic(
depth=1,
θ=Parameter("θ"),
hex_rows=hex_rows_test,
hex_cols=hex_cols_test,
measure=False,
add_dd=False,
)
circuit_dynamic_test.draw("mpl", fold=-1)Output:
circuit_dynamic_dd_test, _ = gen_hex_dynamic(
depth=1,
θ=Parameter("θ"),
hex_rows=hex_rows_test,
hex_cols=hex_cols_test,
measure=False,
add_dd=True,
)
circuit_dynamic_dd_test.draw("mpl", fold=-1)Output:
마찬가지로, 대규모 예제에 대한 동적 회로를 구성하십시오:
circuits_dynamic = []
circuits_dynamic_dd = []
observables_dynamic = []
for depth in depths:
circuit, obs = gen_hex_dynamic(
depth=depth,
θ=Parameter("θ"),
hex_rows=hex_rows,
hex_cols=hex_cols,
measure=True,
add_dd=False,
)
circuits_dynamic.append(circuit)
circuit_dd, _ = gen_hex_dynamic(
depth=depth,
θ=Parameter("θ"),
hex_rows=hex_rows,
hex_cols=hex_cols,
measure=True,
add_dd=True,
)
circuits_dynamic_dd.append(circuit_dd)
observables_dynamic.append(obs)2단계: 하드웨어 실행을 위한 문제 최적화
이제 회로를 하드웨어로 트랜스파일할 준비가 되었습니다. 단일 표준 구현과 동적 회로 구현을 모두 하드웨어로 트랜스파일할 것입니다.
하드웨어로 트랜스파일하기 위해 먼저 백엔드를 인스턴스화합니다. 가능하다면, (measure_2) MidCircuitMeasure 명령어가 지원되는 백엔드를 선택할 것입니다.
service = QiskitRuntimeService()
try:
backend = service.least_busy(
operational=True,
simulator=False,
use_fractional_gates=True,
filters=lambda b: "measure_2" in b.supported_instructions,
)
except QiskitBackendNotFoundError:
backend = service.least_busy(
operational=True,
simulator=False,
use_fractional_gates=True,
)동적 회로용 트랜스파일레이션
먼저, DD 시퀀스를 추가하는 경우와 추가하지 않는 경우 모두 동적 회로를 트랜스파일합니다. 일관된 결과를 위해 모든 회로에서 동일한 물리적 큐비트 세트를 사용하도록 보장하기 위해, 먼저 회로를 한 번 트랜스파일한 후, 패스 매니저에서 initial_layout 지정된 모든 후속 회로에 해당 레이아웃을 사용합니다. 그런 다음 샘플러 프리미티브 입력으로 원시 통합 블록 (PUBs)을 구성합니다.
pm_temp = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
)
isa_temp = pm_temp.run(circuits_dynamic[-1])
dynamic_layout = isa_temp.layout.initial_index_layout(filter_ancillas=True)
pm = generate_preset_pass_manager(
optimization_level=3, backend=backend, initial_layout=dynamic_layout
)
dynamic_isa_circuits = [pm.run(circ) for circ in circuits_dynamic]
dynamic_pubs = [(circ, params) for circ in dynamic_isa_circuits]
dynamic_isa_circuits_dd = [pm.run(circ) for circ in circuits_dynamic_dd]
dynamic_pubs_dd = [(circ, params) for circ in dynamic_isa_circuits_dd]아래에 트랜스파일된 회로의 큐비트 배치를 시각화할 수 있습니다. 검은색 원은 동적 회로 구현에 사용된 데이터 큐비트와 보조 큐비트를 나타냅니다.
def _heron_coords_r2():
cord_map = np.array(
[
[
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
],
-1
* np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),
],
dtype=int,
)
hcords = []
ycords = cord_map[0]
xcords = cord_map[1]
for i in range(156):
hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])
return hcordsplot_circuit_layout(
dynamic_isa_circuits_dd[8],
backend,
qubit_coordinates=_heron_coords_r2(),
view="virtual",
)Output:
에서 파일을 찾을 수 neato 없다는 plot_circuit_layout() 오류가 발생한다면, graphviz 패키지가 설치되어 있고 PATH에 포함되어 있는지 확인하십시오. 기본 위치가 아닌 다른 위치(예: MacOS 사용)에 설치하는 homebrew 경우, PATH 환경 PATH 변수를 업데이트해야 할 수 있습니다. 이 작업은 이 노트북 내에서 다음을 사용하여 수행할 수 있습니다:
import os
os.environ['PATH'] = f"path/to/neato{os.pathsep}{os.environ['PATH']}"dynamic_isa_circuits[1].draw(fold=-1, output="mpl", idle_wires=False)Output:
dynamic_isa_circuits_dd[1].draw(fold=-1, output="mpl", idle_wires=False)Output:
다음 명령어로 트랜스파일하기 MidCircuitMeasure
MidCircuitMeasure 이는 기존 측정 기능에 추가된 것으로, 회로 중간 지점 측정을 수행하도록 특별히 보정되었습니다. 이 MidCircuitMeasure 명령어는 백엔드에서 지원하는 measure_2 명령어와 대응됩니다. 모든 백엔드에서 지원되는 measure_2 것은 아닙니다. 를 사용하여 이를 service.backends(filters=lambda b: "measure_2" in b.supported_instructions) 지원하는 백엔드를 찾을 수 있습니다. 여기서는 백엔드가 해당 연산을 지원하는 경우, 회로 내에 정의된 중간 회로 측정값이 해당 MidCircuitMeasure 연산을 사용하여 실행되도록 회로를 트랜스파일하는 방법을 설명합니다.
아래에는 명령어와 표준 measure measure_2 명령어의 실행 시간을 출력합니다.
print(
f'Mid-circuit measurement `measure_2` duration: '
f"{backend.instruction_durations.get('measure_2',0) * backend.dt * 1e9/1e3} μs"
)
print(
f'Terminal measurement `measure` duration: '
f"{backend.instruction_durations.get('measure',0) * backend.dt *1e9/1e3} μs"
)Output:
Mid-circuit measurement `measure_2` duration: 1.3800000000000003 μs
Terminal measurement `measure` duration: 2.1800000000000006 μs
"""Pass that replaces terminal measures in the middle of the circuit with
MidCircuitMeasure instructions."""
class ConvertToMidCircuitMeasure(TransformationPass):
"""This pass replaces terminal measures in the middle of the circuit with
MidCircuitMeasure instructions.
"""
def __init__(self, target):
super().__init__()
self.target = target
def run(self, dag):
"""Run the pass on a dag."""
mid_circ_measure = None
for inst in self.target.instructions:
if isinstance(inst[0], Instruction) and inst[0].name.startswith(
"measure_"
):
mid_circ_measure = inst[0]
break
if not mid_circ_measure:
return dag
final_measure_nodes = calc_final_ops(dag, {"measure"})
for node in dag.op_nodes(Measure):
if node not in final_measure_nodes:
dag.substitute_node(node, mid_circ_measure, inplace=True)
return dag
pm = PassManager(ConvertToMidCircuitMeasure(backend.target))
dynamic_isa_circuits_meas2 = [pm.run(circ) for circ in dynamic_isa_circuits]
dynamic_pubs_meas2 = [(circ, params) for circ in dynamic_isa_circuits_meas2]
dynamic_isa_circuits_dd_meas2 = [
pm.run(circ) for circ in dynamic_isa_circuits_dd
]
dynamic_pubs_dd_meas2 = [
(circ, params) for circ in dynamic_isa_circuits_dd_meas2
]단일 회로용 트랜스파일레이션
동적 회로와 그 단위적 대응체 간 공정한 비교를 위해, 데이터 큐비트로 동적 회로에 사용된 동일한 물리적 큐비트 집합을 단위적 회로 변환을 위한 레이아웃으로 사용한다.
init_layout = [
dynamic_layout[ind] for ind in range(circuits_unitary[0].num_qubits)
]
pm = generate_preset_pass_manager(
target=backend.target,
initial_layout=init_layout,
optimization_level=3,
)
def transpile_minimize(circ: QuantumCircuit, pm: PassManager, iterations=10):
"""Transpile circuits for specified number of iterations and return the one
with smallest two-qubit gate depth"""
circs = [pm.run(circ) for i in range(iterations)]
circs_sorted = sorted(
circs,
key=lambda x: x.depth(lambda x: x.operation.num_qubits == 2),
)
return circs_sorted[0]
unitary_isa_circuits = []
for circ in circuits_unitary:
circ_t = transpile_minimize(circ, pm, iterations=100)
unitary_isa_circuits.append(circ_t)
unitary_pubs = [(circ, params) for circ in unitary_isa_circuits]트랜스파일된 유니터리 회로의 큐비트 배치를 시각화합니다. 검은색 원은 단위 회로를 트랜스파일링하는 데 사용되는 물리적 큐비트를 나타내며, 그 인덱스는 가상 큐비트 인덱스에 대응합니다. 이를 동적 회로에 대해 플롯된 레이아웃과 비교함으로써, 단위 회로가 동적 회로의 데이터 큐비트와 동일한 물리적 큐비트 집합을 사용함을 확인할 수 있습니다.
plot_circuit_layout(
unitary_isa_circuits[-1],
backend,
qubit_coordinates=_heron_coords_r2(),
view="virtual",
)Output:
이제 트랜스파일된 회로에 DD 시퀀스를 추가하고 작업 제출을 위한 해당 PUB를 구성합니다.
pm_dd = PassManager(
[
ALAPScheduleAnalysis(target=backend.target),
PadDynamicalDecoupling(
dd_sequence=[
XGate(),
RZGate(np.pi),
XGate(),
RZGate(-np.pi),
],
spacing=[1 / 4, 1 / 2, 0, 0, 1 / 4],
target=backend.target,
),
]
)
unitary_isa_circuits_dd = pm_dd.run(unitary_isa_circuits)
unitary_pubs_dd = [(circ, params) for circ in unitary_isa_circuits_dd]유니터리 회로와 동적 회로의 2큐비트 게이트 깊이 비교
# compare circuit depth of unitary and dynamic circuit implementations
unitary_depth = [
unitary_isa_circuits[i].depth(lambda x: x.operation.num_qubits == 2)
for i in range(len(unitary_isa_circuits))
]
dynamic_depth = [
dynamic_isa_circuits[i].depth(lambda x: x.operation.num_qubits == 2)
for i in range(len(dynamic_isa_circuits))
]
plt.plot(
list(range(len(unitary_depth))),
unitary_depth,
label="unitary circuits",
color="#be95ff",
)
plt.plot(
list(range(len(dynamic_depth))),
dynamic_depth,
label="dynamic circuits",
color="#ff7eb6",
)
plt.xlabel("Trotter steps")
plt.ylabel("Two-qubit depth")
plt.legend()Output:
<matplotlib.legend.Legend at 0x12628b0e0>
측정 기반 회로의 주요 이점은 다중 ZZ 상호작용 구현 시 CX 레이어를 병렬화할 수 있으며, 측정이 동시에 수행될 수 있다는 점이다. 이는 모든 ZZ 상호작용이 교환 가능하기 때문에, 측정의 깊이가 1로 계산이 수행될 수 있기 때문이다. 회로를 트랜스파일링한 후, 동적 회로 접근법이 표준 단위적 접근법보다 훨씬 짧은 2큐비트 깊이를 산출함을 관찰한다. 다만 중간 회로 측정과 고전적 피드포워드 자체도 시간이 소요되며 그 자체로 오류 원인을 도입한다는 점을 유의해야 한다.
3단계: Qiskit primitives 명령어로 실행합니다
로컬 테스트 모드
하드웨어에 작업을 제출하기 전에 로컬 테스트 모 드를 사용하여 동적 회로의 소규모 테스트 시뮬레이션을 실행할 수 있습니다.
aer_sim = AerSimulator()
pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
circuit_dynamic_test.measure_all()
isa_qc = pm.run(circuit_dynamic_test)
with Batch(backend=aer_sim) as batch:
sampler = Sampler(mode=batch)
result = sampler.run([(isa_qc, params)]).result()
print(
"Simulated average magnetization at trotter step = 1 at three theta values"
)
result[0].data["meas"].expectation_values(obs_dynamic_test[0])Output:
Simulated average magnetization at trotter step = 1 at three theta values
array([ 0.16666667, 0.01529948, -0.14290365])
MPS 시뮬레이션
대규모 회로의 경우, 선택한 결합 차원에 따라 기대값에 대한 근사값을 제공하는 MPS(최소 다중 matrix_product_state 시뮬레이터) 시뮬레이터를 사용할 수 있습니다. 이후 MPS 시뮬레이션 결과를 기준선으로 삼아 하드웨어에서 얻은 결과와 비교합니다.
# The MPS simulation below took approximately 7 minutes to run on a
# laptop with Apple M1 chip
mps_backend = AerSimulator(
method="matrix_product_state",
matrix_product_state_truncation_threshold=1e-5,
matrix_product_state_max_bond_dimension=100,
)
mps_sampler = Aer_Sampler.from_backend(mps_backend)
shots = 4096
data_sim = []
for j in range(points):
circ_list = [
circ.assign_parameters([params[j]]) for circ in circuits_unitary
]
mps_job = mps_sampler.run(circ_list, shots=shots)
result = mps_job.result()
point_data = [
result[d].data["meas"].expectation_values(observables_unitary)
for d in depths
]
data_sim.append(point_data) # data at one theta value
data_sim = np.array(data_sim)회로와 관측 가능한 대상을 준비한 후, 이제 샘플러 프리미티브를 사용하여 하드웨어에서 이를 실행합니다.
여기서 우리는, dynamic_pubs, 및 unitary_pubs 에 대해 세 개의 dynamic_pubs_dd작업을 제출합니다. 각각은 세 가지 다른 트로터 단계( ) 매개변수에 해당하는 아홉 가지 서로 다른 트로터 단계에 대응하는 매개변수화된 회로 목록이다.
shots = 10000
with Batch(backend=backend) as batch:
sampler = Sampler(mode=batch)
sampler.options.experimental = {
"execution": {
"scheduler_timing": True
}, # set to True to retrieve circuit timing info
}
job_unitary = sampler.run(unitary_pubs, shots=shots)
print(f"unitary: {job_unitary.job_id()}")
job_unitary_dd = sampler.run(unitary_pubs_dd, shots=shots)
print(f"unitary_dd: {job_unitary_dd.job_id()}")
job_dynamic = sampler.run(dynamic_pubs, shots=shots)
print(f"dynamic: {job_dynamic.job_id()}")
job_dynamic_dd = sampler.run(dynamic_pubs_dd, shots=shots)
print(f"dynamic_dd: {job_dynamic_dd.job_id()}")
job_dynamic_meas2 = sampler.run(dynamic_pubs_meas2, shots=shots)
print(f"dynamic_meas2: {job_dynamic_meas2.job_id()}")
job_dynamic_dd_meas2 = sampler.run(dynamic_pubs_dd_meas2, shots=shots)
print(f"dynamic_dd_meas2: {job_dynamic_dd_meas2.job_id()}")Output:
unitary: d96s4b52su3c739hakrg
unitary_dd: d96s4bt2su3c739haksg
dynamic: d96s4c0tcv6s73dk55mg
dynamic_dd: d96s4ckqp3as739qvid0
dynamic_meas2: d96s4csqp3as739qvie0
dynamic_dd_meas2: d96s4daf47jc73a5v8s0
4단계: 원하는 고전적 형식으로 결과를 후처리하고 반환
작업이 완료된 후, 작업 결과 메타데이터에서 회로 실행 시간을 추출하여 회로 일정 정보를 시각화할 수 있습니다. 회로의 스케줄링 정보를 시각화하는 방법에 대해 더 자세히 알아보려면 이 페이지를 참조하십시오.
# Circuit durations is reported in the unit of `dt`
# which can be retrieved from `Backend` object
unitary_durations = [
job_unitary.result()[i].metadata["compilation"]["scheduler_timing"][
"circuit_duration"
]
for i in depths
]
dynamic_durations = [
job_dynamic.result()[i].metadata["compilation"]["scheduler_timing"][
"circuit_duration"
]
for i in depths
]
dynamic_durations_meas2 = [
job_dynamic_meas2.result()[i].metadata["compilation"]["scheduler_timing"][
"circuit_duration"
]
for i in depths
]
result_dd = job_dynamic_dd.result()[1]
circuit_schedule_dd = result_dd.metadata["compilation"]["scheduler_timing"][
"timing"
]
# to visualize the circuit schedule, one can show the figure below
fig_dd = draw_circuit_schedule_timing(
circuit_schedule=circuit_schedule_dd,
included_channels=None,
filter_readout_channels=False,
filter_barriers=False,
width=1000,
)
# Save to a file since the figure is large
fig_dd.write_html("scheduler_timing_dd.html")단위 회로와 동적 회로의 회로 지속 시간을 그래프로 나타낸다. 아래 플롯에서 볼 수 있듯이, 중간 회로 측정 및 고전적 연산에 소요되는 시간에도 불구하고, 동적 회로 구현은 단위성 구현과 measure_2 유사한 회로 지속 시간을 보여준다.
# visualize circuit durations
def convert_dt_to_microseconds(circ_duration: List, backend_dt: float):
dt = backend_dt * 1e6 # dt in microseconds
return list(map(lambda x: x * dt, circ_duration))
dt = backend.target.dt
plt.plot(
depths,
convert_dt_to_microseconds(unitary_durations, dt),
color="#be95ff",
linestyle=":",
label="unitary",
)
plt.plot(
depths,
convert_dt_to_microseconds(dynamic_durations, dt),
color="#ff7eb6",
linestyle="-.",
label="dynamic",
)
plt.plot(
depths,
convert_dt_to_microseconds(dynamic_durations_meas2, dt),
color="#ff7eb6",
linestyle="-.",
marker="s",
mfc="none",
label="dynamic w/ meas2",
)
plt.xlabel("Trotter steps")
plt.ylabel(r"Circuit durations in $\mu$s")
plt.legend()Output:
<matplotlib.legend.Legend at 0x12bfde270>
작업이 완료된 후, 아래 데이터를 추출하여 이전에 구축한 관측량 observables_unitary``observables_dynamic 또는 에 의해 추정된 평균 자화량을 계산합니다.
runs = {
"unitary": (
job_unitary,
[observables_unitary] * len(circuits_unitary),
),
"unitary_dd": (
job_unitary_dd,
[observables_unitary] * len(circuits_unitary),
),
# Omitting Dyn w/o DD and Dynamic w/ DD plots for better readability
# "dynamic": (job_dynamic, observables_dynamic),
# "dynamic_dd": (job_dynamic_dd, observables_dynamic),
"dynamic_meas2": (job_dynamic_meas2, observables_dynamic),
"dynamic_dd_meas2": (
job_dynamic_dd_meas2,
observables_dynamic,
),
}data_dict = {}
for key, (job, obs) in runs.items():
data = []
for i in range(points):
data.append(
[
job.result()[ind].data["meas"].expectation_values(obs[ind])[i]
for ind in depths
]
)
data_dict[key] = data아래 그림은 국소 자기장의 강도에 해당하는 다양한 값에서 Trotter 단계에 따른 스핀 자화도를 나타냅니다. 우리는 단일 이상 회로에 대한 사전 계산된 MPS 시뮬레이션 결과와 다음의 실험 결과를 함께 플롯합니다:
- DD를 사용한 단일 회로 구동
- DD와 함께 동적 회로를 구동하는
MidCircuitMeasure
plt.figure(figsize=(10, 6))
colors = ["#0f62fe", "#be95ff", "#ff7eb6"]
for i in range(points):
plt.plot(
depths,
data_sim[i],
color=colors[i],
linestyle="solid",
label=f"θ={pi_check(i*max_angle/(points-1))} (MPS)",
)
# plt.plot(
# depths,
# data_dict["unitary"][i],
# color=colors[i],
# linestyle=":",
# label=f"θ={pi_check(i*max_angle/(points-1))} (Unitary)",
# )
plt.plot(
depths,
data_dict["unitary_dd"][i],
color=colors[i],
marker="o",
mfc="none",
linestyle=":",
label=f"θ={pi_check(i*max_angle/(points-1))} (Unitary w/DD)",
)
# Omitting Dyn w/o DD and Dynamic w/ DD plots for better readability
# plt.plot(
# depths,
# data_dict["dynamic"][i],
# color=colors[i],
# linestyle="-.",
# label=f"θ={pi_check(i*max_angle/(points-1))} (Dyn w/o DD)",
# )
# plt.plot(
# depths,
# data_dict["dynamic_dd"][i],
# marker="D",
# mfc="none",
# color=colors[i],
# linestyle="-.",
# label=f"θ={pi_check(i*max_angle/(points-1))} (Dynamic w/ DD)",
# )
# plt.plot(
# depths,
# data_dict["dynamic_meas2"][i],
# color=colors[i],
# marker="s",
# mfc="none",
# linestyle=':',
# label=f"θ={pi_check(i*max_angle/(points-1))} (Dynamic w/ MidCircuitMeas)",
# )
plt.plot(
depths,
data_dict["dynamic_dd_meas2"][i],
color=colors[i],
marker="*",
markersize=8,
linestyle=":",
label=f"θ={pi_check(i*max_angle/(points-1))} "
f"(Dynamic w/ DD & MidCircuitMeas)",
)
plt.xlabel("Trotter steps", fontsize=16)
plt.ylabel("Average magnetization", fontsize=16)
plt.xticks(rotation=45)
handles, labels = plt.gca().get_legend_handles_labels()
plt.legend(
handles,
labels,
loc="upper right",
bbox_to_anchor=(1.46, 1.0),
shadow=True,
ncol=1,
)
plt.title(
f"{hex_rows}x{hex_cols} hex ring, {num_qubits} data qubits, "
f"{len(ancilla)} ancilla qubits \n{backend.name}: Sampler"
)
plt.show()Output:
실험 결과와 시뮬레이션 결과를 비교해 보면, 동적 회로 구현(별표가 있는 점선)이 표준 단위적 구현(원이 있는 점선)보다 전반적으로 더 우수한 성능을 보인다는 것을 알 수 있습니다. 요약하자면, 우리는 하드웨어에 본래 존재하지 않는 토폴로지인 허니컴 격자 상에서 이징 스핀 모델을 시뮬레이션하기 위한 해결책으로 동적 회로를 제시한다. 동적 회로 해법은 추가 보조 큐비트와 고전적 피드포워드 연산을 도입하는 대가로, SWAP 게이트 사용보다 짧은 2-큐비트 게이트 깊이로 비근접 큐비트 간 ZZ 상호작용을 가능하게 한다.
참조
[1.] Qiskit을 이용한 양자 컴퓨팅, Javadi-Abhari, A. 저 트레이니쉬, M., 크르술리치, K., 우드, C.J., 리쉬먼, J., 가콘, J., 마르티엘, S., 네이션, P.D., 비숍, L.S., 크로스, A.W. 존슨, B.R 2024. arXiv preprint arXiv:2405.08810 (2024)