Skip to main content
IBM Quantum Platform

パウリ伝播を用いたノイズを含む量子系のシミュレーション

このガイドでは、 3x3 の正方格子上で、ノイズを含む9量子ビットの横磁場イジングモデル(TFIM)の時間的ダイナミクスを古典的にシミュレーションするために、パッケージ pauli-prop を使用します。 PauliLindbladError の指示を用いて、一連のエンタングルメント層( U\mathcal{U} )に作用するノイズチャネル( Λ\Lambda )を定義する。次に、観測量( OO )をノイズの混入した回路を通じて後方伝播させ、さまざまなノイズモデルおよびノイズのない場合について、期待値を推定する。

騒音の多いEV

観測量が回路を逆方向に伝播するにつれて、エンタングルメント層 Uk\mathcal{U}_k に関連する各ノイズチャネル Λk\Lambda_k は、 OO における、そのパウリ・リンドブラッド生成子と反交換するパウリ項を減衰させる。 具体的には、 Gk,iG_{k,i} が Λk\Lambda_k のパウリ生成子であり、そのレートが γk,i\gamma_{k,i} である場合、 OO に含まれるパウリ項 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格子を指定し、エッジの着色を用いて、エンタングルメント層の最小集合を特定する
  • 各固有のエンタングルメント層について、合成ノイズモデル( Λk\Lambda_k )を生成する。 UkU_k
    • さまざまなスケールのノイズモデルを作成し、ゲートノイズがシステムに与える影響を調査する
  • 対象とするさまざまな深さおよびノイズスケールに対応した、ノイズのない量子回路とノイズのある量子回路を作成する
    • ノイズの多い回路では、各エンタングルメント層の前に命令 PauliLindbladError が挿入される
  • パウリ伝播を用いて、さまざまな深さにおける系の正確な期待値をシミュレーションする
    • 9量子ビットの場合、これは OO を 494^9 項まで拡張することで行われ、これによりパウリ空間全体を網羅することになる
  • パウリ伝播を用いて、ノイズを含む期待値をシミュレートする
  • ゲートノイズの増加が量子モデルの精度をどのように低下させるかを観察する

3x3 の正方格子を作成し、辺に対する4色着色法を求める

グラフの頂点は量子ビットを表し、辺は2つの量子ビット間の接続を表しています。 エッジの色付けは、量子回路内の固有のエンタングルメント層に対応しており、異なる色に割り当てられた接続上のゲートは同時に適用できないようになっている。

各層のノイズは個別に学習する必要があるため、効率的なノイズ学習プロトコルを実装するには、最小限の固有のエンタングルメント層の集合を特定することがしばしば重要となる。 習得すべきレイヤーが多ければ多いほど、QPUからのショット数も増やす必要があります。 このデモでは、レイヤー情報を用いてノイズの多い回路を構築し、各エンタングルメント層の前に PauliLindbladErrorqiskit-aer 命令を挿入することで、QPUのゲートノイズをモデル化しています。

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")

Output:

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
]

量子回路を作成する

このデモでは、横磁場アイジングモデル(TFIM)の時間的挙動を、トロッターステップ数を増やしながら(1~10ステップ)、シミュレーションします。 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: トロッター歩における時間の変化
  • 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)

Output:

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キュービットの実証実験では、すべてのシミュレーションを厳密に行います。 パウリ伝播の切り捨ては行われないため、異なるノイズモデル間における期待値の差は、すべてゲート誤差に起因するとみなすことができる。 シミュレーションのプロセスは、以下の4つのステップで行われます

  • 回路内のクリフォードゲートを、以下の方法を用いて回路の前方へと展開してください。 pauli_prop.evolve_through_cliffords
  • 回路の非クリフォード部分を通じて、観測可能量を以下を用いて伝播させる pauli_prop.propagate_through_circuit
    • 観測量が完全なパウリ空間の大きさまで拡大するように設定することで、厳密なシミュレーションを行う。 494^9
  • Qiskitの機能を使用して、進化した観測量を回路のクリフォード部分を通じて伝播させる SparsePauliOp.evolve
  • OO における各対角項の係数を合計することで、ゼロ状態に対する期待値 ∣0⟩⊗N|0\rangle^{\otimes N} を推定する(すべての量子ビットにおいて ZZ または II を含む項)
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."
)

Output:

Ran 10 noiseless and 50 noisy simulations in 103s.

ゲート誤差がモデルに及ぼす影響を観察する

これは9キュービットの実験であるため、パウリ伝播ルーチンは厳密であり、ノイズプロットに現れる誤差はすべてゲート誤差に起因するものであることを覚えておいてください。

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:

Output of the previous code cell
このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。