Przejdź do głównej treści

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.4.0
qiskit-ibm-runtime~=0.46.1
qiskit-aer~=0.17

Możesz użyć klasy Neat, aby przeanalizować wpływ szumów na zadanie Estimator. Do weryfikacji składni użyj trybu testowania lokalnego.

Użycie klasy Neat

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. 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 RZ i warstwy bramek CNOT.
  • Ma strukturę lustrzaną, to znaczy stosuje unitarną operację U po 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)

Output of the previous code cell

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 nieCliffordowe 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)

Output of the previous code cell

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.

ostrożnie

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.98242188, 0.984375 , 0.98828125, 0.99023438, 0.96484375,
0.97265625])), NeatPubResult(vals=array([0.96875 , 0.97070312, 0.98046875, 0.98828125, 0.96484375,
0.984375 ])), NeatPubResult(vals=array([0.94140625, 0.94726562, 0.92773438, 0.93164062, 0.93164062,
0.95703125])), NeatPubResult(vals=array([0.91015625, 0.90429688, 0.90039062, 0.93164062, 0.94140625,
0.953125 ])), NeatPubResult(vals=array([0.875 , 0.88476562, 0.88476562, 0.8984375 , 0.91601562,
0.91210938])), NeatPubResult(vals=array([0.88476562, 0.89453125, 0.86523438, 0.91015625, 0.86914062,
0.91992188]))])

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 "
f"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:
1.95%

Mean absolute difference between ideal and noisy results for circuits with 2 layers:
2.38%

Mean absolute difference between ideal and noisy results for circuits with 3 layers:
6.06%

Mean absolute difference between ideal and noisy results for circuits with 4 layers:
7.65%

Mean absolute difference between ideal and noisy results for circuits with 5 layers:
10.48%

Mean absolute difference between ideal and noisy results for circuits with 6 layers:
10.94%

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 "
f"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 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"
)
Mean absolute difference for factors [1, 1.1]:
2.42%

Mean absolute difference for factors [1, 1.1, 1.2]:
11.34%

Mean absolute difference for factors [1, 1.5, 2]:
3.68%

Mean absolute difference for factors [1, 1.5, 2, 2.5, 3]:
4.77%

Mean absolute difference for factors [1, 4]:
3.61%

Wynik z najmniejszą różnicą sugeruje, które opcje wybrać.

Następne kroki

Zalecenia