Pauli propagation으로 노이즈가 있는 양자 시스템 시뮬레이션하기
이 가이드에서는 pauli-prop 패키지를 사용하여 3x3 정사각 격자 위의 노이즈가 있는 9 큐비트 횡자기장 Ising 모델(TFIM)의 시간 동역학을 고전적으로 시뮬레이션합니다. PauliLindbladError 명령어를 사용하여 얽힘 층의 집합 에 작용하는 노이즈 채널 를 정의합니다. 그런 다음 관측량 를 노이즈가 있는 회로를 통해 거꾸로 전파하고, 여러 노이즈 모델과 노이즈가 없는 경우에 대해 기댓값을 추정합니다.

관측량이 회로를 통해 거꾸로 전파될 때, 얽힘 층 와 연관된 각 노이즈 채널 는 Pauli-Lindblad 생성자와 반교환하는 의 Pauli 항을 감쇠시킵니다. 구체적으로, 가 비율 를 갖는 의 Pauli 생성자라면, 의 Pauli 항 는 다음과 같이 변환됩니다. , 여기서 는 의 계수입니다. 가 회로의 시작 부분까지 전파되면, 의 각 대각 항(모든 큐비트에서 또는 를 포함하는 항)의 계수를 합하여 영 상태 에 대한 기댓값을 간단히 계산할 수 있습니다.
워크플로:
- TFIM 격자를 지정하고, 에지 색칠을 사용하여 최소한의 얽힘 층 집합을 찾습니다
- 각 고유한 얽힘 층 에 대해 합성 노이즈 모델 를 생성합니다
- 게이트 노이즈가 시스템에 미치는 영향을 연구하기 위해 다양한 규모의 노이즈 모델을 만듭니다
- 관심 있는 여러 깊이와 노이즈 규모에 대해 노이즈 없는 양자 회로와 노이즈가 있는 양자 회로를 만듭니다
- 노이즈가 있는 회로에서는 각 얽힘 층 앞에
PauliLindbladError명령어가 삽입됩니다
- 노이즈가 있는 회로에서는 각 얽힘 층 앞에
- Pauli propagation을 사용하여 여러 깊이에서 시스템의 정확한 기댓값을 시뮬레이션합니다
- 9 큐비트의 경우, 가 전체 Pauli 공간을 포괄하는 개 항까지 커지도록 하여 이를 수행합니다
- Pauli propagation을 사용하여 노이즈가 있는 기댓값을 시뮬레이션합니다
- 게이트 노이즈가 증가할수록 양자 모델의 정확도가 어떻게 저하되는지 관찰합니다
3x3 정사각 격자를 생성하고 에지에 대한 4-색칠 찾기
그래프의 꼭짓점은 큐비트를 나타내고, 에지는 두 큐비트 사이의 연결을 나타냅니다. 에지 색칠은 양자 회로의 고유한 얽힘 층에 해당하며, 서로 다른 색과 연관된 연결 위의 게이트는 동시에 적용될 수 없습니다.
각 층의 노이즈를 독립적으로 학습해야 하므로, 효율적인 노이즈 학습 프로토콜을 구현하려면 고유한 얽힘 층의 최소 집합을 찾는 것이 중요한 경우가 많습니다. 학습해야 할 층이 많을수록 QPU에서 더 많은 샷을 수행해야 합니다. 이 데모에서는 층 정보를 사용하여 노이즈가 있는 회로를 구성하고, QPU 게이트 노이즈를 모델링하기 위해 각 얽힘 층 앞에 qiskit-aer의 PauliLindbladError 명령어를 삽입합니다.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "pauli_prop": "pauli-prop", "qiskit": "qiskit", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_aer": "qiskit-aer", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from collections import defaultdict
import numpy as np
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.coloring import auto_color_edges
# Define rectangular square-lattice on 20 qubits
num_rows = 3
num_cols = 3
num_qubits = num_rows * num_cols
coupling_map = CouplingMap.from_grid(
num_rows=num_rows, num_columns=num_cols, bidirectional=False
)
# Create mapping from color to edge list
coloring = auto_color_edges(coupling_map.get_edges())
color_to_edge = defaultdict(list)
for edge, color in coloring.items():
color_to_edge[color].append(edge)
from rustworkx import PyDiGraph
from rustworkx.visualization import graphviz_draw
# Inspect graph coupling and unique entangling layers
print(
f"The circuit will have {num_qubits} qubits and {len(color_to_edge)} unique entangling layers."
)
sq_lattice = PyDiGraph()
sq_lattice.extend_from_weighted_edge_list(
[
(source, target, color)
for ((source, target), color) in coloring.items()
]
)
def color_edge_4color(edge):
color_dict = {0: "red", 1: "green", 2: "blue", 3: "orange"}
return {"color": color_dict[edge]}
graphviz_draw(sq_lattice, edge_attr_fn=color_edge_4color, method="neato")
The circuit will have 9 qubits and 4 unique entangling layers.

합성 노이즈 모델 생성하기
양자 회로를 만들기 전에, 각 얽힘 층에 대한 노이즈 모델(PauliLindbladError 인스턴스)을 생성합니다. 이를 나중에 양자 회로에 명령어로 삽입할 것입니다. 각 층마다 다양한 규모의 노이즈 채널을 생성합니다. 구체적으로 층 게이트당 오류(Error Per Layered Gate, EPLG)가 대략 .0004, .0008, .0012, .0016, 및 .002인 노이즈 모델을 생성합니다.
from qiskit.quantum_info import SparsePauliOp, pauli_basis
from qiskit_aer.noise import PauliLindbladError
# Pauli-Lindblad noise parameters
seed = 1764
target_EPLGs = [0.0004, 0.0008, 0.0012, 0.0016, 0.002]
def generate_random_pauli_lindblad_noise(
edges,
num_qubits: int | None = None,
noise_scale: float = 1e-3,
seed: int | None = None,
) -> PauliLindbladError:
"""Generate random Pauli-Lindblad noise over the full Pauli basis."""
if num_qubits is None:
num_qubits = np.max(edges)
basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]
basis_paulis = SparsePauliOp.from_sparse_list(
[
(pauli.to_label(), edge, 1)
for pauli in basis_paulis
for edge in edges
],
num_qubits=num_qubits,
)
basis_paulis = basis_paulis.simplify()
basis_paulis = basis_paulis.paulis
rng = np.random.default_rng(seed=seed)
rates = rng.random(len(basis_paulis)) * noise_scale
return PauliLindbladError(generators=basis_paulis, rates=rates)
num_generators = (
(num_rows * num_cols)
+ (num_rows - 1) * num_cols
+ num_rows * (num_cols - 1)
)
noise_scales = [
EPLG * (num_rows * num_cols) / num_generators for EPLG in target_EPLGs
]
noise_models_per_EPLG = [
[
generate_random_pauli_lindblad_noise(
color_to_edge[color],
num_qubits=num_qubits,
noise_scale=noise_scale,
seed=seed,
)
for color in range(len(color_to_edge))
]
for noise_scale in noise_scales
]
양자 회로 만들기
이 데모에서는 Trotter 단계 수(1-10 단계)를 늘려 가며 횡자기장 Ising 모델(TFIM)의 시간 동역학을 시뮬레이션합니다. 10가지 회로 깊이 각각에 대해, 다양한 규모의 노이즈 모델(EPLGs = .0004, .0008, .0012, .0016, .002)이 주어졌을 때 게이트 노이즈의 영향을 시뮬레이션합니다. 노이즈는 Qiskit Aer의 PauliLindbladError 명령어로 QuantumCircuit에 삽입됩니다. 고려하는 해밀토니안은 다음과 같습니다.
여기서 는 최근접 스핀 사이의 결합을 나타내고, 는 전역 횡자기장입니다.
여기서는 다양한 시간 및 노이즈 규모에 걸쳐 시간 발전 해밀토니안을 구현합니다. 총 60개의 회로를 만듭니다. Trotter 깊이가 다른 노이즈 없는 회로 10개와, 5가지 노이즈 규모에 걸친 10개의 Trotter 깊이에 대한 노이즈 있는 회로 50개입니다. 연결성 그래프가 주어지면, 모델은 몇 가지 변수로 매개변수화됩니다.
num_steps: Trotter 단계 수J: 연결된 사이트의 결합 세기h: 외부 자기장의 세기dt: Trotter 단계당 시간 변화량initial_state_angle: 모든 큐비트에 균일하게 적용되는 초기 들뜸
from typing import Any
from qiskit import QuantumCircuit
# Ising model parameters
num_steps = 10
J = -1.0
dt = 0.25 / abs(J)
h = 2.0 * abs(J)
initial_state_angle = np.pi / 18.0
rx_angle = 2.0 * h * dt
rzz_angle = 2.0 * J * dt
def generate_ising_circuit(
num_qubits: int,
num_steps: int,
rx_angle: float,
rzz_angle: float,
coloring: dict[Any, list[tuple[int, int]]],
layer_noise_models: list[PauliLindbladError] | None = None,
initial_state_angle: float | None = None,
) -> QuantumCircuit:
"""Generate a quantum circuit implementing a transverse-field Ising model"""
qc = QuantumCircuit(num_qubits)
if initial_state_angle:
qc.ry(initial_state_angle, range(num_qubits))
qc.rx(rx_angle / 2, range(num_qubits))
for i in range(num_steps):
for j, layer in enumerate(coloring):
edges = coloring[layer]
if layer_noise_models:
qc.append(layer_noise_models[j], qargs=range(num_qubits))
for edge in edges:
qc.rzz(rzz_angle, *edge)
if i == num_steps - 1:
qc.rx(rx_angle / 2, range(num_qubits))
else:
qc.rx(rx_angle, range(num_qubits))
return qc
# Create the noiseless and noisy circuits
noiseless_circs = []
noisy_circs = []
for steps in range(1, num_steps + 1):
noiseless_circs.append(
generate_ising_circuit(
num_qubits,
steps,
rx_angle,
rzz_angle,
color_to_edge,
initial_state_angle=initial_state_angle,
)
)
noisy_circs_per_step = []
for noise_models in noise_models_per_EPLG:
noisy_circs_per_step.append(
generate_ising_circuit(
num_qubits,
steps,
rx_angle,
rzz_angle,
color_to_edge,
layer_noise_models=noise_models,
initial_state_angle=initial_state_angle,
)
)
noisy_circs.append(noisy_circs_per_step)
print(
f"{num_steps} noiseless and {num_steps * len(target_EPLGs)} noisy Trotter circuits generated. {num_steps} different depths across {len(target_EPLGs)} different noise models"
)
print("\nBelow: Initial state and one noisy Trotter step.")
noisy_circs[0][0].draw("mpl", fold=-1)
10 noiseless and 50 noisy Trotter circuits generated. 10 different depths across 5 different noise models
Below: Initial state and one noisy Trotter step.

관측량 지정 및 시뮬레이션 실행하기
이 데모에서는 평균 두 사이트 상관자의 기댓값을 시뮬레이션합니다.
여기서 는 모든 큐비트에 대한 균일한 회전에 해당하고, 는 개의 Trotter 층을 나타내며, 는 격자에서 연결된 모든 꼭짓점 쌍을 인덱싱합니다.
마지막으로 pauli_prop를 사용하여 각 회로에 대한 관측량 기댓값을 시뮬레이션합니다. 이 9 큐비트 데모에서는 모든 시뮬레이션을 정확하게 수행합니다. Pauli propagation 절단은 수행하지 않으므로, 노이즈 모델 간 기댓값의 차이는 전적으로 게이트 오류에 기인한다고 볼 수 있습니다. 시뮬레이션 과정은 네 단계로 처리됩니다.
pauli_prop.evolve_through_cliffords를 사용하여 회로의 Clifford 게이트를 회로 앞쪽으로 발전시킵니다pauli_prop.propagate_through_circuit를 사용하여 회로의 non-Clifford 부분을 통해 관측량을 전파합니다- 관측량이 전체 Pauli 공간의 크기 까지 커지도록 허용하여 정확한 시뮬레이션을 수행합니다
- Qiskit의
SparsePauliOp.evolve를 사용하여 발전된 관측량을 회로의 Clifford 부분을 통해 전파합니다 - 각 대각 항( 또는 를 모든 큐비트에 포함하는 항)의 계수를 에서 합하여 영 상태 에 대한 기댓값을 추정합니다
import time
from pauli_prop import evolve_through_cliffords, propagate_through_circuit
from qiskit.quantum_info import Pauli
# Average ZZ-correlator observable
id_pauli = Pauli("I" * num_qubits)
observable = 2 * SparsePauliOp(
[
id_pauli.dot(Pauli("ZZ"), [i, j])
for i in range(num_qubits)
for j in range(i + 1, num_qubits)
]
)
observable /= num_qubits**2
# Pauli propagation parameters
max_terms = 4**num_qubits # Exact propagation
atol = 1e-12
# Run simulations
exact_evs = []
noisy_evs = [[] for _ in range(len(target_EPLGs))]
st = time.perf_counter()
for i, noiseless_circ in enumerate(noiseless_circs):
cliff, non_cliff = evolve_through_cliffords(noiseless_circ)
evolved_obs = propagate_through_circuit(
observable, non_cliff, max_terms=max_terms, atol=atol, frame="h"
)[0]
evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame="h")
exact_evs.append(
float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())
)
for j in range(len(target_EPLGs)):
noisy_circ = noisy_circs[i][j]
cliff, non_cliff = evolve_through_cliffords(noisy_circ)
evolved_obs = propagate_through_circuit(
observable, non_cliff, max_terms=max_terms, atol=1e-12, frame="h"
)[0]
evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame="h")
noisy_evs[j].append(
float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())
)
print(
f"Ran {len(noiseless_circs)} noiseless and {len(target_EPLGs) * num_steps} noisy simulations in {int(time.perf_counter() - st)}s."
)
Ran 10 noiseless and 50 noisy simulations in 103s.
모델에 대한 게이트 오류의 영향 관찰하기
9 큐비트 실험이므로 Pauli propagation 루틴은 정확하며, 노이즈 그래프의 모든 오차는 게이트 오류에 기인한다는 점을 기억하세요.
import matplotlib.pyplot as plt
xs = range(1, num_steps + 1)
plt.plot(xs, exact_evs, label="Noiseless", color="black", marker="o")
colors = [".3", ".4", ".5", ".6", ".7"]
for i, evs in enumerate(noisy_evs):
plt.plot(
xs,
evs,
label=f"{target_EPLGs[i]} EPLG",
linestyle="--",
color=colors[i],
marker="o",
)
plt.xlabel("# Trotter steps")
plt.ylabel(r"$\langle Z_{tot}^2 \rangle$")
plt.legend()
plt.show()
