Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Simula sistemi quantistici con rumore con la propagazione di Pauli

In questa guida usiamo il pacchetto pauli-prop per simulare classicamente la dinamica temporale di un modello di Ising a campo trasverso (TFIM) a nove qubit con rumore su un reticolo quadrato 3x3. Usiamo le istruzioni PauliLindbladError per definire un canale di rumore, Λ\Lambda, che agisce su un insieme di strati di entangling, U\mathcal{U}. Poi propaghiamo l'osservabile, OO, all'indietro attraverso il circuito con rumore e stimiamo i valori di aspettazione per diversi modelli di rumore, oltre al caso senza rumore.

Noisy EV

Quando l'osservabile viene propagata all'indietro attraverso il circuito, ogni canale di rumore, Λk\Lambda_k, associato allo strato di entangling, Uk\mathcal{U}_k, smorza i termini di Pauli in OO che anticommutano con i suoi generatori di Pauli-Lindblad. In particolare, se Gk,iG_{k,i} è un generatore di Pauli di Λk\Lambda_k con tasso, γk,i\gamma_{k,i}, allora un termine di Pauli, PP, in OO si trasforma come: 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 di aspettazione 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:

  • Specifica il reticolo TFIM e usa la colorazione degli archi per identificare un insieme minimo di strati di entangling
  • Genera modelli di rumore sintetici, Λk\Lambda_k, per ciascuno strato di entangling univoco, UkU_k
    • Crea modelli di rumore di varie scale per studiare l'impatto del rumore dei gate sul sistema
  • Crea circuiti quantistici senza e con rumore per le varie profondità e scale di rumore di interesse
    • Nei circuiti con rumore, le istruzioni PauliLindbladError vengono inserite prima di ogni strato di entangling
  • Usa la propagazione di Pauli per simulare i valori di aspettazione esatti del sistema a varie profondità
    • Per nove qubit, questo si ottiene lasciando crescere OO fino a 494^9 termini, coprendo l'intero spazio di Pauli
  • Usa la propagazione di Pauli per simulare i valori di aspettazione con rumore
  • Osserva come l'aumento del rumore dei gate degrada l'accuratezza del modello quantistico

Genera un reticolo quadrato 3x3 e trova una 4-colorazione degli archi​

I vertici del grafo rappresentano i qubit e gli archi rappresentano una connessione tra due qubit. La colorazione degli archi corrisponde a strati di entangling univoci nel circuito quantistico, in modo tale che i gate su connessioni associate a colori diversi non possano essere applicati simultaneamente.

Identificare un insieme minimo di strati di entangling univoci è spesso importante per implementare protocolli efficienti di apprendimento del rumore, poiché il rumore di ciascuno strato deve essere appreso in modo indipendente. Più strati dobbiamo apprendere, più shot dobbiamo prelevare dalla QPU. Per questa demo usiamo le informazioni sugli strati per costruire circuiti con rumore e inserire istruzioni PauliLindbladError da qiskit-aer prima di ogni strato di entangling, per modellare il rumore dei gate della QPU.

# 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

Genera modelli di rumore sintetici​

Prima di creare i circuiti quantistici, generiamo un modello di rumore (istanza di PauliLindbladError) per ciascuno strato di entangling. Più avanti li incorporeremo come istruzioni nei nostri circuiti quantistici. Per ogni strato generiamo canali di rumore di scale diverse. In particolare, generiamo modelli di rumore con Error Per Layered Gate (EPLG) di circa .0004, .0008, .0012, .0016, e .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
]

Crea i circuiti quantistici​

Per questa demo simuliamo la dinamica temporale di un modello di Ising a campo trasverso (TFIM) per numeri crescenti di passi di Trotter (1-10 passi). Per ciascuna delle 10 profondità di circuito, simuliamo l'effetto del rumore dei gate, dati modelli di rumore di scale diverse (EPLGs = .0004, .0008, .0012, .0016, .002). Il rumore viene inserito nel QuantumCircuit come istruzione PauliLindbladError di Qiskit Aer. L'Hamiltoniana considerata è:

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 spin primi vicini, i<ji<j, e hh è il campo trasverso globale.

Qui implementiamo l'Hamiltoniana evoluta nel tempo su varie scale di tempo e di rumore. Creiamo 60 circuiti in totale: 10 circuiti senza rumore che variano nella profondità di Trotter e 50 circuiti con rumore per le 10 profondità di Trotter su cinque scale di rumore. Dato un grafo di connettività, il modello è parametrizzato da poche variabili:

  • num_steps: il numero di passi di Trotter
  • J: intensità dell'accoppiamento dei siti connessi
  • h: intensità del campo magnetico esterno
  • dt: variazione del tempo in un passo di Trotter
  • initial_state_angle: un'eccitazione iniziale, Ry(θ)R_y(\theta), da applicare uniformemente 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)
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

Specifica l'osservabile ed esegui le simulazioni​

Per questa demo simuliamo i valori di aspettazione 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 connesse di vertici sul reticolo.

Infine usiamo pauli_prop per simulare i valori di aspettazione dell'osservabile per ciascuno dei circuiti. Per questa demo a nove qubit eseguiamo tutte le simulazioni in modo esatto. Non verrà eseguito alcun troncamento della propagazione di Pauli; pertanto, le differenze nei valori di aspettazione tra i diversi modelli di rumore possono essere attribuite interamente all'errore dei gate. Il processo di simulazione si articola in quattro passi:

  • Fai evolvere i gate di Clifford del circuito all'inizio del circuito usando pauli_prop.evolve_through_cliffords
  • Propaga l'osservabile attraverso la parte non Clifford del circuito usando pauli_prop.propagate_through_circuit
    • Eseguiamo simulazioni esatte permettendo all'osservabile di crescere fino alla dimensione dell'intero spazio di Pauli, 494^9
  • Propaga l'osservabile evoluta attraverso la parte di Clifford del circuito usando SparsePauliOp.evolve di Qiskit
  • Stima il valore di aspettazione 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."
)
Ran 10 noiseless and 50 noisy simulations in 103s.

Osserva l'effetto dell'errore dei gate sul modello​

Ricorda che, trattandosi di un esperimento a nove qubit, la routine di propagazione di Pauli è esatta e tutto l'errore 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 of the previous code cell