Saltar al contenido principal

Depurar trabajos de Qiskit Runtime

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Se recomienda usar estas versiones o más recientes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17

Antes de enviar una carga de trabajo intensiva de Qiskit Runtime para ejecutarse en hardware, puedes usar la clase Neat (Noisy Estimator Analyzer Tool) de Qiskit Runtime para verificar que tu carga de trabajo de Estimator está configurada correctamente, que es probable que devuelva resultados precisos, que usa las opciones más apropiadas para el problema especificado, y más.

Neat Cliffordiza los circuitos de entrada para una simulación eficiente, conservando su estructura y profundidad. Los circuitos de Clifford sufren niveles de ruido similares y son un buen sustituto para estudiar el circuito original de interés.

Los siguientes ejemplos ilustran situaciones en las que Neat puede ser un recurso útil. Primero, importa los paquetes relevantes y autentícate en el servicio Qiskit Runtime.

Preparar el entorno

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime
import numpy as np
import random

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp

from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
from qiskit_ibm_runtime.debug_tools import Neat

from qiskit_aer.noise import NoiseModel, depolarizing_error
# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Generate a preset pass manager
# This will be used to convert the abstract circuit to an equivalent Instruction Set Architecture (ISA) circuit.
pm = generate_preset_pass_manager(backend=backend, optimization_level=0)

# Set the random seed
random.seed(10)

Inicializar un circuito objetivo

Considera un circuito de seis qubits que tiene las siguientes propiedades:

  • Alterna entre rotaciones RZ aleatorias y capas de puertas CNOT.
  • Tiene una estructura especular, es decir, aplica una unitaria U seguida de su inversa.
def generate_circuit(n_qubits, n_layers):
r"""
A function to generate a pseudo-random a circuit with ``n_qubits`` qubits and
``2*n_layers`` entangling layers of the type used in this notebook.
"""
# An array of random angles
angles = [
[random.random() for q in range(n_qubits)] for s in range(n_layers)
]

qc = QuantumCircuit(n_qubits)
qubits = list(range(n_qubits))

# do random circuit
for layer in range(n_layers):
# rotations
for q_idx, qubit in enumerate(qubits):
qc.rz(angles[layer][q_idx], qubit)

# cx gates
control_qubits = (
qubits[::2] if layer % 2 == 0 else qubits[1 : n_qubits - 1 : 2]
)
for qubit in control_qubits:
qc.cx(qubit, qubit + 1)

# undo random circuit
for layer in range(n_layers)[::-1]:
# cx gates
control_qubits = (
qubits[::2] if layer % 2 == 0 else qubits[1 : n_qubits - 1 : 2]
)
for qubit in control_qubits:
qc.cx(qubit, qubit + 1)

# rotations
for q_idx, qubit in enumerate(qubits):
qc.rz(-angles[layer][q_idx], qubit)

return qc

# Generate a random circuit
qc = generate_circuit(6, 3)
# Convert the abstract circuit to an equivalent ISA circuit.
isa_qc = pm.run(qc)

qc.draw("mpl", idle_wires=0)

Salida de la celda de código anterior

Elige operadores Z de Pauli simple como observables y úsalos para inicializar los bloques unificados primitivos (PUBs).

# Initialize the observables
obs = ["ZIIIII", "IZIIII", "IIZIII", "IIIZII", "IIIIZI", "IIIIIZ"]
print(f"Observables: {obs}")

# Map the observables to the backend's layout
isa_obs = [SparsePauliOp(o).apply_layout(isa_qc.layout) for o in obs]

# Initialize the PUBs, which consist of six-qubit circuits with `n_layers` 1, ..., 6
all_n_layers = [1, 2, 3, 4, 5, 6]

pubs = [(pm.run(generate_circuit(6, n)), isa_obs) for n in all_n_layers]
Observables: ['ZIIIII', 'IZIIII', 'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']

Cliffordizar los circuitos

Los circuitos PUB definidos anteriormente no son de Clifford, lo que los hace difíciles de simular clásicamente. Sin embargo, puedes usar el método to_clifford de Neat para mapearlos a circuitos de Clifford y obtener una simulación más eficiente. El método to_clifford es un envoltorio alrededor del paso de Transpiler ConvertISAToClifford, que también puede usarse de forma independiente. En particular, reemplaza las puertas de un solo qubit no Clifford del circuito original con puertas de un solo qubit de Clifford, pero no modifica las puertas de dos qubits, el número de qubits ni la profundidad del circuito.

Consulta Simulación eficiente de circuitos estabilizadores con las primitivas de Qiskit Aer para obtener más información sobre la simulación de circuitos de Clifford. Primero, inicializa Neat.

# You could specify a custom `NoiseModel` here. If `None`, `Neat`
# pulls the noise model from the given backend
noise_model = None

# Initialize `Neat`
analyzer = Neat(backend, noise_model)

A continuación, Cliffordiza los PUBs.

clifford_pubs = analyzer.to_clifford(pubs)

clifford_pubs[0].circuit.draw("mpl", idle_wires=0)

Salida de la celda de código anterior

Aplicación 1: Analizar el impacto del ruido en las salidas del circuito

Este ejemplo muestra cómo usar Neat para estudiar el impacto de distintos modelos de ruido en los PUBs en función de la profundidad del circuito, ejecutando simulaciones en condiciones ideales (ideal_sim) y con ruido (noisy_sim). Esto puede ser útil para establecer expectativas sobre la calidad de los resultados experimentales antes de ejecutar un trabajo en una QPU. Para obtener más información sobre los modelos de ruido, consulta Simulación exacta y con ruido con las primitivas de Qiskit Aer.

Los resultados simulados admiten operaciones matemáticas y, por tanto, pueden compararse entre sí (o con resultados experimentales) para calcular figuras de mérito.

precaución

Una QPU puede verse afectada por distintos tipos de ruido. El modelo de ruido de Qiskit Aer utilizado aquí solo simula algunos de ellos y, por lo tanto, es probable que sea menos severo que el ruido en una QPU real.

Para más detalles sobre qué errores se incluyen al inicializar un modelo de ruido desde una QPU, consulta la referencia de la API de Aer NoiseModel.

Comienza realizando simulaciones clásicas ideales y con ruido.

# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)
print(f"Ideal results:\n {ideal_results}\n")

# Perform a noisy simulation with the backend's noise model
noisy_results = analyzer.noisy_sim(clifford_pubs)
print(f"Noisy results:\n {noisy_results}\n")
Ideal results:
NeatResult([NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.]))])
Noisy results:
NeatResult([NeatPubResult(vals=array([0.99023438, 0.99609375, 0.9921875 , 0.99023438, 0.99414062,
0.99414062])), NeatPubResult(vals=array([0.984375 , 0.99414062, 0.98242188, 0.98828125, 0.98632812,
0.99414062])), NeatPubResult(vals=array([0.96679688, 0.97070312, 0.95898438, 0.97851562, 0.98046875,
0.98828125])), NeatPubResult(vals=array([0.9453125 , 0.953125 , 0.97070312, 0.96875 , 0.98242188,
0.99023438])), NeatPubResult(vals=array([0.93164062, 0.9375 , 0.953125 , 0.96875 , 0.96484375,
0.98046875])), NeatPubResult(vals=array([0.92578125, 0.921875 , 0.93359375, 0.953125 , 0.95898438,
0.9765625 ]))])

A continuación, aplica operaciones matemáticas para calcular la diferencia absoluta. El resto de la guía usa la diferencia absoluta como figura de mérito para comparar los resultados ideales con los resultados con ruido o experimentales, aunque se pueden definir figuras de mérito similares.

La diferencia absoluta muestra que el impacto del ruido crece con el tamaño de los circuitos.

# Figure of merit: Absolute difference
def rdiff(res1, re2):
r"""The absolute difference between `res1` and re2`.

--> The closer to `0`, the better.
"""
d = abs(res1 - re2)
return np.round(d.vals * 100, 2)

for idx, (ideal_res, noisy_res) in enumerate(
zip(ideal_results, noisy_results)
):
vals = rdiff(ideal_res, noisy_res)

# Print the mean absolute difference for the observables
mean_vals = np.round(np.mean(vals), 2)
print(
f"Mean absolute difference between ideal and noisy results for circuits with {all_n_layers[idx]} layers:\n {mean_vals}%\n"
)
Mean absolute difference between ideal and noisy results for circuits with 1 layers:
0.72%

Mean absolute difference between ideal and noisy results for circuits with 2 layers:
1.17%

Mean absolute difference between ideal and noisy results for circuits with 3 layers:
2.6%

Mean absolute difference between ideal and noisy results for circuits with 4 layers:
3.16%

Mean absolute difference between ideal and noisy results for circuits with 5 layers:
4.4%

Mean absolute difference between ideal and noisy results for circuits with 6 layers:
5.5%

Puedes seguir estas pautas aproximadas y simplificadas para mejorar circuitos de este tipo:

  • Si la diferencia absoluta media es mayor que el 90%, la mitigación probablemente no ayudará.
  • Si la diferencia absoluta media es menor que el 90%, la Amplificación Probabilística de Errores (PEA) probablemente podrá mejorar los resultados.
  • Si la diferencia absoluta media es menor que el 80%, ZNE con plegado de puertas también podrá mejorar los resultados.

Dado que todas las diferencias absolutas anteriores son menores que el 90%, aplicar PEA al circuito original debería mejorar la calidad de sus resultados. Puedes especificar distintos modelos de ruido en el analizador. El siguiente ejemplo realiza la misma prueba pero añade un modelo de ruido personalizado.

# Set up a noise model with strength 0.02 on every two-qubit gate
noise_model = NoiseModel()
for qubits in backend.coupling_map:
noise_model.add_quantum_error(
depolarizing_error(0.02, 2), ["ecr", "cx"], qubits
)

# Update the analyzer's noise model
analyzer.noise_model = noise_model

# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)

# Perform a noisy simulation with the backend's noise model
noisy_results = analyzer.noisy_sim(clifford_pubs)

# Compare the results
for idx, (ideal_res, noisy_res) in enumerate(
zip(ideal_results, noisy_results)
):
values = rdiff(ideal_res, noisy_res)

# Print the mean absolute difference for the observables
mean_values = np.round(np.mean(values), 2)
print(
f"Mean absolute difference between ideal and noisy results for circuits with {all_n_layers[idx]} layers:\n {mean_values}%\n"
)
Mean absolute difference between ideal and noisy results for circuits with 1 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 2 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 3 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 4 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 5 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 6 layers:
0.0%

Como se muestra, dado un modelo de ruido, puedes intentar cuantificar el impacto del ruido en los PUBs de interés (en su versión Cliffordizada) antes de ejecutarlos en una QPU.

Aplicación 2: Comparar distintas estrategias

Este ejemplo usa Neat para ayudar a identificar las mejores opciones para tus PUBs. Para ello, considera ejecutar un problema de estimación con PEA, que no se puede simular con qiskit_aer. Puedes usar Neat para determinar qué factores de amplificación de ruido funcionarán mejor y, luego, usar esos factores al ejecutar el experimento original en una QPU.

# Generate a circuit with six qubits and six layers
isa_qc = pm.run(generate_circuit(6, 3))

# Use the same observables as previously
pubs = [(isa_qc, isa_obs)]
clifford_pubs = analyzer.to_clifford(pubs)
noise_factors = [
[1, 1.1],
[1, 1.1, 1.2],
[1, 1.5, 2],
[1, 1.5, 2, 2.5, 3],
[1, 4],
]
# Run the PUBs on a QPU
estimator = Estimator(backend)
estimator.options.default_shots = 100000
estimator.options.twirling.enable_gates = True
estimator.options.twirling.enable_measure = True
estimator.options.twirling.shots_per_randomization = 100
estimator.options.resilience.measure_mitigation = True
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.amplifier = "pea"

jobs = []
for factors in noise_factors:
estimator.options.resilience.zne.noise_factors = factors
jobs.append(estimator.run(clifford_pubs))

results = [job.result() for job in jobs]
# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)
# Look at the mean absolute difference to quickly tell the best choice for your options
for factors, res in zip(noise_factors, results):
d = rdiff(ideal_results[0], res[0])
print(
f"Mean absolute difference for factors {factors}:\n {np.round(np.mean(d), 2)}%\n"
)
Mean absolute difference for factors [1, 1.1]:
6.83%

Mean absolute difference for factors [1, 1.1, 1.2]:
8.76%

Mean absolute difference for factors [1, 1.5, 2]:
8.03%

Mean absolute difference for factors [1, 1.5, 2, 2.5, 3]:
10.17%

Mean absolute difference for factors [1, 4]:
8.02%

El resultado con la diferencia más pequeña indica qué opciones elegir.

Próximos pasos

Recomendaciones