Depurar trabajos de Qiskit Runtime
El código de esta página se ha desarrollado teniendo en cuenta los siguientes requisitos. Recomendamos utilizar estas versiones o versiones más recientes.
qiskit[all]~=2.5.0 qiskit-ibm-runtime~=0.47.0 qiskit-aer~=0.17
Puedes utilizar la Neat clase para analizar el impacto del ruido en una carga de trabajo de Estimator. Para verificar la sintaxis, utiliza el modo de prueba local.
Neat uso de la clase
Antes de enviar una carga de trabajo de Qiskit Runtime que consuma muchos recursos para que se ejecute en hardware, puede utilizar la clase Qiskit Runtime Neat (Noisy Estimator Analyzer Tool) para verificar que la carga de trabajo de Estimator está configurada correctamente, que es probable que devuelva resultados precisos, que utiliza las opciones más adecuadas para el problema especificado, etc.
Neat Cliffordiza los circuitos de entrada para una simulación eficaz, conservando su estructura y profundidad. Los circuitos Clifford sufren niveles similares de ruido y son un buen sustituto para estudiar el circuito original de interés.
En primer lugar, importe los paquetes pertinentes y autentíquese en el servicio Qiskit Runtime.
Prepare el entorno
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
Consideremos un circuito de seis qubits que tiene las siguientes propiedades:
- Alterna entre rotaciones aleatorias
RZy capas de puertasCNOT. - Tiene una estructura especular, es decir, aplica un
Uunitario seguido de su inverso.
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)Output:
Elija operadores Z de un solo Pauli como observables y utilícelos para inicializar los bloques unificados primitivos (PUB).
# 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]Output:
Observables: ['ZIIIII', 'IZIIII', 'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']
Cliffordizar los circuitos
Los circuitos PUB definidos anteriormente no son Clifford, lo que dificulta su simulación clásica. Sin embargo, puede utilizar el método Neat to_clifford para convertirlos en circuitos Clifford y conseguir una simulación más eficaz. El método to_clifford es una envoltura del método ConvertISAToClifford que también puede utilizarse de forma independiente. En concreto, sustituye las puertas single-qubit no Clifford del circuito original por puertas single-qubit Clifford, pero no muta las puertas two-qubit, el número de qubits ni la profundidad del circuito.
Consulte Simulación eficiente de circuitos estabilizadores con primitivas Qiskit Aer para obtener más información sobre la simulación de circuitos Clifford.
En primer lugar, 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 PUB.
clifford_pubs = analyzer.to_clifford(pubs)
clifford_pubs[0].circuit.draw("mpl", idle_wires=0)Output:
Aplicación 1: Analizar el impacto del ruido en las salidas del circuito
Este ejemplo muestra cómo utilizar Neat para estudiar el impacto de diferentes modelos de ruido en los PUB en función de la profundidad del circuito, ejecutando simulaciones tanto en condiciones ideales (ideal_sim) como ruidosas (noisy_sim). Esto puede resultar ú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, consulte Simulación exacta y ruidosa con primitivas Aer de Qiskit.
Los resultados simulados admiten operaciones matemáticas, por lo que pueden compararse entre sí (o con resultados experimentales) para calcular cifras de mérito.
Una QPU puede verse afectada por distintos tipos de ruido. El modelo de ruido Qiskit Aer utilizado aquí sólo simula algunos de ellos y, por tanto, es probable que sea menos grave que el ruido de una QPU real.
Para más detalles sobre qué errores se incluyen al inicializar un modelo de ruido desde una QPU, consulte la referencia de la API Aer NoiseModel API reference.
Comience realizando simulaciones clásicas ideales y ruidosas.
# 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")Output:
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.99414062, 0.99414062, 0.99804688, 0.99609375, 0.98828125,
0.99023438])), NeatPubResult(vals=array([0.9765625 , 0.97851562, 0.9765625 , 0.98632812, 0.98828125,
0.99414062])), NeatPubResult(vals=array([0.953125 , 0.9609375 , 0.9609375 , 0.97265625, 0.97851562,
0.97460938])), NeatPubResult(vals=array([0.953125 , 0.94335938, 0.94140625, 0.97070312, 0.96679688,
0.99414062])), NeatPubResult(vals=array([0.94335938, 0.92382812, 0.95703125, 0.96875 , 0.96679688,
0.97265625])), NeatPubResult(vals=array([0.92773438, 0.90429688, 0.91210938, 0.93554688, 0.95117188,
0.97265625]))])
A continuación, aplica operaciones matemáticas para calcular la diferencia absoluta. En el resto de la guía se utiliza la diferencia absoluta como cifra de mérito para comparar resultados ideales con resultados ruidosos o experimentales, pero pueden establecerse cifras 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 "
f"for circuits with {all_n_layers[idx]} layers:\n {mean_vals}%\n"
)Output:
Mean absolute difference between ideal and noisy results for circuits with 1 layers:
0.65%
Mean absolute difference between ideal and noisy results for circuits with 2 layers:
1.66%
Mean absolute difference between ideal and noisy results for circuits with 3 layers:
3.32%
Mean absolute difference between ideal and noisy results for circuits with 4 layers:
3.84%
Mean absolute difference between ideal and noisy results for circuits with 5 layers:
4.46%
Mean absolute difference between ideal and noisy results for circuits with 6 layers:
6.61%
Puedes seguir estas pautas aproximadas y simplificadas para mejorar los circuitos de este tipo:
- Si la diferencia absoluta media es superior al 90%, es probable que la mitigación no sirva de nada.
- Si la diferencia absoluta media es inferior al 90%, es probable que la Amplificación Probabilística de Errores (PEA) pueda mejorar los resultados.
- Si la diferencia absoluta media es inferior al 80%, es probable que la ZNE con plegado de compuerta también pueda mejorar los resultados.
Dado que todas las diferencias absolutas anteriores son inferiores al 90%, es de esperar que la aplicación de PEA al circuito original mejore la calidad de sus resultados.
Puede especificar diferentes 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 "
f"for circuits with {all_n_layers[idx]} layers:\n {mean_values}%\n"
)Output:
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, se puede tratar de cuantificar el impacto del ruido en la (versión Cliffordizada de los) PUBs de interés antes de ejecutarlos en una QPU.
Aplicación 2: Comparar diferentes estrategias
Este ejemplo utiliza Neat para ayudar a identificar las mejores opciones para sus PUBs. Para ello, considere la posibilidad de ejecutar un problema de estimación con PEA, que no puede simularse con qiskit_aer. Puede utilizar Neat para ayudar a determinar qué factores de amplificación de ruido funcionarán mejor, y luego utilizar esos factores cuando ejecute 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 determine
# 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 "
f"{factors}:\n {np.round(np.mean(d), 2)}%\n"
)Output:
Mean absolute difference for factors [1, 1.1]:
4.57%
Mean absolute difference for factors [1, 1.1, 1.2]:
5.24%
Mean absolute difference for factors [1, 1.5, 2]:
2.62%
Mean absolute difference for factors [1, 1.5, 2, 2.5, 3]:
2.4%
Mean absolute difference for factors [1, 4]:
2.05%
El resultado con la menor diferencia sugiere qué opciones elegir.
Próximos pasos
- Lee una descripción general de las herramientas de depuración de Qiskit.
- Aprenda sobre la simulación exacta y ruidosa con las primitivas Aer de Qiskit.
- Infórmese sobre las opciones disponibles de Qiskit Runtime.
- Aprenda sobre las técnicas de mitigación y supresión de errores.
- Visita el tema Transpile con gestores de pases.
- Aprenda a transpilear circuitos como parte de los flujos de trabajo de patrones de Qiskit utilizando Qiskit Runtime.
- Revisa la documentación de la API de herramientas de depuración.