Esecuzione del debug dei processi con NEAT
Il codice di questa pagina è stato sviluppato in base ai seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.5.1 qiskit-ibm-runtime~=0.47.0 qiskit-aer~=0.17
È possibile utilizzare la Neat classe per analizzare l'impatto del rumore su un carico di lavoro di Estimator. Per la verifica della sintassi, utilizza la modalità di test locale.
Neat utilizzo della classe
Prima di inviare un carico di lavoro che richiede un elevato impiego di risorse per l'esecuzione sull'hardware, è possibile utilizzare la classe NEAT (Noisy Estimator Analyzer Tool) di IBM Quantum Compute per verificare che il carico di lavoro dell'Estimator sia configurato correttamente, che sia in grado di restituire risultati accurati, che utilizzi le opzioni più appropriate per il problema specificato e altro ancora.
Neat Cliffordizza i circuiti di ingresso per una simulazione efficiente, pur mantenendo la sua struttura e profondità. I circuiti Clifford soffrono di livelli simili di rumore e sono un buon proxy per studiare il circuito originale di interesse.
Per prima cosa, importa i pacchetti necessari ed effettua l'autenticazione al servizio di comput IBM Quantum.
Prepara l'ambiente
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 circuito di destinazione
Si consideri un circuito a sei qubit con le seguenti proprietà:
- Alterna rotazioni
RZcasuali e strati di porteCNOT. - Ha una struttura speculare, cioè applica un
Uunitario seguito 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)Output:
Scegliere gli operatori single-Pauli Z come osservabili e usarli per inizializzare i blocchi primitivi unificati (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']
Cliffordizza i circuiti
I circuiti PUB definiti in precedenza non sono Clifford, il che li rende difficili da simulare in modo classico. Tuttavia, è possibile utilizzare il metodo Neat to_clifford per mapparli in circuiti di Clifford per una simulazione più efficiente. Il metodo to_clifford è un wrapper attorno al passaggio ConvertISAToClifford che può essere usato anche indipendentemente. In particolare, sostituisce le porte a singolo qubit non Clifford del circuito originale con porte a singolo qubit Clifford, ma non modifica le porte a due qubit, il numero di qubit o la profondità del circuito.
Per ulteriori informazioni sulla simulazione dei circuiti Clifford, vedere Simulazione efficiente di circuiti stabilizzatori con le primitive Aer di Qiskit.
Per prima cosa, inizializzare 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)Quindi, Cliffordizzare i PUB.
clifford_pubs = analyzer.to_clifford(pubs)
clifford_pubs[0].circuit.draw("mpl", idle_wires=0)Output:
Applicazione 1: Analizzare l'impatto del rumore sulle uscite del circuito
Questo esempio mostra come utilizzare Neat per studiare l'impatto di diversi modelli di rumore sui PUB in funzione della profondità del circuito, eseguendo simulazioni sia in condizioni ideali (ideal_sim) che rumorose (noisy_sim). Questo può essere utile per definire le aspettative sulla qualità dei risultati sperimentali prima di eseguire un lavoro su una QPU. Per ulteriori informazioni sui modelli di rumore, consultare Simulazione esatta e rumorosa con le primitive Aer di Qiskit.
I risultati simulati supportano le operazioni matematiche e possono quindi essere confrontati tra loro (o con i risultati sperimentali) per calcolare le cifre di merito.
Una QPU può essere influenzata da diversi tipi di rumore. Il modello di rumore di Qiskit Aer utilizzato qui simula solo alcuni di essi e quindi è probabile che il rumore sia meno grave di quello di una QPU reale.
Per i dettagli su quali errori vengono inclusi quando si inizializza un modello di rumore da una QPU, si veda il riferimento all'API di Aer NoiseModel API.
Iniziare 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")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.9921875 , 0.99414062, 1. , 0.99609375, 0.99609375,
0.99609375])), NeatPubResult(vals=array([0.97460938, 0.97851562, 0.98046875, 0.99609375, 0.99414062,
0.99609375])), NeatPubResult(vals=array([0.96679688, 0.98242188, 0.97460938, 0.98046875, 0.99414062,
0.99609375])), NeatPubResult(vals=array([0.96679688, 0.96289062, 0.97460938, 0.9765625 , 0.98632812,
0.99414062])), NeatPubResult(vals=array([0.921875 , 0.92773438, 0.95898438, 0.9609375 , 0.97265625,
0.98242188])), NeatPubResult(vals=array([0.90820312, 0.92773438, 0.95507812, 0.9609375 , 0.97460938,
0.984375 ]))])
Successivamente, applicare le operazioni matematiche per calcolare la differenza assoluta. Il resto della guida utilizza la differenza assoluta come cifra di merito per confrontare i risultati ideali con quelli rumorosi o sperimentali, ma è possibile impostare cifre di merito simili.
La differenza assoluta mostra che l'impatto del rumore cresce con le dimensioni dei circuiti.
# Figure of merit: Absolute difference
def rdiff(res1, res2):
r"""The absolute difference between `res1` and res2`.
--> The closer to `0`, the better.
"""
d = abs(res1 - res2)
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.42%
Mean absolute difference between ideal and noisy results for circuits with 2 layers:
1.34%
Mean absolute difference between ideal and noisy results for circuits with 3 layers:
1.76%
Mean absolute difference between ideal and noisy results for circuits with 4 layers:
2.31%
Mean absolute difference between ideal and noisy results for circuits with 5 layers:
4.59%
Mean absolute difference between ideal and noisy results for circuits with 6 layers:
4.82%
Per migliorare i circuiti di questo tipo è possibile seguire queste linee guida approssimative e semplificate:
- Se la differenza media assoluta è superiore al 90%, è probabile che la mitigazione non sia utile.
- Se la differenza assoluta media è inferiore al 90%, l' amplificazione probabilistica degli errori (PEA) sarà probabilmente in grado di migliorare i risultati.
- Se la differenza media assoluta è inferiore all'80%, anche la ZNE con ripiegamento delle porte sarà probabilmente in grado di migliorare i risultati.
Poiché tutte le differenze assolute di cui sopra sono inferiori al 90%, applicando la PEA al circuito originale si spera di migliorare la qualità dei risultati.
Nell'analizzatore è possibile specificare diversi modelli di rumore. L'esempio seguente 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", "cz"], 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:
3.78%
Mean absolute difference between ideal and noisy results for circuits with 2 layers:
6.38%
Mean absolute difference between ideal and noisy results for circuits with 3 layers:
13.84%
Mean absolute difference between ideal and noisy results for circuits with 4 layers:
18.88%
Mean absolute difference between ideal and noisy results for circuits with 5 layers:
22.95%
Mean absolute difference between ideal and noisy results for circuits with 6 layers:
26.2%
Come mostrato, dato un modello di rumore, è possibile cercare di quantificare l'impatto del rumore sui PUB (versione Cliffordizzata dei PUB) di interesse prima di eseguirli su una QPU.
Applicazione 2: Confronto tra diverse strategie
Questo esempio utilizza Neat per aiutare a identificare le opzioni migliori per i vostri PUB. A tal fine, si consideri l'esecuzione di un problema di stima con PEA, che non può essere simulato con qiskit_aer. È possibile utilizzare Neat per determinare quali fattori di amplificazione del rumore funzionano meglio, quindi utilizzare tali fattori quando si esegue l'esperimento originale su 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]:
3.92%
Mean absolute difference for factors [1, 1.1, 1.2]:
3.64%
Mean absolute difference for factors [1, 1.5, 2]:
4.41%
Mean absolute difference for factors [1, 1.5, 2, 2.5, 3]:
3.84%
Mean absolute difference for factors [1, 4]:
5.14%
Il risultato con la differenza minore suggerisce quali opzioni scegliere.
Passi successivi
- Leggi una panoramica sugli strumenti di debug di Qiskit.
- Scopri la simulazione esatta e rumorosa con le primitive Aer di Qiskit.
- Scopri le opzioni disponibili per Compute su IBM Quantum.
- Scopri le tecniche di mitigazione e soppressione degli errori.
- Visitate l'argomento Transpile con i pass manager.
- Scopri come traspilare i circuiti nell'ambito dei flussi di lavoro dei pattern di Qiskit.
- Consulta la documentazione relativa alle API degli strumenti di debug.