Debugowanie zadań Qiskit Runtime
Wersje pakietów
Kod na tej stronie został opracowany przy użyciu następujących wymagań. Zalecamy użycie tych lub nowszych wersji.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
Zanim prześlesz zasobochłonne zadanie Qiskit Runtime do wykonania na sprzęcie, możesz użyć klasy Neat (Noisy Estimator Analyzer Tool) z Qiskit Runtime, aby sprawdzić, czy twoje zadanie Estimator jest skonfigurowane poprawnie, czy prawdopodobnie zwróci dokładne wyniki, czy używa najbardziej odpowiednich opcji dla określonego problemu i nie tylko.
Neat przeprowadza kliffordyzację (ang. Cliffordization) obwodów wejściowych w celu wydajnej symulacji, zachowując jednocześnie ich strukturę i głębokość. Obwody Clifforda podlegają podobnym poziomom szumu i stanowią dobry punkt odniesienia do badania oryginalnego Circuit.
Poniższe przykłady ilustrują sytuacje, w których Neat może być przydatnym narzędziem.
Najpierw zaimportuj odpowiednie pakiety i uwierzytelnij się w usłudze Qiskit Runtime.
Przygotowanie środowiska
# 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)
Inicjalizacja docelowego Circuit
Rozważ sześcioqubitowy Circuit o następujących właściwościach:
- Naprzemiennie stosuje losowe rotacje
RZi warstwy bramekCNOT. - Ma strukturę lustrzaną, to znaczy stosuje unitarną operację
Upo której następuje jej odwrotność.
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)
Wybierz pojedyncze operatory Pauliego Z jako obserwable i użyj ich do inicjalizacji primitive unified blocs (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']
Kliffordyzacja obwodów
Poprzednio zdefiniowane obwody PUB nie są obwodami Clifforda, co utrudnia ich klasyczną symulację. Możesz jednak użyć metody to_clifford klasy Neat, aby zmapować je do obwodów Clifforda w celu wydajniejszej symulacji. Metoda to_clifford jest opakowaniem wokół przejścia Transpiler ConvertISAToClifford, które można również stosować niezależnie. W szczególności zastępuje nieClliffordowe bramki jednoQubitowe w oryginalnym Circuit bramkami Clifforda jednoQubitowymi, ale nie modyfikuje bramek dwuQubitowych, liczby Qubitów ani głębokości Circuit.
Zobacz Wydajna symulacja obwodów stabilizatorowych z użyciem prymitywów Qiskit Aer, aby uzyskać więcej informacji o symulacji obwodów Clifforda.
Najpierw zainicjalizuj 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)
Następnie przeprowadź kliffordyzację PUBów.
clifford_pubs = analyzer.to_clifford(pubs)
clifford_pubs[0].circuit.draw("mpl", idle_wires=0)
Zastosowanie 1: Analiza wpływu szumów na wyniki Circuit
Ten przykład pokazuje, jak używać Neat do badania wpływu różnych modeli szumów na PUBy w zależności od głębokości Circuit, poprzez uruchamianie symulacji zarówno w warunkach idealnych (ideal_sim), jak i zaszumionych (noisy_sim). Może to być przydatne do ustalenia oczekiwań dotyczących jakości wyników eksperymentalnych przed uruchomieniem zadania na QPU. Aby dowiedzieć się więcej o modelach szumów, zobacz Dokładna i zaszumiona symulacja z użyciem Qiskit Aer primitives.
Symulowane wyniki obsługują operacje matematyczne i dlatego można je porównywać ze sobą (lub z wynikami eksperymentalnymi) w celu obliczenia wskaźników jakości.
QPU może być dotknięte przez różne rodzaje szumów. Użyty tutaj model szumów Qiskit Aer symuluje tylko niektóre z nich i dlatego prawdopodobnie jest mniej dotkliwy niż szumy na prawdziwym QPU.
Szczegółowe informacje o tym, jakie błędy są uwzględniane podczas inicjalizacji modelu szumów z QPU, znajdziesz w dokumentacji API Aer NoiseModel.
Zacznij od przeprowadzenia idealnych i zaszumionych symulacji klasycznych.
# 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 ]))])
Następnie zastosuj operacje matematyczne, aby obliczyć różnicę bezwzględną. W dalszej części przewodnika różnica bezwzględna jest używana jako wskaźnik jakości do porównywania wyników idealnych z wynikami zaszumionymi lub eksperymentalnymi, ale można skonfigurować podobne wskaźniki.
Różnica bezwzględna pokazuje, że wpływ szumów rośnie wraz z rozmiarem 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%
Możesz kierować się następującymi uproszczonymi wytycznymi, aby ulepszać Circuit tego typu:
- Jeśli średnia różnica bezwzględna jest większa niż 90%, mitygacja prawdopodobnie nie pomoże.
- Jeśli średnia różnica bezwzględna jest mniejsza niż 90%, Probabilistic Error Amplification (PEA) prawdopodobnie będzie w stanie poprawić wyniki.
- Jeśli średnia różnica bezwzględna jest mniejsza niż 80%, ZNE z fałdowaniem Gate również prawdopodobnie będzie w stanie poprawić wyniki.
Ponieważ wszystkie powyższe różnice bezwzględne są mniejsze niż 90%, zastosowanie PEA do oryginalnego Circuit powinno poprawić jakość jego wyników. Możesz podać różne modele szumów w analizatorze. Poniższy przykład przeprowadza ten sam test, ale dodaje niestandardowy model szumów.
# 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%
Jak widać, mając dany model szumów, możesz próbować określić ilościowo wpływ szumów na PUBy (w wersji Cliffordowej) przed ich uruchomieniem na QPU.
Zastosowanie 2: Porównywanie różnych strategii
Ten przykład używa Neat, aby pomóc zidentyfikować najlepsze opcje dla twoich PUBów. W tym celu rozważ uruchomienie problemu estymacji z PEA, którego nie można symulować przy użyciu qiskit_aer. Możesz użyć Neat, aby ustalić, które współczynniki wzmocnienia szumów będą działać najlepiej, a następnie użyć tych współczynników podczas uruchamiania oryginalnego eksperymentu na 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%
Wynik z najmniejszą różnicą sugeruje, które opcje wybrać.
Następne kroki
- Dowiedz się więcej o dokładnej i zaszumionej symulacji z użyciem Qiskit Aer primitives.
- Dowiedz się więcej o dostępnych opcjach Qiskit Runtime.
- Dowiedz się więcej o technikach mitygacji i tłumienia błędów.
- Odwiedź temat Transpilacja za pomocą menedżerów przebiegów.
- Dowiedz się, jak transpilować Circuit w ramach przepływów pracy Qiskit patterns przy użyciu Qiskit Runtime.
- Review the Debugging tools API documentation.