Skip to main content
IBM Quantum Platform

Simulazione di sistemi quantistici soggetti a rumore mediante la propagazione di Pauli

In questa guida utilizziamo il pacchetto pauli-prop per simulare in modo classico le dinamiche temporali di un modello di Ising a campo trasversale (TFIM) a nove qubit con rumore su un reticolo quadrato di tipo “ 3x3 ”. Utilizziamo le istruzioni PauliLindbladError per definire un canale di rumore, Λ\Lambda, che agisce su un insieme di livelli di entanglement, U\mathcal{U}. Successivamente, propaghiamo l’osservabile, OO, all’indietro attraverso il circuito soggetto a rumore e stimiamo i valori attesi per una varietà di modelli di rumore, oltre che per il caso privo di rumore.

Veicolo elettrico rumoroso

Man mano che l’osservabile viene propagato all’indietro attraverso il circuito, ciascun canale di rumore, Λk\Lambda_k, associato a uno strato di entanglement, Uk\mathcal{U}_k, smorza i termini di Pauli in OO che anticommutano con i propri generatori di Pauli-Lindblad. In particolare, se Gk,iG_{k,i} è un generatore di Pauli di Λk\Lambda_k con rata γk,i\gamma_{k,i}, allora un termine di Pauli, PP, in OO si trasforma come segue: 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, dove cPc_P è il coefficiente di PP. Una volta che OO è stato propagato all’inizio del circuito, il valore atteso rispetto allo stato zero, ∣0⟩⊗N|0\rangle^{\otimes N}, può essere calcolato banalmente sommando i coefficienti di ciascun termine diagonale in OO (termini contenenti ZZ o II su tutti i qubit).

Flusso di lavoro:

  • Specificare il reticolo TFIM e utilizzare la colorazione dei bordi per identificare un insieme minimo di strati di entanglement
  • Generare modelli di rumore sintetico, Λk\Lambda_k, per ogni singolo strato di entanglement, UkU_k
    • Creare modelli di rumore su varie scale per studiare l'impatto del rumore dei gate sul sistema
  • Creare circuiti quantistici privi di rumore e con rumore per le varie profondità e scale di rumore di interesse
    • Nei circuiti rumorosi, prima di ogni strato di intrecciamento vengono inserite delle istruzioni PauliLindbladError
  • Utilizzare la propagazione di Pauli per simulare i valori esatti delle aspettative del sistema a varie profondità
    • Per nove qubit, ciò avviene facendo crescere l' OO e fino a comprendere i termini 494^9, coprendo così l'intero spazio di Pauli
  • Utilizzare la propagazione di Pauli per simulare valori attesi soggetti a rumore
  • Si osservi come l'aumento del rumore di gate comprometta l'accuratezza del modello quantistico

Generare un reticolo quadrato di tipo “ 3x3 ” e trovare una colorazione a 4 colori sugli spigoli

I vertici del grafo rappresentano i qubit, mentre gli archi rappresentano una connessione tra due qubit. La colorazione dei bordi corrisponde a livelli di entanglement distinti nel circuito quantistico, in modo tale che le porte presenti sulle connessioni associate a colori diversi non possano essere applicate contemporaneamente.

L'identificazione di un insieme minimo di livelli di entanglement univoci è spesso importante per l'implementazione di protocolli efficienti di apprendimento del rumore, poiché il rumore di ciascun livello deve essere appreso in modo indipendente. Più livelli dobbiamo imparare, più immagini dobbiamo acquisire dalla QPU. In questa demo, utilizziamo le informazioni relative agli strati per costruire circuiti soggetti a rumore e inseriamo istruzioni di tipo “ PauliLindbladError ” prima qiskit-aer di ogni strato di entanglement, al fine di modellare il rumore dei gate della 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

Generare modelli di rumore sintetico

Prima di creare i circuiti quantistici, generiamo un modello di rumore (istanza di PauliLindbladError ) per ciascuno degli strati di entanglement. In seguito li inseriremo come istruzioni nei nostri circuiti quantistici. Per ogni livello, generiamo canali di rumore su scale diverse. Nello specifico, generiamo modelli di rumore con un valore di Error Per Layered Gate (EPLG) pari a circa e .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
]

Creare i circuiti quantistici

In questa dimostrazione simuliamo le dinamiche temporali di un modello di Ising a campo trasversale (TFIM) per un numero crescente di passi di Trotter (da 1 a 10 passi). Per ciascuna delle 10 profondità del circuito, simuliamo l’effetto del rumore di gate, sulla base di modelli di rumore su scale diverse (EPLGs = .0004, .0008, .0012, .0016, .002). Il rumore viene inserito nel circuito QuantumCircuit tramite l’istruzione “ PauliLindbladError ” di Qiskit Aer. L'Hamiltoniano considerato è:

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

dove J>0J>0 descrive l'accoppiamento tra gli spin più vicini, i<ji<j, e hh è il campo trasversale globale.

Qui implementiamo l'Hamiltoniano evoluto nel tempo su varie scale temporali e di rumore. Creiamo un totale di 60 circuiti: 10 circuiti privi di rumore con profondità di Trotter variabile e 50 circuiti con rumore per le 10 profondità di Trotter, distribuiti su cinque scale di rumore. Dato un grafo di connettività, il modello è parametrizzato da alcune variabili:

  • num_steps: Il numero di passi di Trotter
  • J: Forza di accoppiamento dei siti collegati
  • h: Intensità del campo magnetico esterno
  • dt: Variazione temporale lungo un passo di Trotter
  • initial_state_angle: Un'eccitazione iniziale, Ry(θ)R_y(\theta), da applicare in modo uniforme a tutti i qubit
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

Specificare le variabili osservabili ed eseguire le simulazioni

Per questa dimostrazione, simuliamo i valori attesi del correlatore medio a due siti:

⟨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

dove Ψ(θ)\Psi(\theta) corrisponde a una rotazione uniforme Ry(θ)R_y(\theta) su tutti i qubit, Us\mathscr{U}^s descrive ss strati di Trotter e (j,k)(j,k) indicizza tutte le coppie di vertici connessi sul reticolo.

Infine, utilizziamo pauli_prop per simulare i valori attesi osservabili per ciascuno dei circuiti. Per questa dimostrazione con nove qubit, eseguiamo tutte le simulazioni in modo esatto. Non verrà effettuato alcun troncamento della propagazione di Pauli; pertanto, le differenze nei valori attesi tra i diversi modelli di rumore possono essere interamente attribuite all'errore del gate. Il processo di simulazione si articola in quattro fasi:

  • Spostare i porte Clifford nel circuito verso la parte anteriore del circuito utilizzando pauli_prop.evolve_through_cliffords
  • Propagare l'osservabile attraverso la parte non-Clifford del circuito utilizzando pauli_prop.propagate_through_circuit
    • Eseguiamo simulazioni esatte lasciando che l’osservabile raggiunga le dimensioni dello spazio di Pauli completo, 494^9
  • Propagare l'osservabile evoluto attraverso la parte Clifford del circuito utilizzando Qiskit's SparsePauliOp.evolve
  • Calcolare il valore atteso rispetto allo stato zero, ∣0⟩⊗N|0\rangle^{\otimes N}, sommando i coefficienti di ciascun termine diagonale in OO (termini contenenti ZZ o II su tutti i qubit)
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.

Osservare l'effetto dell'errore di gate sul modello

Ricordate che, trattandosi di un esperimento a nove qubit, la routine di propagazione di Pauli è esatta e tutto l'errore riscontrabile nei grafici del rumore può essere attribuito all'errore dei gate.

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
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.