メインコンテンツへスキップ
このプロジェクトはベータ版です。バグを見つけましたか?GitHub Issueを開く。質問やアイデアがありますか?Discussionsで質問する — フィードバックをお待ちしています!
このページの翻訳品質はいかがですか?

Pauli伝播でノイズのある量子システムをシミュレーションする

このガイドでは、pauli-propパッケージを使って、3x3正方格子上のノイズのある9量子ビット横磁場イジング模型(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が回路の先頭まで伝播されたら、ゼロ状態∣0⟩⊗N|0\rangle^{\otimes N}に関する期待値は、OO内の各対角項(すべての量子ビットでZZまたはIIを含む項)の係数を足し合わせることで簡単に計算できます。

ワークフロー:

  • TFIMの格子を指定し、辺彩色を使って最小限のエンタングリング層の集合を特定します
  • 各固有のエンタングリング層UkU_kに対して、合成ノイズモデルΛk\Lambda_kを生成します
    • ゲートノイズがシステムに与える影響を調べるために、さまざまなスケールのノイズモデルを作成します
  • 関心のあるさまざまな深さとノイズスケールについて、ノイズなしおよびノイズありの量子回路を作成します
    • ノイズのある回路では、各エンタングリング層の前にPauliLindbladError命令が挿入されます
  • Pauli伝播を使って、さまざまな深さでのシステムの厳密な期待値をシミュレーションします
    • 9量子ビットの場合、OOを494^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.

Output of the previous code cell

合成ノイズモデルを生成する​

量子回路を作成する前に、各エンタングリング層についてノイズモデル(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に挿入されます。考慮するハミルトニアンは次のとおりです。

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個の回路を作成します。トロッターの深さが異なる10個のノイズなし回路と、5つのノイズスケールにわたる10通りのトロッターの深さに対する50個のノイズあり回路です。接続性グラフが与えられると、モデルはいくつかの変数によってパラメータ化されます。

  • num_steps: トロッターステップの数
  • J: 接続されたサイト間の結合強度
  • h: 外部磁場の強さ
  • dt: 1回のトロッターステップあたりの時間の変化量
  • 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

オブザーバブルを指定してシミュレーションを実行する​

このデモでは、平均的な2サイト相関子の期待値をシミュレーションします。

⟨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個のトロッター層を表し、(j,k)(j,k)は格子上のすべての接続された頂点対を指標とします。

最後に、pauli_propを使って、各回路のオブザーバブル期待値をシミュレーションします。この9量子ビットのデモでは、すべてのシミュレーションを厳密に実行します。Pauli伝播の切り捨ては行われません。そのため、異なるノイズモデル間での期待値の違いは、すべてゲート誤差に起因すると見なせます。シミュレーションの過程は4つのステップで行われます。

  • pauli_prop.evolve_through_cliffordsを使って、回路内のCliffordゲートを回路の先頭に発展させます
  • pauli_prop.propagate_through_circuitを使って、オブザーバブルを回路の非Clifford部分に通して伝播させます
    • オブザーバブルを完全なPauli空間のサイズ494^9まで増加させることで、厳密なシミュレーションを行います
  • QiskitのSparsePauliOp.evolveを使って、発展したオブザーバブルを回路のClifford部分に通して伝播させます
  • OO内の各対角項(すべての量子ビットでZZまたはIIを含む項)の係数を足し合わせることで、ゼロ状態∣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伝播ルーチンは厳密であり、ノイズのプロットに現れる誤差はすべてゲート誤差に起因すると見なせることを思い出してください。

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