Qiskit Runtime Jobs debuggen
Paketversionen
Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese Versionen oder neuere zu verwenden.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
Bevor du eine ressourcenintensive Qiskit Runtime-Arbeitslast zur Ausführung auf Hardware einreichst, kannst du die Qiskit Runtime-Klasse Neat (Noisy Estimator Analyzer Tool) verwenden, um zu überprüfen, ob deine Estimator-Arbeitslast korrekt eingerichtet ist, wahrscheinlich genaue Ergebnisse liefert, die am besten geeigneten Optionen für das angegebene Problem verwendet und mehr.
Neat cliffordisiert die Eingabe-Circuits für eine effiziente Simulation, während deren Struktur und Tiefe erhalten bleiben. Clifford-Circuits leiden unter ähnlichen Rauschpegeln und sind ein guter Stellvertreter für die Untersuchung des ursprünglichen Circuits von Interesse.
Die folgenden Beispiele veranschaulichen Situationen, in denen Neat eine nützliche Ressource sein kann.
Importiere zunächst die relevanten Pakete und authentifiziere dich beim Qiskit Runtime-Dienst.
Umgebung vorbereiten
# 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)
Einen Ziel-Circuit initialisieren
Betrachte einen Sechs-Qubit-Circuit mit den folgenden Eigenschaften:
- Wechselt zwischen zufälligen
RZ-Rotationen und Schichten vonCNOT-Gates. - Hat eine Spiegelstruktur, d. h., er wendet eine unitäre Transformation
Ugefolgt von ihrer Inversen an.
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)
Wähle einzelne Pauli-Z-Operatoren als Observablen und verwende sie zur Initialisierung der Primitive Unified Blocs (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']
Die Circuits cliffordisieren
Die zuvor definierten PUB-Circuits sind nicht Clifford, was ihre klassische Simulation erschwert. Du kannst jedoch die Neat-Methode to_clifford verwenden, um sie für eine effizientere Simulation auf Clifford-Circuits abzubilden. Die Methode to_clifford ist ein Wrapper um den Transpiler-Pass ConvertISAToClifford, der auch unabhängig verwendet werden kann. Insbesondere ersetzt er nicht-Clifford-Einzel-Qubit-Gates im ursprünglichen Circuit durch Clifford-Einzel-Qubit-Gates, mutiert jedoch nicht die Zwei-Qubit-Gates, die Anzahl der Qubits oder die Circuit-Tiefe.
Weitere Informationen zur Clifford-Circuit-Simulation findest du unter Effiziente Simulation von Stabilizer-Circuits mit Qiskit Aer-Primitiven.
Initialisiere zunächst 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)
Cliffordisiere dann die PUBs.
clifford_pubs = analyzer.to_clifford(pubs)
clifford_pubs[0].circuit.draw("mpl", idle_wires=0)
Anwendung 1: Den Einfluss von Rauschen auf die Circuit-Ausgaben analysieren
Dieses Beispiel zeigt, wie man Neat verwendet, um den Einfluss verschiedener Rauschmodelle auf PUBs als Funktion der Circuit-Tiefe zu untersuchen, indem Simulationen sowohl unter idealen (ideal_sim) als auch unter verrauschten (noisy_sim) Bedingungen durchgeführt werden. Dies kann nützlich sein, um Erwartungen an die Qualität der experimentellen Ergebnisse zu setzen, bevor ein Job auf einem QPU ausgeführt wird. Weitere Informationen zu Rauschmodellen findest du unter Exakte und verrauschte Simulation mit Qiskit Aer-Primitiven.
Die simulierten Ergebnisse unterstützen mathematische Operationen und können daher miteinander (oder mit experimentellen Ergebnissen) verglichen werden, um Gütekriterien zu berechnen.
Ein QPU kann von verschiedenen Arten von Rauschen betroffen sein. Das hier verwendete Qiskit Aer-Rauschmodell simuliert nur einige davon und ist daher wahrscheinlich weniger stark als das Rauschen auf einem echten QPU.
Einzelheiten dazu, welche Fehler bei der Initialisierung eines Rauschmodells aus einem QPU enthalten sind, findest du in der Aer-NoiseModel-API-Referenz.
Führe zunächst ideale und verrauschte klassische Simulationen durch.
# 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 ]))])
Wende anschließend mathematische Operationen an, um die absolute Differenz zu berechnen. Im weiteren Verlauf dieses Leitfadens wird die absolute Differenz als Gütekriterium verwendet, um ideale Ergebnisse mit verrauschten oder experimentellen Ergebnissen zu vergleichen. Es können jedoch ähnliche Gütekriterien eingerichtet werden.
Die absolute Differenz zeigt, dass der Einfluss von Rauschen mit der Größe der Circuits wächst.
# 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%
Du kannst diese groben und vereinfachten Richtlinien befolgen, um Circuits dieser Art zu verbessern:
- Wenn die mittlere absolute Differenz größer als 90 % ist, wird Mitigation wahrscheinlich nicht helfen.
- Wenn die mittlere absolute Differenz kleiner als 90 % ist, wird Probabilistic Error Amplification (PEA) die Ergebnisse wahrscheinlich verbessern können.
- Wenn die mittlere absolute Differenz kleiner als 80 % ist, wird auch ZNE mit Gate-Faltung die Ergebnisse voraussichtlich verbessern können.
Da alle obigen absoluten Differenzen kleiner als 90 % sind, wird die Anwendung von PEA auf den ursprünglichen Circuit hoffentlich die Qualität seiner Ergebnisse verbessern. Du kannst verschiedene Rauschmodelle im Analyzer angeben. Das folgende Beispiel führt denselben Test durch, fügt aber ein benutzerdefiniertes Rauschmodell hinzu.
# 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%
Wie gezeigt, kannst du bei einem gegebenen Rauschmodell versuchen, den Einfluss von Rauschen auf die (cliffordisierte Version der) PUBs von Interesse zu quantifizieren, bevor du sie auf einem QPU ausführst.
Anwendung 2: Verschiedene Strategien benchmarken
Dieses Beispiel verwendet Neat, um die besten Optionen für deine PUBs zu ermitteln. Betrachte dazu die Ausführung eines Schätzproblems mit PEA, das nicht mit qiskit_aer simuliert werden kann. Du kannst Neat verwenden, um zu bestimmen, welche Rauschverstärkungsfaktoren am besten geeignet sind, und diese Faktoren dann bei der Ausführung des ursprünglichen Experiments auf einem QPU verwenden.
# 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%
Das Ergebnis mit der kleinsten Differenz gibt an, welche Optionen zu wählen sind.
Nächste Schritte
- Erfahre mehr über Exakte und verrauschte Simulation mit Qiskit Aer-Primitiven.
- Erfahre mehr über verfügbare Qiskit Runtime-Optionen.
- Erfahre mehr über Fehlerminderung und Fehlerunterdrückungstechniken.
- Besuche das Thema Transpilieren mit Pass-Managern.
- Erfahre, wie Circuits transpiliert werden als Teil von Qiskit-Patterns-Workflows mit Qiskit Runtime.
- Lies die API-Dokumentation der Debugging-Tools.