Skip to main content
IBM Quantum Platform

Simulação de sistemas quânticos sujeitos a ruído com propagação de Pauli

Neste guia, utilizamos o pacote pauli-prop para simular, de forma clássica, a dinâmica temporal de um modelo de Ising de campo transversal (TFIM) de nove qubits com ruído em uma rede quadrada de 3x3. Utilizamos instruções do tipo “ PauliLindbladError ” para definir um canal de ruído, Λ\Lambda, que atua sobre um conjunto de camadas de entrelaçamento, U\mathcal{U}. Em seguida, propagamos o observável, OO, de trás para frente através do circuito com ruído e estimamos os valores esperados para uma variedade de modelos de ruído, bem como para o caso sem ruído.

Veículo elétrico barulhento

À medida que a grandeza observável é propagada retroativamente pelo circuito, cada canal de ruído, Λk\Lambda_k, associado à camada de entrelaçamento, Uk\mathcal{U}_k, atenua os termos de Pauli em OO, que anticomutam com seus geradores de Pauli-Lindblad. Especificamente, se Gk,iG_{k,i} for um gerador de Pauli de Λk\Lambda_k com taxa γk,i\gamma_{k,i}, então um termo de Pauli, PP, em OO se transforma como: 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, onde cPc_P é o coeficiente de PP. Uma vez que OO tenha sido propagado até o início do circuito, o valor esperado em relação ao estado zero, ∣0⟩⊗N|0\rangle^{\otimes N}, pode ser calculado de forma trivial somando-se os coeficientes de cada termo diagonal em OO (termos que contêm ZZ ou II em todos os qubits).

Fluxo de trabalho:

  • Especifique a rede TFIM e utilize a coloração de arestas para identificar um conjunto mínimo de camadas de entrelaçamento
  • Gerar modelos de ruído sintético, Λk\Lambda_k, para cada camada de entrelaçamento única, UkU_k
    • Criar modelos de ruído em várias escalas para estudar o impacto do ruído das portas no sistema
  • Criar circuitos quânticos sem ruído e com ruído para as diversas profundidades e escalas de ruído de interesse
    • Em circuitos ruidosos, são inseridas PauliLindbladError instruções antes de cada camada de entrelaçamento
  • Utilize a propagação de Pauli para simular os valores exatos da esperança do sistema em várias profundidades
    • Para nove qubits, isso é feito permitindo que OO cresça até 494^9 termos, abrangendo todo o espaço de Pauli
  • Utilize a propagação de Pauli para simular valores esperados com ruído
  • Observe como o aumento do ruído do portão prejudica a precisão do modelo quântico

Gere uma rede quadrada de 3x3 e encontre uma coloração de 4 cores nas arestas

Os vértices do grafo representam qubits, e as arestas representam uma conexão entre dois qubits. A coloração das arestas corresponde a camadas de entrelaçamento distintas no circuito quântico, de modo que as portas nas conexões associadas a cores diferentes não podem ser aplicadas simultaneamente.

Identificar um conjunto mínimo de camadas de entrelaçamento únicas costuma ser importante para a implementação de protocolos eficientes de aprendizado de ruído, já que o ruído de cada camada deve ser aprendido de forma independente. Quanto mais camadas precisamos aprender, mais tentativas precisamos fazer a partir da QPU. Nesta demonstração, utilizamos as informações das camadas para construir circuitos com ruído e injetamos instruçõ PauliLindbladError es antes qiskit-aer de cada camada de entrelaçamento, a fim de modelar o ruído das portas da 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

Gerar modelos de ruído sintético

Antes de criar os circuitos quânticos, geramos um modelo de ruído (instância de PauliLindbladError ) para cada uma das camadas de entrelaçamento. Mais tarde, vamos incorporá-las como instruções em nossos circuitos quânticos. Para cada camada, geramos canais de ruído em diferentes escalas. Mais especificamente, geramos modelos de ruído com o Erro por Portão em Camadas (EPLG) de aproximadamente 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
]

Criar os circuitos quânticos

Nesta demonstração, simulamos a dinâmica temporal de um modelo de Ising de campo transversal (TFIM) para um número crescente de passos de Trotter (1 a 10 passos). Para cada uma das 10 profundidades do circuito, simulamos o efeito do ruído de porta, com base em modelos de ruído de escalas variadas (EPLGs = .0004, .0008, .0012, .0016, .002). O ruído é inserido no circuito como QuantumCircuit uma instrução PauliLindbladError do Qiskit Aer. O hamiltoniano considerado é:

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

onde J>0J>0 descreve o acoplamento entre os spins vizinhos mais próximos, i<ji<j, e hh é o campo transversal global.

Aqui, implementamos o hamiltoniano evoluído no tempo em várias escalas de tempo e de ruído. Criamos um total de 60 circuitos: 10 circuitos sem ruído, com profundidades de Trotter variadas, e 50 circuitos com ruído para as 10 profundidades de Trotter, distribuídos por cinco escalas de ruído. Dado um grafo de conectividade, o modelo é parametrizado por algumas variáveis:

  • num_steps: O número de passos de Trotter
  • J: Força de acoplamento dos nós conectados
  • h: Intensidade do campo magnético externo
  • dt: Variação do tempo ao longo de um passo de Trotter
  • initial_state_angle: Uma excitação inicial, Ry(θ)R_y(\theta), a ser aplicada uniformemente a todos os qubits
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

Especifique as variáveis observáveis e execute as simulações

Para esta demonstração, simulamos os valores esperados do correlacionador médio de dois sítios:

⟨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

onde Ψ(θ)\Psi(\theta) corresponde a uma rotação uniforme Ry(θ)R_y(\theta) em todos os qubits, Us\mathscr{U}^s descreve ss camadas de Trotter e (j,k)(j,k) indexa todos os pares de vértices conectados na rede.

Por fim, utilizamos pauli_prop para simular os valores esperados observáveis para cada um dos circuitos. Para essa demonstração de nove qubits, realizamos todas as simulações de forma exata. Não será realizado nenhum truncamento da propagação de Pauli; portanto, as diferenças nos valores esperados entre os diferentes modelos de ruído podem ser inteiramente atribuídas ao erro do gate. O processo de simulação é realizado em quatro etapas:

  • Transforme as portas Clifford do circuito para a parte inicial do circuito usando pauli_prop.evolve_through_cliffords
  • Propague a observável pela parte não-Clifford do circuito utilizando pauli_prop.propagate_through_circuit
    • Realizamos simulações exatas permitindo que a observável cresça até atingir o tamanho do espaço de Pauli completo, 494^9
  • Propagar a variável observável resultante pela parte Clifford do circuito utilizando o Qiskit's SparsePauliOp.evolve
  • Calcule o valor esperado em relação ao estado zero, ∣0⟩⊗N|0\rangle^{\otimes N}, somando os coeficientes de cada termo diagonal em OO (termos que contêm ZZ ou II em todos os qubits)
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.

Observe o efeito do erro de porta no modelo

Lembre-se de que, como se trata de um experimento com nove qubits, a rotina de propagação de Pauli é exata, e todo o erro nos gráficos de ruído pode ser atribuído ao erro das portas.

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
Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.