주 콘텐츠로 건너뛰기
이 프로젝트는 베타 단계입니다. 버그를 발견하셨나요? GitHub 이슈 만들기. 질문이나 아이디어가 있으신가요? Discussions에서 질문하기 — 피드백을 기다립니다!
이 페이지의 번역 품질은 어떻습니까?

Pauli propagation으로 노이즈가 있는 양자 시스템 시뮬레이션하기

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

Noisy EV

관측량이 회로를 통해 거꾸로 전파될 때, 얽힘 층 Uk\mathcal{U}_k와 연관된 각 노이즈 채널 Λk\Lambda_k는 Pauli-Lindblad 생성자와 반교환하는 OO의 Pauli 항을 감쇠시킵니다. 구체적으로, Gk,iG_{k,i}가 비율 γk,i\gamma_{k,i}를 갖는 Λk\Lambda_k의 Pauli 생성자라면, OO의 Pauli 항 PP는 다음과 같이 변환됩니다. cP↦cPe−2γk,iif {P,Gk,i}=0c_P \mapsto c_P e^{-2\gamma_{k,i}} \quad \text{if } \{P, G_{k,i}\}=0, 여기서 cPc_P는 PP의 계수입니다. OO가 회로의 시작 부분까지 전파되면, OO의 각 대각 항(모든 큐비트에서 ZZ 또는 II를 포함하는 항)의 계수를 합하여 영 상태 ∣0⟩⊗N|0\rangle^{\otimes N}에 대한 기댓값을 간단히 계산할 수 있습니다.

워크플로:

  • TFIM 격자를 지정하고, 에지 색칠을 사용하여 최소한의 얽힘 층 집합을 찾습니다
  • 각 고유한 얽힘 층 UkU_k에 대해 합성 노이즈 모델 Λk\Lambda_k를 생성합니다
    • 게이트 노이즈가 시스템에 미치는 영향을 연구하기 위해 다양한 규모의 노이즈 모델을 만듭니다
  • 관심 있는 여러 깊이와 노이즈 규모에 대해 노이즈 없는 양자 회로와 노이즈가 있는 양자 회로를 만듭니다
    • 노이즈가 있는 회로에서는 각 얽힘 층 앞에 PauliLindbladError 명령어가 삽입됩니다
  • Pauli propagation을 사용하여 여러 깊이에서 시스템의 정확한 기댓값을 시뮬레이션합니다
    • 9 큐비트의 경우, OO가 전체 Pauli 공간을 포괄하는 494^9개 항까지 커지도록 하여 이를 수행합니다
  • 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.

Output of the previous code cell

합성 노이즈 모델 생성하기​

양자 회로를 만들기 전에, 각 얽힘 층에 대한 노이즈 모델(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에 삽입됩니다. 고려하는 해밀토니안은 다음과 같습니다.

H=−J∑⟨i,j⟩ZiZj+h∑iXiH = -J\sum\limits_{\langle i,j \rangle} Z_iZ_j + h\sum\limits_iX_i

여기서 J>0J>0는 최근접 스핀 i<ji<j 사이의 결합을 나타내고, hh는 전역 횡자기장입니다.

여기서는 다양한 시간 및 노이즈 규모에 걸쳐 시간 발전 해밀토니안을 구현합니다. 총 60개의 회로를 만듭니다. Trotter 깊이가 다른 노이즈 없는 회로 10개와, 5가지 노이즈 규모에 걸친 10개의 Trotter 깊이에 대한 노이즈 있는 회로 50개입니다. 연결성 그래프가 주어지면, 모델은 몇 가지 변수로 매개변수화됩니다.

  • num_steps: Trotter 단계 수
  • J: 연결된 사이트의 결합 세기
  • h: 외부 자기장의 세기
  • dt: Trotter 단계당 시간 변화량
  • initial_state_angle: 모든 큐비트에 균일하게 적용되는 초기 들뜸 Ry(θ)R_y(\theta)
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.

Output of the previous code cell

관측량 지정 및 시뮬레이션 실행하기​

이 데모에서는 평균 두 사이트 상관자의 기댓값을 시뮬레이션합니다.

⟨O⟩=⟨Ztot2(s)⟩=1N2∑⟨Ψ(θ)∣(U†)sZjZk(U)s∣Ψ(θ)⟩\langle O \rangle = \langle Z_{tot}^2(s) \rangle = \frac{1}{N^2}\sum \langle \Psi(\theta)|(\mathscr{U}^{\dagger})^sZ_jZ_k(\mathscr{U})^s|\Psi(\theta) \rangle

여기서 Ψ(θ)\Psi(\theta)는 모든 큐비트에 대한 균일한 Ry(θ)R_y(\theta) 회전에 해당하고, Us\mathscr{U}^s는 ss개의 Trotter 층을 나타내며, (j,k)(j,k)는 격자에서 연결된 모든 꼭짓점 쌍을 인덱싱합니다.

마지막으로 pauli_prop를 사용하여 각 회로에 대한 관측량 기댓값을 시뮬레이션합니다. 이 9 큐비트 데모에서는 모든 시뮬레이션을 정확하게 수행합니다. Pauli propagation 절단은 수행하지 않으므로, 노이즈 모델 간 기댓값의 차이는 전적으로 게이트 오류에 기인한다고 볼 수 있습니다. 시뮬레이션 과정은 네 단계로 처리됩니다.

  • pauli_prop.evolve_through_cliffords를 사용하여 회로의 Clifford 게이트를 회로 앞쪽으로 발전시킵니다
  • pauli_prop.propagate_through_circuit를 사용하여 회로의 non-Clifford 부분을 통해 관측량을 전파합니다
    • 관측량이 전체 Pauli 공간의 크기 494^9까지 커지도록 허용하여 정확한 시뮬레이션을 수행합니다
  • Qiskit의 SparsePauliOp.evolve를 사용하여 발전된 관측량을 회로의 Clifford 부분을 통해 전파합니다
  • 각 대각 항(ZZ 또는 II를 모든 큐비트에 포함하는 항)의 계수를 OO에서 합하여 영 상태 ∣0⟩⊗N|0\rangle^{\otimes N}에 대한 기댓값을 추정합니다
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()

Output of the previous code cell