Skip to main content
IBM Quantum Platform

Ruído quântico e mitigação de erros

Note

Toshinari Itoko (28 de junho de 2024)

Baixe o pdf da palestra original. Observe que alguns trechos de código podem se tornar obsoletos, pois são imagens estáticas.

O tempo aproximado da QPU para executar esse experimento é de 1 m 40 s.


1. Introdução

Ao longo desta lição, examinaremos o ruído e como ele pode ser atenuado em computadores quânticos. Começaremos analisando os efeitos do ruído usando um simulador que pode simular o ruído de algumas maneiras, inclusive usando perfis de ruído de computadores quânticos reais. Em seguida, passaremos para os computadores quânticos reais, nos quais o ruído é inerente. Examinaremos os efeitos da atenuação de erros, incluindo combinações de elementos como extrapolação de ruído zero (ZNE) e gate-twirling.

Começaremos carregando alguns pacotes.

# !pip install qiskit qiskit_aer qiskit_ibm_runtime
# !pip install jupyter
# !pip install matplotlib pylatexenc
import qiskit

qiskit.__version__

Output:

'2.0.2'
import qiskit_aer

qiskit_aer.__version__

Output:

'0.17.1'
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__

Output:

'0.40.1'

2. Simulação ruidosa sem mitigação de erros

O Qiskit Aer é um simulador clássico para computação quântica. Ele pode simular não apenas a execução ideal, mas também a execução ruidosa de circuitos quânticos. Este notebook demonstra como executar uma simulação com ruído usando o Qiskit Aer:

  1. Criar um modelo de ruído
  2. Crie um amostrador de ruído (simulador) com o modelo de ruído
  3. Execute um circuito quântico no amostrador com ruído
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])

2.1 Construa um circuito de teste

Consideramos circuitos de 1 qubit de brinquedo que apenas repetem portas X d vezes (d=0... 100) e medir o observável Z .

from qiskit.circuit import QuantumCircuit

MAX_DEPTH = 100
circuits = []
for d in range(MAX_DEPTH + 1):
    circ = QuantumCircuit(1)
    for _ in range(d):
        circ.x(0)
        circ.barrier(0)
    circ.measure_all()
    circuits.append(circ)

display(circuits[3].draw(output="mpl"))

Output:

Output of the previous code cell
from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp.from_list([("Z", 1.0)])
obs

Output:

SparsePauliOp(['Z'],
              coeffs=[1.+0.j])

2.2 Crie um modelo de ruído

Para fazer uma simulação com ruído, precisamos especificar NoiseModel. Nesta seção, mostraremos como criar o site NoiseModel .

Primeiro, precisamos definir erros quânticos (ou de leitura) para adicionar a um modelo de ruído.

from qiskit_aer.noise.errors import (
    coherent_unitary_error,
    amplitude_damping_error,
    ReadoutError,
)
from qiskit.circuit.library import RXGate

# Coherent (unitary) error: Over X-rotation error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.coherent_unitary_error.html#qiskit_aer.noise.coherent_unitary_error
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())

# Incoherent error: Amplitude dumping error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.amplitude_damping_error.html#qiskit_aer.noise.amplitude_damping_error
AMPLITUDE_DAMPING_PARAM = 0.02  # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)

# Readout (measurement) error: Readout error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.ReadoutError.html#qiskit_aer.noise.ReadoutError
PREP0_MEAS1 = 0.03  # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.08  # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
    [[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
from qiskit_aer.noise import NoiseModel

noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))

2.3 Construa um sampler ruidoso com o modelo de ruído

from qiskit_aer.primitives import SamplerV2 as Sampler

noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})

2.4 Execute circuitos quânticos no amostrador ruidoso

job = noisy_sampler.run(circuits, shots=400)
result = job.result()
result[0].data.meas.get_counts()

Output:

{'0': 389, '1': 11}

2.5 Resultados do gráfico

import matplotlib.pyplot as plt

plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    color="gray",
    linestyle="-",
)
plt.scatter(ds, [result[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o")
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

2.6 Simulação ideal

ideal_sampler = Sampler()
job_ideal = ideal_sampler.run(circuits)
result_ideal = job_ideal.result()
plt.title("Ideal simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds],
    color="gray",
    linestyle="-",
)
plt.scatter(
    ds, [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o"
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

2.7 Exercício

Ajustando o código abaixo,

  • Experimente 25x número de disparos (= 10_000 disparos) e verifique se é obtido um gráfico mais suave
  • Altere os parâmetros de ruído (OVER_ROTATION_ANGLE, AMPLITUDE_DAMPING_PARAM, PREP0_MEAS1 ou PREP1_MEAS0 ) e veja como o gráfico muda
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())
AMPLITUDE_DAMPING_PARAM = 0.02  # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)
PREP0_MEAS1 = 0.1  # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.05  # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
    [[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))
options = {
    "backend_options": {"noise_model": noise_model},
}
noisy_sampler = Sampler(options=options)
job = noisy_sampler.run(circuits, shots=400)
result = job.result()
plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    marker="o",
    linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

2.8 Simulação ruidosa mais realista

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService

service = QiskitRuntimeService()
real_backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)  # Eagle

Output:

<IBMBackend('ibm_strasbourg')>
aer = AerSimulator.from_backend(real_backend)
noisy_sampler = Sampler(mode=aer)
job = noisy_sampler.run(circuits)
result = job.result()
plt.title("Noisy simulation with noise model from real backend")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    marker="o",
    linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

3. Computação quântica real com mitigação de erros

Nesta parte, demonstramos como obter resultados com atenuação de erros (valores de expectativa) usando o Qiskit Estimator. Consideramos circuitos Trotterizados de 6 qubits para simular a evolução do tempo do modelo de Ising unidimensional e observamos como o erro é dimensionado em relação ao número de etapas de tempo.

backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)  # Eagle
backend

Output:

<IBMBackend('ibm_strasbourg')>
NUM_QUBITS = 6
NUM_TIME_STEPS = list(range(8))
RX_ANGLE = 0.1
RZZ_ANGLE = 0.1

3.1 Construa circuitos

# Build circuits with different number of time steps
circuits = []
for n_steps in NUM_TIME_STEPS:
    circ = QuantumCircuit(NUM_QUBITS)
    for i in range(n_steps):
        # rx layer
        for q in range(NUM_QUBITS):
            circ.rx(RX_ANGLE, q)
        # 1st rzz layer
        for q in range(1, NUM_QUBITS - 1, 2):
            circ.rzz(RZZ_ANGLE, q, q + 1)
        # 2nd rzz layer
        for q in range(0, NUM_QUBITS - 1, 2):
            circ.rzz(RZZ_ANGLE, q, q + 1)
    circ.barrier()  # need not to optimize the circuit
    # Uncompute stage
    for i in range(n_steps):
        for q in range(0, NUM_QUBITS - 1, 2):
            circ.rzz(-RZZ_ANGLE, q, q + 1)
        for q in range(1, NUM_QUBITS - 1, 2):
            circ.rzz(-RZZ_ANGLE, q, q + 1)
        for q in range(NUM_QUBITS):
            circ.rx(-RX_ANGLE, q)
    circuits.append(circ)

Para conhecer o resultado ideal com antecedência, usamos circuitos de computação-descomputação que consistem em um primeiro estágio em que o circuito original UU é aplicado e um segundo estágio em que ele é revertido UU^\dagger. Observe que o resultado ideal de tais circuitos será trivialmente o estado de entrada 000000|000000\rangle, que tem os valores de expectativa triviais para quaisquer observáveis de Pauli, por exemplo, IIIIIZ=1\langle IIIIIZ \rangle = 1.

# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")

Output:

Output of the previous code cell

Observação: Como mostrado acima, o circuito com kk etapas de tempo terá 4k4k camadas de porta de dois qubits.

obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obs

Output:

SparsePauliOp(['IIIIIZ'],
              coeffs=[1.+0.j])

3.2 Transpilar os circuitos

Transpilamos os circuitos para o backend com otimização (optimization_level=1).

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuits = pm.run(circuits)
display(isa_circuits[2].draw("mpl", idle_wires=False, fold=-1))

Output:

Output of the previous code cell

3.3 Executar usando o Estimador (com diferentes níveis de resiliência)

Definir o nível de resiliência (estimator.options.resilience_level) é a maneira mais fácil de aplicar a mitigação de erros ao usar o Qiskit Estimator. O Estimator suporta os seguintes níveis de resiliência (em 28/06/2024). Veja mais detalhes no guia de configuração da mitigação de erros.

image.png
from qiskit_ibm_runtime import Batch
from qiskit_ibm_runtime import EstimatorV2 as Estimator

jobs = []
job_ids = []
with Batch(backend=backend):
    for resilience_level in [0, 1, 2]:
        estimator = Estimator()
        estimator.options.resilience_level = resilience_level
        job = estimator.run(
            [(circ, obs.apply_layout(circ.layout)) for circ in isa_circuits]
        )
        job_ids.append(job.job_id())
        print(f"Job ID (rl={resilience_level}): {job.job_id()}")
        jobs.append(job)

Output:

Job ID (rl=0): d146vcnmya70008emprg
Job ID (rl=1): d146vdnqf56g0081sva0
Job ID (rl=2): d146ven5z6q00087c61g
# check job status
for job in jobs:
    print(job.status())

Output:

DONE
DONE
DONE
# REPLACE WITH YOUR OWN JOB IDS
jobs = [service.job(job_id) for job_id in job_ids]
# Get results
results = [job.result() for job in jobs]

3.4 Resultados do gráfico

plt.title("Error mitigation with different resilience levels")
labels = ["0 (No mitigation)", "1 (TREX)", "2 (ZNE + Gate twirling)"]
steps = NUM_TIME_STEPS
for result, label in zip(results, labels):
    plt.errorbar(
        x=steps,
        y=[result[s].data.evs for s in steps],
        yerr=[result[s].data.stds for s in steps],
        marker="o",
        linestyle="-",
        capsize=4,
        label=label,
    )
plt.hlines(
    1.0, min(steps), max(steps), linestyle="dashed", label="Ideal", colors="black"
)
plt.xlabel("Time steps")
plt.ylabel("Mitigated <IIIIIZ>")
plt.legend()
plt.show()

Output:

Output of the previous code cell

4 (Opcional) Personalizar opções de mitigação de erros

Podemos personalizar a aplicação de técnicas de atenuação de erros por meio de opções, conforme mostrado abaixo.

# TREX
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.shots_per_randomization = "auto"

# Gate twirling
estimator.options.twirling.enable_gates = True
# ZNE
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = [1, 3, 5]
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")

# Dynamical decoupling
estimator.options.dynamical_decoupling.enable = True  # Default: False
estimator.options.dynamical_decoupling.sequence_type = "XX"

# Other options
estimator.options.default_shots = 10_000

Consulte os guias e a referência de API a seguir para obter detalhes sobre as opções de atenuação de erros.

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.