Symulacja 2D modelu Isinga z nachylonym polem przy użyciu funkcji QESEM
Funkcje Qiskit to funkcja eksperymentalna dostępna wyłącznie dla użytkowników planów IBM Quantum® Premium Plan, Flex Plan oraz On-Prem (przez API IBM Quantum Platform). Są one w statusie wersji zapoznawczej i mogą ulec zmianie.
Szacowany czas użycia: 20 minut na procesorze Heron r2. (UWAGA: To jest tylko szacunek. Rzeczywisty czas może się różnić.)
Tło
Ten samouczek pokazuje, jak używać QESEM, funkcji Qiskit firmy Qedma, do symulacji dynamiki kanonicznego kwantowego modelu spinowego — 2D modelu Isinga z nachylonym polem (TFI, ang. tilted-field Ising) z kątami nie-Cliffordowymi:
gdzie oznacza najbliższych sąsiadów na sieci. Symulowanie ewolucji czasowej wielociałowych układów kwantowych to zadanie obliczeniowo trudne dla klasycznych komputerów. Komputery kwantowe, w przeciwieństwie do nich, są z natury zaprojektowane do wydajnego wykonywania tego zadania. Model TFI stał się w szczególności popularnym punktem odniesienia dla sprzętu kwantowego ze względu na bogate zachowanie fizyczne i przyjazną dla sprzętu implementację.
Zamiast symulować dynamikę ciągłą w czasie, przyjmujemy ściśle powiązany kopnięty model Isinga. Dynamika może być wyrażona dokładnie jako periodyczny Circuit kwantowy, w którym każdy krok ewolucji składa się z trzech warstw ułamkowych Gate dwu-Qubitowych , przeplatanych warstwami Gate jednoQubitowych i .
Użyjemy ogólnych kątów, które są trudne zarówno dla klasycznej symulacji, jak i korekcji błędów. Konkretnie, wybraliśmy , i , umieszczając model daleko od jakiegokolwiek punktu całkowalności.
W tym samouczku wykonamy następujące kroki:
- Oszacujemy oczekiwany czas działania QPU dla pełnej korekcji błędów przy użyciu analitycznych i empirycznych funkcji szacowania czasu QESEM.
- Zbudujemy i zasymulujemy Circuit modelu 2D Isinga z nachylonym polem, używając układów Qubitów inspirowanych sprzętem i warstw Gate.
- Zwizualizujemy łączność Qubitów urządzenia oraz wybrane podgrafy dla naszego eksperymentu.
- Zademonstrujemy użycie propagacji wstecznej operatorów (OBP) w celu zmniejszenia głębokości Circuitu. Technika ta usuwa operacje z końca Circuitu kosztem większej liczby pomiarów operatorów.
- Wykonamy nieobciążoną korekcję błędów (EM) dla wielu obserwowalnych jednocześnie przy użyciu QESEM, porównując wyniki idealne, zaszumione i po korekcji.
- Przeanalizujemy i wykreślimy wpływ korekcji błędów na namagnesowanie dla różnych głębokości Circuitu.
Uwaga: OBP generalnie zwraca zbiór możliwie niekomutujących obserwowalnych. QESEM automatycznie optymalizuje bazy pomiarowe, gdy docelowe obserwowalne zawierają niekomutujące człony. Generuje kandydujące zestawy baz pomiarowych przy użyciu kilku algorytmów heurystycznych i wybiera zestaw minimalizujący liczbę odrębnych baz. Oznacza to, że QESEM grupuje kompatybilne obserwowalne we wspólne bazy, aby zmniejszyć całkowitą liczbę wymaganych konfiguracji pomiarowych, poprawiając wydajność.
O QESEM
QESEM to niezawodne, wysokoprecyzyjne oprogramowanie oparte na charakteryzacji, implementujące wydajną, nieobciążoną kwazi-probabilistyczną korekcję błędów. Jest zaprojektowane do korekcji błędów w ogólnych Circuit kwantowych i jest niezależne od zastosowania. Zostało zwalidowane na różnych platformach sprzętowych, w tym w eksperymentach w skali użytkowej na urządzeniach IBM® Eagle i Heron. Etapy przepływu pracy QESEM są następujące:
- Charakteryzacja urządzenia — mapuje wierności Gate i identyfikuje błędy koherentne, dostarczając danych kalibracyjnych w czasie rzeczywistym. Ten etap zapewnia, że korekcja korzysta z operacji o najwyższej dostępnej wierności.
- Transpilacja uwzględniająca szum — generuje i ocenia alternatywne mapowania Qubitów, zestawy operacji i bazy pomiarowe, wybierając wariant minimalizujący szacowany czas QPU, z opcjonalną równoległością w celu przyspieszenia zbierania danych.
- Tłumienie błędów — redefiniuje natywne Gate, stosuje splatanie Pauliego i optymalizuje sterowanie na poziomie impulsów (na obsługiwanych platformach) w celu poprawy wierności.
- Charakteryzacja Circuitu — buduje dostosowany lokalny model błędów i dopasowuje go do pomiarów QPU w celu kwantyfikacji resztkowego szumu.
- Korekcja błędów — konstruuje wielotypowe kwazi-probabilistyczne dekompozycje i próbkuje z nich w adaptacyjnym procesie minimalizującym czas QPU korekcji i wrażliwość na fluktuacje sprzętowe, osiągając wysoką dokładność przy dużych objętościach Circuitu.
Więcej informacji na temat QESEM i eksperymentu w skali użytkowej tego modelu na 103-Qubitowym, wysokołącznościowym podgrafie natywnej geometrii heavy-hex ibm_marrakesh można znaleźć w artykule Reliable high-accuracy error mitigation for utility-scale quantum circuits.

Wymagania
Zainstaluj następujące pakiety Pythona przed uruchomieniem notatnika:
- Qiskit SDK v2.0.0 lub nowszy (
pip install qiskit) - Qiskit Runtime v0.40.0 lub nowszy (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog v0.8.0 lub nowszy (
pip install qiskit-ibm-catalog) - Dodatek Qiskit do propagacji wstecznej operatorów v0.3.0 lub nowszy (
pip install qiskit-addon-obp) - Dodatek Qiskit Utils v0.1.1 lub nowszy (
pip install qiskit-addon-utils) - Symulator Qiskit Aer v0.17.1 lub nowszy (
pip install qiskit-aer) - Matplotlib v3.10.3 lub nowszy (
pip install matplotlib)
Konfiguracja
Najpierw zaimportuj odpowiednie biblioteki:
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-aer qiskit-ibm-catalog qiskit-ibm-runtime
%matplotlib inline
from typing import Sequence
import matplotlib.pyplot as plt
import numpy as np
import qiskit
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_catalog import QiskitFunctionsCatalog
from qiskit_aer import AerSimulator
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import (
plot_gate_map,
)
Następnie uwierzytelnij się za pomocą klucza API z panelu IBM Quantum Platform. Następnie wybierz funkcję Qiskit w następujący sposób. (Zwróć uwagę, że ze względów bezpieczeństwa najlepiej jest zapisać dane uwierzytelniające konta w lokalnym środowisku, jeśli korzystasz z zaufanej maszyny, aby nie musieć wpisywać klucza API przy każdym uwierzytelnianiu.)
# Paste here your instance and token strings
instance = "YOUR_INSTANCE"
token = "YOUR_TOKEN"
channel = "ibm_quantum_platform"
catalog = QiskitFunctionsCatalog(
channel=channel, token=token, instance=instance
)
qesem_function = catalog.load("qedma/qesem")
Krok 1: Mapowanie wejść klasycznych na problem kwantowy
Zaczynamy od zdefiniowania funkcji tworzącej Circuit Trottera:
def trotter_circuit_from_layers(
steps: int,
theta_x: float,
theta_z: float,
theta_zz: float,
layers: Sequence[Sequence[tuple[int, int]]],
init_state: str | None = None,
) -> qiskit.QuantumCircuit:
"""
Generates an ising trotter circuit
:param steps: trotter steps
:param theta_x: RX angle
:param theta_z: RZ angle
:param theta_zz: RZZ angle
:param layers: list of layers (can be list of layers in device)
:param init_state: Initial state to prepare. If None, will not prepare any state. If "+", will
add Hadamard gates to all qubits.
:return: QuantumCircuit
"""
qubits = sorted({i for layer in layers for edge in layer for i in edge})
circ = qiskit.QuantumCircuit(max(qubits) + 1)
if init_state == "+":
print("init_state = +")
for q in qubits:
circ.h(q)
for _ in range(steps):
for q in qubits:
circ.rx(theta_x, q)
circ.rz(theta_z, q)
for layer in layers:
for edge in layer:
circ.rzz(theta_zz, *edge)
circ.barrier(qubits)
return circ
Następnie tworzymy funkcję do obliczania idealnych wartości oczekiwanych przy użyciu AerSimulator.
Zwróć uwagę, że w przypadku dużych Circuitów (30 lub więcej Qubitów) zalecamy używanie wstępnie obliczonych wartości z symulacji BP PEPS (ang. belief-propagation PEPS). Ten kod zawiera wstępnie obliczone wartości dla 35 Qubitów jako przykład, oparty na podejściu BP do ewolucji sieci tensorowej PEPS wprowadzonym w tym artykule (który nazywamy PEPS-BP), przy użyciu pakietu Pythona do sieci tensorowych quimb.
def calculate_ideal_evs(circ, obs, num_qubits, step):
# Predefined results for large circuits - calculated using bppeps for 3, 5, 7, 9 trotter steps
predefined_35 = [
0.79537,
0.78653,
0.79699,
]
if num_qubits == 35:
print(
"Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits."
)
return predefined_35[step]
else:
simulator = AerSimulator()
# Use Estimator primitive to get expectation value
estimator = Estimator(simulator)
sim_result = estimator.run([(circ, [obs])], precision=0.0001).result()
# Extracting the result
ideal_values = sim_result[0].data.evs[0]
return ideal_values
Używamy mapowania warstw opartego na sprzęcie, zaczerpniętego z urządzenia Heron, z którego wycinamy warstwy zgodnie z liczbą Qubitów, które chcemy symulować. Definiujemy podgrafy dla 10, 21, 28 i 35 Qubitów, które zachowują strukturę 2D (możesz zmienić na swój ulubiony podgraf):
LAYERS_HERON_R2 = [ # the full set of hardware layers for Heron r2
[
(2, 3),
(6, 7),
(10, 11),
(14, 15),
(20, 21),
(16, 23),
(24, 25),
(17, 27),
(28, 29),
(18, 31),
(32, 33),
(19, 35),
(36, 41),
(42, 43),
(37, 45),
(46, 47),
(38, 49),
(50, 51),
(39, 53),
(60, 61),
(56, 63),
(64, 65),
(57, 67),
(68, 69),
(58, 71),
(72, 73),
(59, 75),
(76, 81),
(82, 83),
(77, 85),
(86, 87),
(78, 89),
(90, 91),
(79, 93),
(94, 95),
(100, 101),
(96, 103),
(104, 105),
(97, 107),
(108, 109),
(98, 111),
(112, 113),
(99, 115),
(116, 121),
(122, 123),
(117, 125),
(126, 127),
(118, 129),
(130, 131),
(119, 133),
(134, 135),
(140, 141),
(136, 143),
(144, 145),
(137, 147),
(148, 149),
(138, 151),
(152, 153),
(139, 155),
],
[
(1, 2),
(3, 4),
(5, 6),
(7, 8),
(9, 10),
(11, 12),
(13, 14),
(21, 22),
(23, 24),
(25, 26),
(27, 28),
(29, 30),
(31, 32),
(33, 34),
(40, 41),
(43, 44),
(45, 46),
(47, 48),
(49, 50),
(51, 52),
(53, 54),
(55, 59),
(61, 62),
(63, 64),
(65, 66),
(67, 68),
(69, 70),
(71, 72),
(73, 74),
(80, 81),
(83, 84),
(85, 86),
(87, 88),
(89, 90),
(91, 92),
(93, 94),
(95, 99),
(101, 102),
(103, 104),
(105, 106),
(107, 108),
(109, 110),
(111, 112),
(113, 114),
(120, 121),
(123, 124),
(125, 126),
(127, 128),
(129, 130),
(131, 132),
(133, 134),
(135, 139),
(141, 142),
(143, 144),
(145, 146),
(147, 148),
(149, 150),
(151, 152),
(153, 154),
],
[
(3, 16),
(7, 17),
(11, 18),
(22, 23),
(26, 27),
(30, 31),
(34, 35),
(21, 36),
(25, 37),
(29, 38),
(33, 39),
(41, 42),
(44, 45),
(48, 49),
(52, 53),
(43, 56),
(47, 57),
(51, 58),
(62, 63),
(66, 67),
(70, 71),
(74, 75),
(61, 76),
(65, 77),
(69, 78),
(73, 79),
(81, 82),
(84, 85),
(88, 89),
(92, 93),
(83, 96),
(87, 97),
(91, 98),
(102, 103),
(106, 107),
(110, 111),
(114, 115),
(101, 116),
(105, 117),
(109, 118),
(113, 119),
(121, 122),
(124, 125),
(128, 129),
(132, 133),
(123, 136),
(127, 137),
(131, 138),
(142, 143),
(146, 147),
(150, 151),
(154, 155),
(0, 1),
(4, 5),
(8, 9),
(12, 13),
(54, 55),
(15, 19),
],
]
subgraphs = { # the subgraphs for the different qubit counts such that it's 2D
10: list(range(22, 29)) + [16, 17, 37],
21: list(range(3, 12)) + list(range(23, 32)) + [16, 17, 18],
28: list(range(3, 12))
+ list(range(23, 32))
+ list(range(45, 50))
+ [16, 17, 18, 37, 38],
35: list(range(3, 12))
+ list(range(21, 32))
+ list(range(41, 50))
+ [16, 17, 18, 36, 37, 38],
42: list(range(3, 12))
+ list(range(21, 32))
+ list(range(41, 50))
+ list(range(63, 68))
+ [16, 17, 18, 36, 37, 38, 56, 57],
}
n_qubits = 35 # 21, 28, 35, 42
layers = [
[
edge
for edge in layer
if edge[0] in subgraphs[n_qubits] and edge[1] in subgraphs[n_qubits]
]
for layer in LAYERS_HERON_R2
]
print(layers)
[[(6, 7), (10, 11), (16, 23), (24, 25), (17, 27), (28, 29), (18, 31), (36, 41), (42, 43), (37, 45), (46, 47), (38, 49)], [(3, 4), (5, 6), (7, 8), (9, 10), (21, 22), (23, 24), (25, 26), (27, 28), (29, 30), (43, 44), (45, 46), (47, 48)], [(3, 16), (7, 17), (11, 18), (22, 23), (26, 27), (30, 31), (21, 36), (25, 37), (29, 38), (41, 42), (44, 45), (48, 49), (4, 5), (8, 9)]]
Teraz wizualizujemy układ Qubitów na urządzeniu Heron dla wybranego podgrafu:
service = QiskitRuntimeService(
channel=channel,
token=token,
instance=instance,
)
backend = service.backend("ibm_fez") # or any available device
selected_qubits = subgraphs[n_qubits]
num_qubits = backend.configuration().num_qubits
qubit_color = [
"#ff7f0e" if i in selected_qubits else "#d3d3d3"
for i in range(num_qubits)
]
plot_gate_map(
backend=backend,
figsize=(15, 10),
qubit_color=qubit_color,
)
plt.show()

Zauważ, że łączność wybranego układu Qubitów niekoniecznie jest liniowa i może obejmować duże obszary urządzenia Heron w zależności od wybranej liczby Qubitów.
Teraz generujemy Circuit Trottera i obserwowalną średniego namagnesowania dla wybranej liczby Qubitów i parametrów:
# Chosen parameters:
theta_x = 0.53
theta_z = 0.1
theta_zz = 1.0
steps = 9
circ = trotter_circuit_from_layers(steps, theta_x, theta_z, theta_zz, layers)
print(
f"Circuit 2q layers: {circ.depth(filter_function=lambda instr: len(instr.qubits) == 2)}"
)
print("\nCircuit structure:")
circ.draw("mpl", scale=0.8, fold=-1, idle_wires=False)
plt.show()
observable = qiskit.quantum_info.SparsePauliOp.from_sparse_list(
[("Z", [q], 1 / n_qubits) for q in subgraphs[n_qubits]],
np.max(subgraphs[n_qubits]) + 1,
) # Average magnetization observable
print(observable)
obs_list = [observable]
Circuit 2q layers: 27
Circuit structure:

SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,
0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j])
Step 2: Optymalizacja problemu pod kątem wykonania na sprzęcie kwantowym
Szacowanie czasu QPU z OBP i bez OBP
Użytkownicy zazwyczaj chcą wiedzieć, ile czasu QPU wymaga ich eksperyment. Jest to jednak problem uznawany za trudny dla komputerów klasycznych.
QESEM oferuje dwa tryby szacowania czasu, które pozwalają ocenić wykonalność eksperymentów:
- Analityczne szacowanie czasu – daje bardzo przybliżone oszacowanie i nie wymaga czasu QPU. Można go użyć, aby sprawdzić, czy dany przebieg transpilacji mógłby potencjalnie skrócić czas QPU.
- Empiryczne szacowanie czasu (demonstrowane tutaj) – daje całkiem dobre oszacowanie i wykorzystuje kilka minut czasu QPU.
W obu przypadkach QESEM zwraca szacowanie czasu potrzebnego do osiągnięcia wymaganej precyzji dla wszystkich obserwowalnych.
run_on_real_hardware = True
precision = 0.05
if run_on_real_hardware:
backend_name = "ibm_fez"
else:
backend_name = "fake_fez"
# Start a job for empirical time estimation
estimation_job_wo_obp = qesem_function.run(
pubs=[(circ, obs_list)],
instance=instance,
backend_name=backend_name, # E.g. "ibm_brisbane"
options={
"estimate_time_only": "empirical", # "empirical" - gets actual time estimates without running full mitigation
"max_execution_time": 120, # Limits the QPU time, specified in seconds.
"default_precision": precision,
},
)
print(estimation_job_wo_obp.job_id)
print(estimation_job_wo_obp.status())
17d3828e-9fdb-482e-8e9b-392f3eefe313
DONE
# Get the result object (blocking method). Use job.status() in a loop for non-blocking.
# This takes 1-3 minutes
result = estimation_job_wo_obp.result()
print(
f"Empirical time estimation (sec): {result[0].metadata['time_estimation_sec']}"
)
Empirical time estimation (sec): 1200
Teraz użyjemy propagacji wstecznej operatora (OBP). (Szczegóły na temat dodatku Qiskit OBP znajdziesz w przewodniku OBP.) Utworzymy funkcję generującą wycinki obwodu do propagacji wstecznej:
def run_backpropagation(circ_vec, observable, steps_vec, max_qwc_groups=8):
"""
Runs backpropagation for a list of circuits and observables.
Returns lists of backpropagated circuits and observables.
"""
op_budget = OperatorBudget(max_qwc_groups=max_qwc_groups)
bp_circuit_vec = []
bp_observable_vec = []
for i, circ in enumerate(circ_vec):
slices = slice_by_gate_types(circ)
bp_observable, remaining_slices, metadata = backpropagate(
observable,
slices,
operator_budget=op_budget,
)
bp_circuit = combine_slices(remaining_slices, include_barriers=True)
bp_circuit_vec.append(bp_circuit)
bp_observable_vec.append(bp_observable)
print(f"n.o. steps: {steps_vec[i]}")
print(f"Backpropagated {metadata.num_backpropagated_slices} slices.")
print(
f"New observable has {len(bp_observable.paulis)} terms, which can be combined into "
f"{len(bp_observable.group_commuting(qubit_wise=True))} groups.\n"
f"After truncation, the error in our observable is bounded by {metadata.accumulated_error(0):.3e}"
)
print("-----------------")
return bp_circuit_vec, bp_observable_vec
Wywołujemy funkcję:
bp_circ_vec, bp_obs_vec = run_backpropagation([circ], observable, [steps])
n.o. steps: 9
Backpropagated 11 slices.
New observable has 363 terms, which can be combined into 4 groups.
After truncation, the error in our observable is bounded by 0.000e+00
-----------------
print("The remaining circuit after backpropagation looks as follows:")
bp_circ_vec[-1].draw("mpl", scale=0.8, fold=-1, idle_wires=False)
None
The remaining circuit after backpropagation looks as follows:

Widzimy, że propagacja wsteczna zredukowała dwie warstwy obwodu. Teraz, gdy mamy zredukowany obwód i rozszerzone obserwowalne, wykonajmy szacowanie czasu dla obwodu po propagacji wstecznej:
# Start a job for empirical time estimation
estimation_job_obp = qesem_function.run(
pubs=[(bp_circ_vec[-1], [bp_obs_vec[-1]])],
instance=instance,
backend_name=backend_name,
options={
"estimate_time_only": "empirical",
"max_execution_time": 120,
"default_precision": precision,
},
)
print(estimation_job_obp.job_id)
print(estimation_job_obp.status())
8bae699d-a16b-4d39-bbd9-d123fbcce55d
DONE
result_obp = estimation_job_obp.result()
print(
f"Empirical time estimation (sec): {result_obp[0].metadata['time_estimation_sec']}"
)
Empirical time estimation (sec): 900
Widzimy, że OBP zmniejsza koszt czasowy mitigacji obwodu.
Step 3: Wykonanie przy użyciu prymitywów Qiskit
Uruchomienie na prawdziwym Backend
Teraz uruchamiamy pełny eksperyment dla kilku kroków Trottera. Liczbę qubitów, wymaganą precyzję i maksymalny czas QPU można modyfikować zgodnie z dostępnymi zasobami QPU. Zwróć uwagę, że ograniczenie maksymalnego czasu QPU wpłynie na końcową precyzję, co zobaczysz na ostatnim wykresie poniżej.
Analizujemy cztery obwody z 5, 7 i 9 krokami Trottera przy precyzji 0,05, porównując ich idealne, zaszumione i skorygowane błędami wartości oczekiwane:
steps_vec = [5, 7, 9]
circ_vec = []
for steps in steps_vec:
circ = trotter_circuit_from_layers(
steps, theta_x, theta_z, theta_zz, layers
)
circ_vec.append(circ)
Ponownie wykonujemy OBP na każdym obwodzie, aby skrócić czas wykonania:
bp_circ_vec_35, bp_obs_vec_35 = run_backpropagation(
circ_vec, observable, steps_vec
)
n.o. steps: 5
Backpropagated 11 slices.
New observable has 363 terms, which can be combined into 4 groups.
After truncation, the error in our observable is bounded by 0.000e+00
-----------------
n.o. steps: 7
Backpropagated 11 slices.
New observable has 363 terms, which can be combined into 4 groups.
After truncation, the error in our observable is bounded by 0.000e+00
-----------------
n.o. steps: 9
Backpropagated 11 slices.
New observable has 363 terms, which can be combined into 4 groups.
After truncation, the error in our observable is bounded by 0.000e+00
-----------------
Teraz uruchamiamy partię pełnych zadań QESEM. Ograniczamy maksymalny czas QPU dla każdego z punktów, aby lepiej kontrolować budżet QPU.
run_on_real_hardware = True
precision = 0.05
if run_on_real_hardware:
backend_name = "ibm_marrakesh"
else:
backend_name = "fake_fez"
# Running full jobs for:
pubs_list = [
[(bp_circ_vec_35[i], bp_obs_vec_35[i])] for i in range(len(bp_obs_vec_35))
]
# Initiating multiple jobs for different lengths
job_list = []
for pubs in pubs_list:
job_obp = qesem_function.run(
pubs=pubs,
instance=instance,
backend_name=backend_name, # E.g. "ibm_brisbane"
options={
"max_execution_time": 300, # Limits the QPU time, specified in seconds.
"default_precision": 0.05,
},
)
job_list.append(job_obp)
Tutaj sprawdzamy status każdego zadania:
for job in job_list:
print(job.status())
DONE
DONE
DONE
DONE
Step 4: Przetwarzanie końcowe i zwracanie wyniku w żądanym formacie klasycznym
Gdy wszystkie zadania zakończą działanie, możemy porównać ich zaszumione i zmitigowane wartości oczekiwane.
ideal_values = []
noisy_values = []
error_mitigated_values = []
error_mitigated_stds = []
for i in range(len(job_list)):
job = job_list[i]
result = job.result() # Blocking - takes 3-5 minutes
noisy_results = result[0].metadata["noisy_results"]
ideal_val = calculate_ideal_evs(circ_vec[i], observable, n_qubits, i)
print("---------------------------------")
print(f"Ideal: {ideal_val}")
print(f"Noisy: {noisy_results.evs}")
print(f"QESEM: {result[0].data.evs} \u00b1 {result[0].data.stds}")
ideal_values.append(ideal_val)
noisy_values.append(noisy_results.evs)
error_mitigated_values.append(result[0].data.evs)
error_mitigated_stds.append(result[0].data.stds)
Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.
---------------------------------
Ideal: 0.79537
Noisy: 0.7039237951821501
QESEM: 0.7828018244130982 ± 0.013257266977728376
Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.
---------------------------------
Ideal: 0.78653
Noisy: 0.6478583812958806
QESEM: 0.7875259197423828 ± 0.02703045139248604
Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.
---------------------------------
Ideal: 0.79699
Noisy: 0.6171787879868142
QESEM: 0.6918791909168913 ± 0.0740873782039517
Na koniec możemy wykreślić magnetyzację w zależności od liczby kroków. Podsumowuje to korzyści z używania funkcji Qiskit QESEM do bezstronnej mitigacji błędów na zaszumionych urządzeniach kwantowych.
plt.plot(steps_vec, ideal_values, "--", label="ideal")
plt.scatter(steps_vec, noisy_values, label="noisy")
plt.errorbar(
steps_vec,
error_mitigated_values,
yerr=error_mitigated_stds,
fmt="o",
capsize=5,
label="QESEM mitigation",
)
plt.legend()
plt.xlabel("n.o. steps")
plt.ylabel("Magnetization")
Text(0, 0.5, 'Magnetization')
Dziewiąty krok ma duży statystyczny pasek błędu, ponieważ ograniczyliśmy czas QPU do 5 minut. Jeśli uruchomisz ten krok przez 15 minut (zgodnie z sugestią empirycznego szacowania czasu), otrzymasz mniejszy pasek błędu. W rezultacie zmitigowana wartość będzie bliższa wartości idealnej.