잡음이 있는 양자 프로세서에서 관찰된 견고하고 일관된 비아벨 하드론 역학
예상 소요 시간: Heron 프로세서(ibm_boston 또는 이에 상응하는 프로세서)에서 6분 (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 비아벨 격자 게이지 이론(특히 SU(2))을 루프-스트링-하드론(LSH) 프레임워크를 활용하여 효율적인 양자 시뮬레이션을 위해 어떻게 재구성할 수 있는가
- 근사적 SU(2) 게이지 이론 해밀토니안을 위한 트로터화 시간 진화 회로를 구성하고 이를 큐비트에 매핑하는 방법
- IBM Quantum® 하드웨어에서 Qiskit Estimator 프리미티브를 사용하여 판독 오차 완화 기능을 적용한 이 회로를 실행하는 방법
전제조건
- 양자 회로 및 게이트의 기초
- Qiskit Estimator 기본 요소 소개
- 양자장론 개념에 대한 기본적인 이해 (도움이 되지만 필수는 아닙니다. ‘배경’ 섹션에서 핵심 내용을 다룹니다)
배경
둥기 부여
강력(strong force)에 대한 SU(3) 게이지 이론인 양자 색역학(QCD)은 쿼크를 하드론으로 결합시키고, 갇힘 현상과 끈 파열을 지배한다. 고전 격자 QCD 기법은 정적 특성을 분석하는 데 탁월하지만, 부호 문제로 인해 실시간 동역학을 시뮬레이션할 수는 없다. 양자 컴퓨터는 게이지장의 자유도를 큐비트에 직접 인코딩함으로써 이러한 장벽을 우회할 수 있는 방법을 제시한다.
이 튜토리얼에서는 이러한 시뮬레이션을 시연합니다. 즉, IBM Quantum 하드웨어를 사용하여 (1+1)차원 SU(2) 격자 게이지 이론에서 하드론의 실시간 전파를 시뮬레이션합니다. 이 이론은 가장 단순한 비아벨 게이지 이론이자 완전한 QCD로 나아가는 디딤돌입니다.
코구트-수스킨드 해밀토니안
이 이론은 1D 공간 격자 위에서, 격자점에는 엇갈리게 배열된 페르미온(물질)이, 연결선에는 SU(2) 게이지장이 배치된 형태로 정립되었다. 무차원 형태로 변환하면 해밀토니안은 다음과 같다:
여기서 는 색전기장 에너지, 는 스태거드 질량 항, 는 물질-게이지 상호작용(호핑) 항, 는 페르미온 질량을 나타내며, 는 상호작용 강도이다. 이 이론의 연속체 극한은 및 에 위치한다.
루프-스트링-하드론(LSH) 프레임워크
주요 과제 중 하나는 각 링크에 대한 게이지장의 힐베르트 공간이 무한차원이라는 점이다. 루프-스트링-하드론(LSH) 프레임워크는 플럭스의 루프, 분리된 전하를 연결하는 스트링, 그리고 하드론(사이트에 위치한 게이지 싱글렛 페르미온 쌍)과 같은 게이지 불변 변수들을 통해 이론을 재구성함으로써 이 문제를 해결합니다. LSH 기저에서는 가우스 법칙이 기저의 정의상 자동으로 성립하므로, 모든 기저 상태가 물리적으로 타당합니다. 각 격자 점은 루프 수 , 유입 스트링, 유출 스트링을 나타내는 세 개의 양자수로 특징지어지며, 여기서 는 페르미온적이며 는 보손적이다. 이러한 값을 바탕으로, 짝수 사이트의 경우 , 홀수 사이트의 경우 로 국소 페르미온 수가 정의된다.
전체 해밀토니안에서 양자 회로까지: 세 가지 핵심 근사법
이 양자 회로는 전체 SU(2) 해밀토니안을 정확히 시뮬레이션 하지는 않는다. 대신, 이 모델은 약한 결합 영역( )에서 유효한 일련의 통제된 근사법을 적용합니다. 무엇이 근사되고 무엇이 근사되지 않는지를 이해하는 것이 필수적입니다:
근사 1 — 의 약결합 극한: 전체 상호작용 해밀토니안 (식 [1] 의 식 (16)에는 와 같은 항을 통해 보손 양자수 에 의존하는 선계수가 포함되어 있다. 약한 결합 영역( )에서는 라는 전기 항이 역학을 지배하며, 이는 가 큰 상태를 선호한다. 일 때, 비율 와 이 모든 선계수는 1로 단순화된다. 그러면 상호작용 해밀토니안은 순전히 국소적인 가장 가까운 이웃 간 이동으로 환원된다:
이는 와 무관하며, 페르미온성 큐비트에만 작용합니다.
근사법 2 — 의 전역 평균 플럭스: 전기 에너지는 각 링크의 에 따라 달라진다. 약결합 진공 상태에서는 가 크며 대략 균일합니다. 사이트별 값을 단일 전역 평균값 로 대체하여, 가 각 사이트의 페르미온 배열에 비례하는 대각 위상이 되도록 한다:
여기서 는 페르미온 구성 에 해당하는 사이트들에 대해 합을 취한 것이며, 는 무시해도 되는 전역 위상이다.
근사법 3 — 트로터화: 지속 시간이 인 한 단계에 대한 시간 진화 연산자는 다음과 같이 분해된다:
여기서 , , 이다. 이 1차 트로터 분해는 일 때 사라지는 오차를 유발한다. 우리는 전체적으로 를 고정한다.
이 세 가지 근사법의 결과로, 사이트당 두 개의 페르미온 큐비트 만이 동역학적 성질을 가지게 되며, 보손 의 자유도는 유효 매개변수로 흡수되었다. 이를 통해 개의 격자 사이트에 대해 개의 큐비트를 갖는 간결한 회로가 도출되며, 여기서 각 트로터 단계는 일정한 2-큐비트 게이트 깊이(단계당 13개)를 갖는다.
이 튜토리얼에서 시뮬레이션하는 내용
이 튜토리얼은 하드론의 전파 과정을 시뮬레이션합니다. 강한 결합 진공 상태(곱 상태)에서 시작하여, 격자 중심에 메손을 배치한 뒤 시간이 지남에 따라 변화를 관찰합니다. 차분 측정 프로토콜 — 중심 메손이 있는 경우와 없는 경우 모두에서 회로를 구동한 뒤 그 값을 서로 뺀다 — 는 하드웨어 노이즈와 경계 효과 모두로부터 코히어런트 하드론 신호를 분리해 낸다. 그 결과,閉じ込められた 메손의 호흡 모드에 특징적인 페르미온 밀도 진동의 광원뿔 패턴이 나타난다.
요구사항
이 튜토리얼을 시작하기 전에 다음을 설치하십시오:
- Qiskit SDK v2.0 또는 그 이후 버전이며, 시각화 기능을 지원합니다
- Qiskit Runtime v0.22 또는 그 이후 (
pip install qiskit-ibm-runtime) - Pauli 전파 패키지 (
pip install pauli-prop) - NumPy (
pip install numpy) - Matplotlib (
pip install matplotlib)
설정
먼저 필요한 라이브러리를 불러오고, LSH 시간 진화를 위한 양자 회로를 구성하는 헬퍼 함수를 정의하는 것으로 시작합니다. 회로 구성에는 세 가지 핵심 기능이 있습니다:
-
pair_hamiltonian_circuit: 인접한 사이트 간의 근사 상호작용 해밀토니안에 대해 2-큐비트 유니터리 연산 을 구현합니다. 게이트 분해식은 다음과 같습니다. . -
electric_hamiltonian_circuit: 각 사이트의 근사 전기장 에너지를 계산하기 위해 2-큐비트 유니터리 연산 을 구현합니다. 게이트 분해식은 다음과 같습니다. . -
construct_circuit: 큐비트 연결을 관리하기 위해 SWAP 게이트를 사용하여 상호작용 항, 전기 항, 질량 항을 층층이 쌓아 완전한 트로터화 회로를 구성합니다.
# Import libraries
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from typing import Optional
import warnings
warnings.filterwarnings("ignore")def pair_hamiltonian_circuit(c: float) -> QuantumCircuit:
"""Two-qubit unitary for the approximate interaction Hamiltonian H_I.
Implements exp(-i * c * H_I^approx) for one pair of neighboring sites,
where c = delta_tau * x.
"""
qc_temp = QuantumCircuit(2)
qc_temp.cx(1, 0)
qc_temp.h(1)
qc_temp.rz(-c, 1)
qc_temp.cx(0, 1)
qc_temp.rz(c, 1)
qc_temp.cx(0, 1)
qc_temp.h(1)
qc_temp.cx(1, 0)
return qc_temp
def electric_hamiltonian_circuit(theta: float) -> QuantumCircuit:
"""Two-qubit unitary for the approximate electric field Hamiltonian H_E.
Implements exp(-i * theta * H_E^approx) for one lattice site,
where theta = -delta_tau * (n_bar_l / 2 + 3/4).
"""
qc_temp = QuantumCircuit(2)
qc_temp.x(0)
qc_temp.rz(theta / 2, 0)
qc_temp.cx(0, 1)
qc_temp.rz(-theta / 2, 1)
qc_temp.cx(0, 1)
qc_temp.rz(theta / 2, 1)
qc_temp.x(0)
return qc_temp
def construct_circuit(
num_lattice_point: int,
num_trotter_steps: int,
c: float,
theta: float,
m: float,
theory: Optional[int] = 2,
barriers: Optional[bool] = False,
measurement: Optional[bool] = False,
add_init_state: Optional[bool] = True,
inverse_mid: Optional[bool] = False,
) -> QuantumCircuit:
"""Construct the full Trotterized time-evolution circuit.
Builds a circuit implementing n Trotter steps of the approximate SU(2)
LSH Hamiltonian evolution. The qubit layout uses a zigzag ordering:
n_i(0), n_i(1), n_o(0), n_o(1), n_i(2), n_i(3), n_o(2), n_o(3), ...
which minimizes the number of SWAP layers needed.
Args:
num_lattice_point: Number of lattice sites
(num_qubits = 2 * num_lattice_point).
num_trotter_steps: Number of Trotter steps.
c: Interaction parameter (delta_tau * x).
theta: Electric field phase parameter.
m: Mass parameter (m_tilde = delta_tau * mu).
theory: 1 for single chain, 2 for SU(2). Default 2.
barriers: Insert barriers between Trotter layers for
visualization.
measurement: Append measurements at the end.
add_init_state: Prepare the half-filled (strong-coupling vacuum)
initial state.
inverse_mid: Swap the central sites
(for differential measurement protocol).
"""
num_qubits = theory * num_lattice_point
qc = QuantumCircuit(num_qubits)
if num_trotter_steps <= 0:
return qc
# --- Initial state preparation ---
if add_init_state:
i = 1
while i < num_lattice_point:
for j in range(theory):
qc.x(i + j * num_lattice_point)
i = i + 2
if inverse_mid:
mid_lattice_qubits = [num_qubits // 2 - 1, num_qubits // 2]
qc.x(mid_lattice_qubits)
else:
i = 1
while i < num_qubits - 1:
qc.swap(i, i + 1)
i = i + 4
# --- Trotter steps ---
for step in range(num_trotter_steps):
if barriers:
qc.barrier()
# First SWAP layer (skipped at step 0 — absorbed into initial state mapping)
if step > 0:
i = 1
while i < num_qubits - 1:
qc.swap(i, i + 1)
i = i + 4
# First layer of pair interactions
j = 0
while j < num_qubits - 2:
circ = pair_hamiltonian_circuit(c)
qc.compose(circ, [j, j + 1], inplace=True)
j = j + 2
if num_lattice_point % 2 == 0:
circ = pair_hamiltonian_circuit(c)
qc.compose(circ, [j, j + 1], inplace=True)
# Second SWAP layer
i = 1
while i < num_qubits - 1:
qc.swap(i, i + 1)
i = i + theory
# Second layer of pair interactions
j = 2
while j < num_qubits - 3:
circ = pair_hamiltonian_circuit(c)
qc.compose(circ, [j, j + 1], inplace=True)
j = j + 2
if num_lattice_point % 2 != 0:
circ = pair_hamiltonian_circuit(c)
qc.compose(circ, [j, j + 1], inplace=True)
# Third SWAP layer
i = 3
while i < num_qubits - 1:
qc.swap(i, i + 1)
i = i + 2 * theory
# Electric field term
if theta != 0:
e_circ = electric_hamiltonian_circuit(theta)
for j in range(num_lattice_point):
qc.compose(e_circ, [2 * j, 2 * j + 1], inplace=True)
# Mass term: Rz(-m_tilde) for even sites, Rz(m_tilde) for odd sites
for q in range(num_qubits):
if q % 2 == 0:
qc.rz(-1 * m, q)
else:
qc.rz(m, q)
if measurement:
qc.measure_all()
return qcdef get_probabilities(expval: float):
"""Convert a Z-expectation value to site occupation probability.
Since <Z> = p(0) - p(1), the occupation probability is p(1) = (1 - <Z>) / 2.
"""
p1 = round((1 - expval) / 2, 3)
return p1
def get_number(expval_data, num_lattice_point):
"""Convert raw Z-expectation values to staggered fermion number n_f at each site.
n_f(r) = n_i(r) + n_o(r) for even r
n_f(r) = 2 - [n_i(r) + n_o(r)] for odd r
The two qubits per site encode (n_i, n_o), and occupation probabilities
give us <n_i> and <n_o>.
"""
N = []
for expvals in expval_data:
Pstep = [get_probabilities(expval) for expval in expvals]
Nstep = []
for k in range(num_lattice_point):
val = Pstep[2 * k] + Pstep[2 * k + 1]
a = 2 * (k % 2) + (1 - 2 * (k % 2)) * val
Nstep.append(float(a))
N.append(Nstep)
return N
def calculate_difference(N, N_mid, num_lattice_point):
"""Differential measurement protocol: |n_f(meson) - n_f(vacuum)|.
Subtracting the vacuum (SCV) evolution from the meson evolution
isolates the coherent hadron signal from symmetric noise and boundary effects.
"""
N_diff = []
for i in range(len(N)):
Nstep_diff = []
for j in range(num_lattice_point):
Nstep_diff.append(abs(N[i][j] - N_mid[i][j]))
N_diff.append(Nstep_diff)
return N_diff소규모 시뮬레이터 예시
먼저, 6개 사이트로 구성된 격자(12 큐비트)를 사용하여 소규모로 워크플로를 시연함으로써, 하드웨어에서 실행하기 전에 회로 구성을 검증하고 물리적 관측량을 파악할 수 있도록 하십시오.
1단계: 고전적 입력을 양자 문제에 매핑하기
해당 논문( , )에서 연구된 약결합 영역에 해당하는 물리적 매개변수를 정의하십시오. 도출된 회로 매개변수는 다음과 같습니다:
- (상호작용 매개변수)
- (전기장 위상)
- (질량 매개변수)
트로터 단계 수마다 두 가지 회로를 구성합니다. 하나는 중심에서 메손을 초기화하는 회로(inverse_mid=True)이고, 다른 하나는 강결합 진공을 준비하는 회로(inverse_mid=False)입니다. 미분 측정 프로토콜은 진공의 진화 과정을 차감하여 하드론 신호를 분리합니다.
# Physical / circuit parameters
num_lattice_point = 6 # 6 lattice sites -> 12 qubits for SU(2)
num_qubits = 2 * num_lattice_point
c = 0.15 # delta_tau * x
theta = 0.01 # electric field phase
m = 0.03 # m_tilde = delta_tau * mu
trotter_steps = range(1, 11) # 10 Trotter steps
print(f"Lattice sites: {num_lattice_point}, Qubits: {num_qubits}")
print(f"Parameters: c={c}, theta={theta}, m_tilde={m}")Output:
Lattice sites: 6, Qubits: 12
Parameters: c=0.15, theta=0.01, m_tilde=0.03
# Build circuits: meson initial state and vacuum (SCV) initial state
circuits_mid = [
construct_circuit(
num_lattice_point,
d,
c,
theta,
m,
barriers=False,
measurement=False,
add_init_state=True,
inverse_mid=True,
)
for d in trotter_steps
]
circuits = [
construct_circuit(
num_lattice_point,
d,
c,
theta,
m,
barriers=False,
measurement=False,
add_init_state=True,
inverse_mid=False,
)
for d in trotter_steps
]
# Visualize a single Trotter step
print(
f"Circuit for 1 Trotter step: {circuits[0].num_qubits} qubits, depth {circuits[0].depth()}"
)
circuits[0].draw("mpl", fold=-1)Output:
Circuit for 1 Trotter step: 12 qubits, depth 26
2단계: 양자 하드웨어 실행을 위해 문제를 최적화한다
관측 가능한 양을 정의합니다: 모든 큐비트에 대한 단일 큐비트 측정. 에서 각 격자 점 에 대한 입직 확률을 추출한 다음, 단계적 페르미온 수 를 구할 수 있습니다.
# Z observable on each qubit
observables = [
SparsePauliOp("I" * i + "Z" + "I" * (num_qubits - i - 1))
for i in range(num_qubits)
]
print(f"Number of observables: {len(observables)}")Output:
Number of observables: 12
3단계: Qiskit primitives를 사용하여 실행하기
소규모에서 잡음이 없는 정확한 시뮬레이션을 수행하려면 를 사용하십시오 StatevectorEstimator .
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
# Run meson circuits
pubs_mid = [(circuit, observables) for circuit in circuits_mid]
result_mid = estimator.run(pubs_mid).result()
# Run vacuum (SCV) circuits
pubs = [(circuit, observables) for circuit in circuits]
result = estimator.run(pubs).result()
# Extract expectation values
raw_expvals_mid = [
result_mid[i].data.evs[::-1] for i in range(len(circuits_mid))
]
raw_expvals = [result[i].data.evs[::-1] for i in range(len(circuits))]
print(f"Computed expectation values for {len(raw_expvals)} Trotter steps")Output:
Computed expectation values for 10 Trotter steps
4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다
기대값을 엇갈린 페르미온 수 로 변환하고, 미분 측정 프로토콜(메손 진공)을 적용하여 하드론 전파 히트맵을 생성합니다. 이는 참고 문헌의 그림 3의 구조를 재현한 것으로, x축에는 격자 점 , y축에는 트로터 단계(시간) , 색상 척도에는 가 표시되어 있습니다.
# Compute fermion numbers
N_mid_sim = get_number(raw_expvals_mid, num_lattice_point)
N_sim = get_number(raw_expvals, num_lattice_point)
N_diff_sim = calculate_difference(N_mid_sim, N_sim, num_lattice_point)
# --- Reproduce Figure 3 style: Staggered Fermionic Occupation Number Dynamics ---
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# Convert to numpy arrays for plotting
N_mid_arr = np.array(N_mid_sim)
N_arr = np.array(N_sim)
N_diff_arr = np.array(N_diff_sim)
# Color scheme
vmax = max(max(sublist) for sublist in N_arr)
vmin = -vmax
# Meson evolution
norm1 = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
im0 = axes[0].imshow(
N_mid_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm1,
extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],
)
axes[0].set_xlabel("Lattice site $r$", fontsize=12)
axes[0].set_ylabel("Trotter step $t$", fontsize=12)
axes[0].set_title("$n_f(r,t)$ — Meson initial state", fontsize=12)
plt.colorbar(im0, ax=axes[0], label="$n_f(r,t)$")
# Vacuum (SCV) evolution
im1 = axes[1].imshow(
N_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm1,
extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],
)
axes[1].set_xlabel("Lattice site $r$", fontsize=12)
axes[1].set_ylabel("Trotter step $t$", fontsize=12)
axes[1].set_title("$n_f(r,t)$ — Vacuum (SCV)", fontsize=12)
plt.colorbar(im1, ax=axes[1], label="$n_f(r,t)$")
# Differential: meson - vacuum
norm2 = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
im2 = axes[2].imshow(
N_diff_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm2,
extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],
)
axes[2].set_xlabel("Lattice site $r$", fontsize=12)
axes[2].set_ylabel("Trotter step $t$", fontsize=12)
axes[2].set_title(
"Staggered Fermionic Occupation Number Dynamics\n$|n_f^{\\mathrm{meson}} - n_f^{\\mathrm{vacuum}}|$",
fontsize=12,
)
plt.colorbar(im2, ax=axes[2], label="$n_f(r,t)$")
plt.suptitle(
f"Hadron propagation — {num_lattice_point}-site lattice (StatevectorEstimator)",
fontsize=14,
y=1.02,
)
plt.tight_layout()
plt.show()Output:
대규모 하드웨어 예시
이제 IBM Quantum 하드웨어에서 30개 사이트 격자(60 큐비트)로 규모를 확장합니다. 이 규모에서, 10 트로터 단계로 구성된 회로는 3,400개 이상의 2-큐비트 게이트와 14,000개의 단일 큐비트 게이트로 이루어져 있습니다.
1~4단계 (단일 코드 블록으로 통합됨)
하드웨어 워크플로의 주요 측면:
- 메손 및 진공 회로를 위한 10개의 트로터 단계 (드리프트를 최소화하기 위해 인터리브 처리됨)
- —를 이용한
optimization_level=1트랜스파일레이션: 회로 레이아웃은 이미 소자 토폴로지(선형 체인)와 동형이기 때문에, 라우팅 SWAP이 필요하지 않습니다. 이 트랜스파일러는 물리적 큐비트의 노이즈가 적은 체인을 선택하고, 게이트를 기본 게이트 집합으로 분해하는 데만 사용됩니다. EstimatorV2TREX 판독 오류 완화 및 파울리 회전 기법을 활용하여Batch모든 작업을 한꺼번에 제출하는 세션
# -------------------------Step 1: Define parameters & build circuits-------------------------
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2, Batch
from qiskit_ibm_runtime.options import (
EstimatorOptions,
ResilienceOptionsV2,
TwirlingOptions,
DynamicalDecouplingOptions,
)
service = QiskitRuntimeService()
num_lattice_point_hw = 30
num_qubits_hw = 2 * num_lattice_point_hw # 60 qubits
c_hw = 0.15
theta_hw = 0.01
m_hw = 0.03
trotter_steps_hw = range(1, 11) # 10 Trotter steps
# Build meson and vacuum circuits
circuits_mid_hw = [
construct_circuit(
num_lattice_point_hw,
d,
c_hw,
theta_hw,
m_hw,
barriers=False,
measurement=False,
add_init_state=True,
inverse_mid=True,
)
for d in trotter_steps_hw
]
circuits_hw = [
construct_circuit(
num_lattice_point_hw,
d,
c_hw,
theta_hw,
m_hw,
barriers=False,
measurement=False,
add_init_state=True,
inverse_mid=False,
)
for d in trotter_steps_hw
]
print(f"Built {len(circuits_hw)} circuit pairs for {num_qubits_hw} qubits")
# -------------------------Step 2: Transpile for hardware-------------------------
# The circuit topology is a linear chain, isomorphic to the device topology.
# We use optimization_level=1 since no routing SWAPs are needed — the transpiler
# only needs to select a low-noise qubit chain and decompose to native gates.
backend = service.backend("ibm_boston")
layout = [
140,
141,
142,
143,
136,
123,
122,
121,
116,
101,
102,
103,
96,
83,
82,
81,
76,
61,
62,
63,
64,
65,
66,
67,
68,
69,
78,
89,
88,
87,
97,
107,
106,
105,
117,
125,
126,
127,
137,
147,
148,
149,
150,
151,
152,
153,
154,
155,
139,
135,
134,
133,
132,
131,
130,
129,
118,
109,
110,
111,
]
pm = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=layout
)
isa_circuits_mid = pm.run(circuits_mid_hw)
isa_circuits = pm.run(circuits_hw)
print(f"Transpiled circuits. Example depth: {isa_circuits[0].depth()}")
# Define and layout-map observables
observables_hw = [
SparsePauliOp("I" * i + "Z" + "I" * (num_qubits_hw - i - 1))
for i in range(num_qubits_hw)
]
isa_observables_mid = [
[obs.apply_layout(isa_circuits_mid[i].layout) for obs in observables_hw]
for i in range(len(isa_circuits_mid))
]
isa_observables = [
[obs.apply_layout(isa_circuits[i].layout) for obs in observables_hw]
for i in range(len(isa_circuits))
]
# Build PUBs — interleave meson and vacuum for each Trotter step
isa_pubs_mid = [
(circ, obs) for circ, obs in zip(isa_circuits_mid, isa_observables_mid)
]
isa_pubs = [(circ, obs) for circ, obs in zip(isa_circuits, isa_observables)]
pubs_to_execute = [
[isa_pubs_mid[i], isa_pubs[i]] for i in range(len(isa_pubs))
]
# -------------------------Step 3: Execute on hardware-------------------------
twirling_options = TwirlingOptions(
enable_gates=True,
enable_measure=True,
shots_per_randomization="auto",
strategy="active-circuit",
)
resilience_options = ResilienceOptionsV2(
measure_mitigation=True, # TREX readout error mitigation
zne_mitigation=False, # ZNE turned off
)
dd_options = DynamicalDecouplingOptions(
enable=False # Circuit is sufficiently dense
)
options = EstimatorOptions(
resilience=resilience_options,
twirling=twirling_options,
dynamical_decoupling=dd_options,
default_shots=10_000,
)
ids = []
with Batch(backend=backend) as batch:
for idx, pub in enumerate(pubs_to_execute):
print(f"Submitting job for Trotter step {idx + 1}")
estimator = EstimatorV2(mode=batch, options=options)
estimator.skip_transpilation = True
job = estimator.run(pub)
ids.append(job.job_id())
batch_id = batch.session_id
job_info = {"ids": ids, "batch_id": batch_id}
print(f"Submitted {len(ids)} jobs. Batch ID: {batch_id}")print(ids)# -------------------------Step 4: Post-process results-------------------------
jobs = [service.job(job_id) for job_id in ids]
results = [job.result() for job in jobs]
# Extract expectation values (index 0 = meson, index 1 = vacuum)
raw_expvals_mid_hw = [result[0].data.evs[::-1] for result in results]
raw_expvals_hw = [result[1].data.evs[::-1] for result in results]
# Compute fermion numbers and differential
N_mid_hw = get_number(raw_expvals_mid_hw, num_lattice_point_hw)
N_hw = get_number(raw_expvals_hw, num_lattice_point_hw)
N_diff_hw = calculate_difference(N_mid_hw, N_hw, num_lattice_point_hw)N_diff_hw_arr = np.array(N_diff_hw)
fig, ax = plt.subplots(figsize=(10, 6))
vmax = np.abs(N_hw).max()
vmin = -vmax
norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
im = ax.imshow(
N_diff_hw_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm,
extent=[0, 8, 0.5, len(trotter_steps_hw) + 0.5],
)
ax.set_xlabel("Lattice site $r$", fontsize=13)
ax.set_ylabel("Trotter step $t$", fontsize=13)
ax.set_title(
"Staggered Fermionic Occupation Number Dynamics\nQuantum Simulation on IBM Hardware — 30-site lattice (60 qubits)",
fontsize=13,
)
cbar = plt.colorbar(im, ax=ax)
cbar.set_label("$n_f(r,t)$", fontsize=12)
plt.tight_layout()
plt.show()Output:
파울리 전파를 이용한 고전적 벤치마킹
파울리 전파법(PPM)은 하이젠베르크 모델에서 측정된 관측량을 회로 전체로 역전파함으로써, 양자 회로에 대한 잡음이 없는 고전적 시뮬레이션을 제공한다. 클리포드 층(CNOT, H, S, X 게이트)에서는 파울리 연산자가 항의 개수를 늘리지 않고 다른 파울리 연산자로 매핑됩니다. 비클리포드 층(회로 내의 게이트)은 분기를 유발할 수 있으며, 최악의 경우 항의 수가 두 배로 늘어날 수 있지만, 많은 분기들은 계수가 작아 생략할 수 있습니다.
...를 사용한 pauli-prop 워크플로는 다음과 같습니다:
evolve_through_cliffords...를 사용하여 회로를 클리포드 부분과 비클리포드 부분으로 나누십시오.atol``propagate_through_circuit각 관측량을 를 사용하여 비클리포드 부분에 대해 전파하되, 최대 개의max_terms파울리 항까지 유지하고, 계수가 절단 임계값 보다 작은 항은 제외한다.- Qiskit에 내장된 클리포드 연산 기능을 사용하여 클리포드 연산을 통해 결과를 도출합니다.
- 대각선 파울리 항( 와 만 포함)의 계수를 합산하여 기대값을 구한다.
절단 임계값
의 propagate_through_circuit 매개변수는 atol 작은 파울리 분기를 얼마나 적극적으로 제거할지 결정합니다. 매우 엄격한 임계값(예를 들어, 1e-12)을 적용하면 거의 모든 분기를 유지하여 정확한 결과를 얻을 수 있지만, 회로 깊이가 깊어질수록 시뮬레이션 시간이 급격히 증가합니다. 이 논문에서 제시된 120-큐비트 시뮬레이션은 기본 설정에서 약 8.5 시간이 소요되었습니다. 1e-3임계값을 높이면(예를 들어, 또는 로 1e-6 ), 계수가 해당 값보다 낮은 항들이 제외되어 추적되는 항의 수가 대폭 줄어들고 계산 속도가 빨라집니다. 그 대가로 발생하는 것은 작고 제어 가능한 근사 오차이며, 이는 서로 다른 임계값에서 얻은 결과를 비교함으로써 검증할 수 있습니다.
import time
from pauli_prop import evolve_through_cliffords, propagate_through_circuit
# ── PPM Configuration ──
# Truncation threshold: controls the speed/accuracy trade-off.
PPM_THRESHOLD = 1e-3
# Maximum Pauli terms to track per observable (hard cap on memory/time)
PPM_MAX_TERMS = 66_000
print(f"PPM settings: atol={PPM_THRESHOLD}, max_terms={PPM_MAX_TERMS}")
# We propagate each single-qubit Z observable through each circuit.
# For PPM, we work with the un-transpiled circuits (ideal noiseless simulation).
observables_pp = [
SparsePauliOp("I" * i + "Z" + "I" * (num_qubits_hw - i - 1))
for i in range(num_qubits_hw)
]
def ppm_expectation_values(
circuit, observables, max_terms=PPM_MAX_TERMS, atol=PPM_THRESHOLD
):
"""Compute expectation values of single-qubit Z observables
via Pauli propagation.
Args:
circuit: The quantum circuit to simulate.
observables: List of single-qubit Z observables.
max_terms: Maximum number of Pauli terms to retain (hard cap).
atol: Absolute tolerance — Pauli terms with coefficients below this
value are discarded during propagation. Larger values give
faster simulation at the cost of approximation accuracy.
"""
circuit = circuit.decompose(["swap"]) # decompose SWAPs into 3 CX gates
cliff, non_cliff = evolve_through_cliffords(circuit)
evs = []
for obs in observables:
evolved_obs = propagate_through_circuit(
obs, non_cliff, max_terms=max_terms, atol=atol, frame="h"
)[0]
evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame="h")
diagonal_mask = ~evolved_obs.paulis.x.any(axis=1)
ev = float(evolved_obs.coeffs[diagonal_mask].sum().real)
evs.append(ev)
return np.array(evs)
# Run PPM for each Trotter step and record wall-clock time
pp_expvals_mid = []
pp_expvals = []
pp_times = []
for idx, d in enumerate(trotter_steps_hw):
t_start = time.perf_counter()
# Meson circuit
evs_mid = ppm_expectation_values(circuits_mid_hw[idx], observables_pp)
# Vacuum circuit
evs_vac = ppm_expectation_values(circuits_hw[idx], observables_pp)
elapsed = time.perf_counter() - t_start
pp_times.append(elapsed)
pp_expvals_mid.append(evs_mid[::-1])
pp_expvals.append(evs_vac[::-1])
print(f"Trotter step {d:2d}: {elapsed:.1f} s")
print(f"\nTotal PPM simulation time: {sum(pp_times):.1f} s")
print(f"Truncation threshold used: {PPM_THRESHOLD}")Output:
PPM settings: atol=0.001, max_terms=66000
Trotter step 1: 5.0 s
Trotter step 2: 7.5 s
Trotter step 3: 11.2 s
Trotter step 4: 14.7 s
Trotter step 5: 18.3 s
Trotter step 6: 22.1 s
Trotter step 7: 25.6 s
Trotter step 8: 29.4 s
Trotter step 9: 33.2 s
Trotter step 10: 36.6 s
Total PPM simulation time: 203.6 s
Truncation threshold used: 0.001
# --- PPM simulation time vs. Trotter steps ---
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(
list(trotter_steps_hw),
pp_times,
"o-",
color="tab:blue",
linewidth=2,
markersize=6,
)
ax.set_xlabel("Trotter step", fontsize=13)
ax.set_ylabel("Wall-clock time (s)", fontsize=13)
ax.set_title(
"Pauli Propagation simulation time vs. Trotter steps\n(30-site lattice, 60 qubits)",
fontsize=13,
)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Output:
# --- PPM heatmap and comparison with hardware ---
N_mid_pp = get_number(pp_expvals_mid, num_lattice_point_hw)
N_pp = get_number(pp_expvals, num_lattice_point_hw)
N_diff_pp = calculate_difference(N_mid_pp, N_pp, num_lattice_point_hw)
N_diff_pp_arr = np.array(N_diff_pp)
fig, axes = plt.subplots(1, 2, figsize=(18, 6))
vmax = np.abs(N_hw).max()
vmin = -vmax
norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)
# PPM result
im0 = axes[0].imshow(
N_diff_pp_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm,
extent=[0, num_lattice_point_hw, 0.5, len(trotter_steps_hw) + 0.5],
)
axes[0].set_xlabel("Lattice site $r$", fontsize=12)
axes[0].set_ylabel("Trotter step $t$", fontsize=12)
axes[0].set_title(
"Pauli Propagation\n(classical noiseless simulation)", fontsize=12
)
plt.colorbar(im0, ax=axes[0], label="$n_f(r,t)$")
# Hardware result
im1 = axes[1].imshow(
N_diff_hw_arr,
aspect="auto",
origin="lower",
cmap="RdBu_r",
norm=norm,
extent=[0, num_lattice_point_hw, 0.5, len(trotter_steps_hw) + 0.5],
)
axes[1].set_xlabel("Lattice site $r$", fontsize=12)
axes[1].set_ylabel("Trotter step $t$", fontsize=12)
axes[1].set_title(
"Quantum Simulation\n(IBM Hardware, readout error mitigation only)",
fontsize=12,
)
plt.colorbar(im1, ax=axes[1], label="$n_f(r,t)$")
plt.suptitle(
"Staggered Fermionic Occupation Number Dynamics — 30-site lattice",
fontsize=14,
y=1.02,
)
plt.tight_layout()
plt.show()Output:
다음 단계
이 글이 흥미로웠다면, 다음 자료를 살펴보시는 것도 좋습니다:
- Qiskit Estimator 기본 요소 문서 — 오류 완화 옵션 구성에 대한 자세한 내용
- 오류 완화 및 억제 기법 — TREX, ZNE 및 기타 완화 기법에 대해 알아보기
- Qiskit Pauli Propagation (pauli-prop) — Pauli 역전파를 통한 Rust 기반 가속 클래식 시뮬레이션
참조
[1] 원문 논문: Ilčić, Majumdar, Mathew 외. “잡음이 있는 양자 프로세서에서 관찰된 견고하고 일관된 비아벨 하드론 역학” arXiv:2602.18080 (2026)