Pauli伝播でノイズのある量子システムをシミュレーションする
このガイドでは、pauli-propパッケージを使って、3x3正方格子上のノイズのある9量子ビット横磁場イジング模型(TFIM)の時間発展を古典的にシミュレーションします。PauliLindbladError命令を使って、エンタングリング層の集合に作用するノイズチャネルを定義します。その後、オブザーバブルをノイズのある回路を通して後ろ向きに伝播させ、ノイズなしの場合に加えて、さまざまなノイズモデルについて期待値を推定します。

オブザーバブルを回路を通して後ろ向きに伝播させるとき、エンタングリング層に対応する各ノイズチャネルは、そのPauli-Lindblad生成子と反可換な内のPauli項を減衰させます。具体的には、が率を持つのPauli生成子であるとき、内のPauli項は次のように変換されます。。ここではの係数です。が回路の先頭まで伝播されたら、ゼロ状態に関する期待値は、内の各対角項(すべての量子ビットでまたはを含む項)の係数を足し合わせることで簡単に計算できます。
ワークフロー:
- TFIMの格子を指定し、辺彩色を使って最小限のエンタングリング層の集合を特定します
- 各固有のエンタングリング層に対して、合成ノイズモデルを生成します
- ゲートノイズがシステムに与える影響を調べるために、さまざまなスケールのノイズモデルを作成します
- 関心のあるさまざまな深さとノイズスケールについて、ノイズなしおよびノイズありの量子回路を作成します
- ノイズのある回路では、各エンタングリング層の前に
PauliLindbladError命令が挿入されます
- ノイズのある回路では、各エンタングリング層の前に
- Pauli伝播を使って、さまざまな深さでのシステムの厳密な期待値をシミュレーションします
- 9量子ビットの場合、を項まで増加させ、完全なPauli空間をカバーすることで行います
- Pauli伝播を使って、ノイズのある期待値をシミュレーションします
- ゲートノイズの増加が量子モデルの精度をどのように低下させるかを観察します
3x3正方格子を生成し、辺の4彩色を求める
グラフの頂点は量子ビットを表し、辺は2つの量子ビット間の接続を表します。辺彩色は量子回路内の固有のエンタングリング層に対応し、異なる色に関連付けられた接続上のゲートは同時に適用できません。
最小限の固有のエンタングリング層の集合を特定することは、効率的なノイズ学習プロトコルを実装するうえでしばしば重要です。各層のノイズは独立に学習する必要があるためです。学習すべき層が多いほど、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インスタンス)を生成します。これらは後で量子回路に命令として埋め込みます。各層について、さまざまなスケールのノイズチャネルを生成します。具体的には、層ゲートあたりの誤差(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
]
量子回路を作成する
このデモでは、トロッターステップ数を増やしながら(1〜10ステップ)、横磁場イジング模型(TFIM)の時間発展をシミュレーションします。10通りの回路の深さそれぞれについて、さまざまなスケールのノイズモデル(EPLGs = .0004, .0008, .0012, .0016, .002)を与えて、ゲートノイズの影響をシミュレーションします。ノイズは、Qiskit AerのPauliLindbladError命令としてQuantumCircuitに挿入されます。考慮するハミルトニアンは次のとおりです。
ここで、は最近接スピンの結合を表し、であり、は大域的な横磁場です。
ここでは、さまざまな時間とノイズのスケールにわたって、時間発展するハミルトニアンを実装します。合計60個の回路を作成します。トロッターの深さが異なる10個のノイズなし回路と、5つのノイズスケールにわたる10通りのトロッターの深さに対する50個のノイズあり回路です。接続性グラフが与えられると、モデルはいくつかの変数によってパラメータ化されます。
num_steps: トロッターステップの数J: 接続されたサイト間の結合強度h: 外部磁場の強さdt: 1回のトロッターステップあたりの時間の変化量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.

オブザーバブルを指定してシミュレーションを実行する
このデモでは、平均的な2サイト相関子の期待値をシミュレーションします。
ここで、はすべての量子ビットに対する一様な回転に対応し、は個のトロッター層を表し、は格子上のすべての接続された頂点対を指標とします。
最後に、pauli_propを使って、各回路のオブザーバブル期待値をシミュレーションします。この9量子ビットのデモでは、すべてのシミュレーションを厳密に実行します。Pauli伝播の切り捨ては行われません。そのため、異なるノイズモデル間での期待値の違いは、すべてゲート誤差に起因すると見なせます。シミュレーションの過程は4つのステップで行われます。
pauli_prop.evolve_through_cliffordsを使って、回路内のCliffordゲートを回路の先頭に発展させますpauli_prop.propagate_through_circuitを使って、オブザーバブルを回路の非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伝播ルーチンは厳密であり、ノイズのプロットに現れる誤差はすべてゲート誤差に起因すると見なせることを思い出してください。
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()
