Debug dei job Qiskit Runtime
Versioni dei pacchetti
Il codice in questa pagina è stato sviluppato con i seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
Prima di inviare un workload Qiskit Runtime che richiede molte risorse per l'esecuzione su hardware, puoi utilizzare la classe Neat (Noisy Estimator Analyzer Tool) di Qiskit Runtime per verificare che il tuo workload Estimator sia configurato correttamente, che sia probabile che restituisca risultati accurati, che utilizzi le opzioni più appropriate per il problema specificato e altro ancora.
Neat "Cliffordizza" i circuit in input per una simulazione efficiente, mantenendone la struttura e la profondità . I circuit Clifford subiscono livelli di rumore simili e rappresentano un buon proxy per studiare il circuit originale di interesse.
I seguenti esempi illustrano situazioni in cui Neat può essere una risorsa utile.
Prima di tutto, importa i pacchetti rilevanti e autenticati al servizio Qiskit Runtime.
Preparare l'ambiente​
# 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)
Inizializzare un circuit di destinazione​
Considera un circuit a sei qubit con le seguenti proprietà :
- Alterna rotazioni
RZcasuali e strati di gateCNOT. - Ha una struttura a specchio, ovvero applica un unitario
Useguito dal suo 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)
Scegli operatori Z di Pauli singoli come osservabili e usali per inizializzare i primitive unified bloc (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]
Observables: ['ZIIIII', 'IZIIII', 'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']
Cliffordizzare i circuit​
I circuit PUB definiti in precedenza non sono Clifford, il che li rende difficili da simulare classicamente. Tuttavia, puoi usare il metodo to_clifford di Neat per mapparli su circuit Clifford per una simulazione più efficiente. Il metodo to_clifford è un wrapper attorno al pass del Transpiler ConvertISAToClifford, che può anche essere usato in modo indipendente. In particolare, sostituisce i gate a singolo qubit non Clifford nel circuit originale con gate a singolo qubit Clifford, ma non modifica i gate a due qubit, il numero di qubit o la profondità del circuit.
Consulta Simulazione efficiente di circuit stabilizzatori con le primitive Qiskit Aer per maggiori informazioni sulla simulazione di circuit Clifford.
Prima di tutto, inizializza 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)
Ora, Cliffordizza i PUB.
clifford_pubs = analyzer.to_clifford(pubs)
clifford_pubs[0].circuit.draw("mpl", idle_wires=0)
Applicazione 1: Analizzare l'impatto del rumore sugli output del circuit​
Questo esempio mostra come usare Neat per studiare l'impatto di diversi modelli di rumore sui PUB in funzione della profondità del circuit, eseguendo simulazioni sia in condizioni ideali (ideal_sim) sia rumorose (noisy_sim). Questo può essere utile per impostare aspettative sulla qualità dei risultati sperimentali prima di eseguire un job su un QPU. Per saperne di più sui modelli di rumore, consulta Simulazione esatta e rumorosa con le primitive Qiskit Aer.
I risultati simulati supportano operazioni matematiche, e possono quindi essere confrontati tra loro (o con risultati sperimentali) per calcolare figure di merito.
Un QPU può essere influenzato da diversi tipi di rumore. Il modello di rumore Qiskit Aer utilizzato qui ne simula solo alcuni e quindi è probabile che sia meno severo del rumore su un QPU reale.
Per i dettagli su quali errori vengono inclusi quando si inizializza un modello di rumore da un QPU, consulta il riferimento API di Aer NoiseModel.
Inizia eseguendo simulazioni classiche ideali e rumorose.
# 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 ]))])
Ora applica operazioni matematiche per calcolare la differenza assoluta. Il resto della guida usa la differenza assoluta come figura di merito per confrontare i risultati ideali con quelli rumorosi o sperimentali, ma è possibile impostare figure di merito simili.
La differenza assoluta mostra che l'impatto del rumore cresce con la dimensione dei circuit.
# 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%
Puoi seguire queste linee guida approssimative e semplificate per migliorare i circuit di questo tipo:
- Se la differenza assoluta media è superiore al 90%, la mitigazione probabilmente non sarà di aiuto.
- Se la differenza assoluta media è inferiore al 90%, Probabilistic Error Amplification (PEA) sarà probabilmente in grado di migliorare i risultati.
- Se la differenza assoluta media è inferiore all'80%, anche ZNE con gate folding sarà probabilmente in grado di migliorare i risultati.
Poiché tutte le differenze assolute di cui sopra sono inferiori al 90%, applicare PEA al circuit originale dovrebbe migliorare la qualità dei suoi risultati. Puoi specificare diversi modelli di rumore nell'analizzatore. Il seguente esempio esegue lo stesso test ma aggiunge un modello di rumore personalizzato.
# 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%
Come mostrato, dato un modello di rumore, puoi provare a quantificare l'impatto del rumore sui PUB di interesse (nella loro versione Cliffordizzata) prima di eseguirli su un QPU.
Applicazione 2: Confrontare diverse strategie​
Questo esempio usa Neat per aiutare a identificare le opzioni migliori per i tuoi PUB. Per farlo, considera l'esecuzione di un problema di stima con PEA, che non può essere simulato con qiskit_aer. Puoi usare Neat per determinare quali fattori di amplificazione del rumore funzioneranno meglio, quindi usare quei fattori quando esegui l'esperimento originale su un 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%
Il risultato con la differenza minore suggerisce quali opzioni scegliere.
Passi successivi​
- Scopri la Simulazione esatta e rumorosa con le primitive Qiskit Aer.
- Scopri le opzioni disponibili di Qiskit Runtime.
- Scopri le tecniche di mitigazione e soppressione degli errori.
- Visita il topic Transpilare con i pass manager.
- Scopri come transpilare i circuit come parte dei flussi di lavoro dei pattern Qiskit usando Qiskit Runtime.
- Consulta la documentazione API degli strumenti di debug.