Skip to main content
IBM Quantum Platform

Simulieren Sie mit der QESEM-Funktion ein geneigtes Ising-Feld mit der Formel „ 2D “

Note

Qiskit Functions sind eine experimentelle Funktion, die nur für Nutzer der Pläne IBM Quantum® Premium Plan, Flex Plan und On-Prem (über IBM Quantum Platform API) verfügbar ist. Sie befinden sich im Status einer Vorabversion und können sich noch ändern.

Geschätzte Nutzungsdauer: 20 Minuten auf einem Heron r2 Prozessor. (HINWEIS: Dies ist nur eine Schätzung. Ihre Laufzeit kann variieren.)


Hintergrund

Dieses Tutorial zeigt, wie man QESEM, die Qiskit-Funktion von Qedma, verwendet, um die Dynamik eines kanonischen Quantenspinmodells, des 2D tilted-field Ising (TFI)-Modells mit Nicht-Clifford-Winkeln, zu simulieren:

H=J∑⟨i,j⟩ZiZj+gx∑iXi+gz∑iZi,H = J \sum_{\langle i,j \rangle} Z_i Z_j + g_x \sum_i X_i + g_z \sum_i Z_i ,

wobei ⟨i,j⟩\langle i,j \rangle die nächsten Nachbarn eines Gitters bezeichnet. Die Simulation der zeitlichen Entwicklung von Vielteilchen-Quantensystemen ist für klassische Computer eine rechenintensive Aufgabe. Quantencomputer hingegen sind von Natur aus darauf ausgelegt, diese Aufgabe effizient zu erfüllen. Insbesondere das TFI-Modell ist aufgrund seines reichhaltigen physikalischen Verhaltens und seiner hardwarefreundlichen Implementierung zu einem beliebten Benchmark auf Quantenhardware geworden.

Anstelle der Simulation einer zeitkontinuierlichen Dynamik verwenden wir das eng verwandte, gekippte Ising-Modell. Die Dynamik kann exakt als periodische Quantenschaltung ausgedrückt werden, wobei jeder Evolutionsschritt aus drei Schichten von fraktionellen Zwei-Qubit-Gattern RZZ(αZZ)R_{ZZ} (\alpha_{ZZ}) besteht, die mit Schichten von Ein-Qubit-Gattern RX(αX)R_X (\alpha_X) und RZ(αZ)R_Z (\alpha_Z) verschachtelt sind.

Wir werden generische Winkel verwenden, die sowohl für die klassische Simulation als auch für die Fehlerbegrenzung eine Herausforderung darstellen. Konkret haben wir αZZ=1.0\alpha_{ZZ} = 1.0, αX=0.53\alpha_X = 0.53 und αZ=0.1\alpha_Z = 0.1 gewählt, wodurch das Modell weit von jedem integrierbaren Punkt entfernt ist.

In diesem Lernprogramm werden wir Folgendes tun:

  • Schätzen Sie die erwartete QPU-Laufzeit für eine vollständige Fehlerbegrenzung mit Hilfe der analytischen und empirischen Zeitschätzungsfunktionen von QESEM.
  • Konstruktion und Simulation der 2D Tilted-Field-Ising-Modellschaltung unter Verwendung von Hardware-inspirierten Qubit-Layouts und Gate-Layern.
  • Visualisieren Sie die Konnektivität von Geräte-Qubits und ausgewählte Teilgraphen für Ihr Experiment.
  • Demonstrieren Sie die Verwendung der Operator-Backpropagation (OBP) zur Reduzierung der Schaltungstiefe. Diese Technik reduziert die Vorgänge am Ende des Schaltkreises, erfordert jedoch mehr Messungen durch den Bediener.
  • Durchführung einer unvoreingenommenen Fehlerminderung (EM) für mehrere Beobachtungsgrößen gleichzeitig unter Verwendung von QESEM und Vergleich von idealen, verrauschten und gemilderten Ergebnissen.
  • Analysieren und zeichnen Sie die Auswirkungen der Fehlerminderung auf die Magnetisierung in verschiedenen Schaltkreistiefen.

Hinweis: OBP liefert im Allgemeinen eine Reihe von möglicherweise nicht-kommutierenden Observablen. QESEM optimiert automatisch die Messbasis, wenn die Zielobservablen nicht-kommutierende Terme enthalten. Es generiert mit Hilfe mehrerer heuristischer Algorithmen Kandidaten für die Messbasis und wählt den Satz aus, der die Anzahl der unterschiedlichen Basen minimiert. Das bedeutet, dass QESEM kompatible Messgrößen in gemeinsamen Basen zusammenfasst, um die Gesamtzahl der erforderlichen Messkonfigurationen zu verringern und so die Effizienz zu steigern.


Über QESEM

QESEM ist eine zuverlässige, hochpräzise, charakterisierungsbasierte Software, die eine effiziente, unvoreingenommene quasi-probabilistische Fehlerminderung implementiert. Es wurde entwickelt, um Fehler in generischen Quantenschaltungen abzumildern und ist anwendungsunabhängig. Es wurde auf verschiedenen Hardware-Plattformen validiert, einschließlich Experimenten auf IBM® Eagle- und Heron-Geräten im industriellen Maßstab. Der QESEM-Arbeitsablauf sieht wie folgt aus:

  1. Gerätecharakterisierung - Abbildung der Gattertreue und Identifizierung kohärenter Fehler, Bereitstellung von Kalibrierungsdaten in Echtzeit. In dieser Phase wird sichergestellt, dass bei der Schadensbegrenzung die bestmöglichen Maßnahmen eingesetzt werden.
  2. Rauschbewusste Transpilation - generiert und bewertet alternative Qubit-Mappings, Operationssätze und Messgrundlagen und wählt die Variante, die die geschätzte QPU-Laufzeit minimiert, mit optionaler Parallelisierung zur Beschleunigung der Datenerfassung.
  3. Fehlerunterdrückung - definiert native Gatter neu, wendet Pauli-Twirling an und optimiert die Impulskontrolle (auf unterstützten Plattformen), um die Wiedergabetreue zu verbessern.
  4. Schaltungscharakterisierung - erstellt ein maßgeschneidertes lokales Fehlermodell und passt es an QPU-Messungen an, um das Restrauschen zu quantifizieren.
  5. Fehlerminimierung - konstruiert quasi-probabilistische Zerlegungen mehrerer Typen und wählt diese in einem adaptiven Prozess aus, der die Zeit für die Minimierung der QPU und die Empfindlichkeit gegenüber Hardware-Schwankungen minimiert und hohe Genauigkeiten bei großen Schaltkreisvolumen erreicht.

Weitere Informationen zu QESEM und einem groß angelegten Experiment mit diesem Modell an einem hochvernetzten Teilgraphen mit 103 Qubits der nativen Heavy-Hex-Geometrie finden Sie unter „Reliable ibm_marrakeshhigh-accuracy error mitigation for utility-scale quantum circuits“.

QESEM-Arbeitsablauf.

Anforderungen

Installieren Sie die folgenden Python Pakete, bevor Sie das Notebook ausführen:

  • Qiskit SDK v2.0.0 oder später (pip install qiskit)
  • Qiskit Runtime v0.40.0 oder später (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.8.0 oder später ( pip install qiskit-ibm-catalog )
  • Operator Backpropagation Qiskit-Zusatzmodul v0.3.0 oder höher ( pip install qiskit-addon-obp )
  • Qiskit Utils Zusatzprogramm v0.1.1 oder höher ( pip install qiskit-addon-utils )
  • Qiskit Aer Simulator v0.17.1 oder höher ( pip install qiskit-aer )
  • Matplotlib v3.10.3 oder später ( pip install matplotlib )

Einrichtung

Importieren Sie zunächst die entsprechenden Bibliotheken:

%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.visualization import (
    plot_gate_map,
)

Melden Sie sich anschließend mit Ihrem API-Schlüssel aus dem Dashboard unter IBM Quantum Platform an. Wählen Sie anschließend die Qiskit-Funktion wie folgt aus. (Beachten Sie, dass es aus Sicherheitsgründen am besten ist, Ihre Anmeldedaten in Ihrer lokalen Umgebung zu speichern, wenn Sie einen vertrauenswürdigen Computer verwenden, damit Sie Ihren API-Schlüssel nicht bei jeder Authentifizierung erneut eingeben müssen.)

# 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")

Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen

Wir beginnen mit der Definition einer Funktion, die den Trotter-Kreislauf erzeugt:

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

Als nächstes erstellen wir eine Funktion zur Berechnung von idealen Erwartungswerten mit AerSimulator.

Bitte beachten Sie, dass wir für große Schaltkreise (30 oder mehr Qubits) die Verwendung vorab berechneter Werte aus PEPS-Simulationen mit Belief-Propagation (BP) empfehlen. Dieser Code enthält als Beispiel vorberechnete Werte für 35 Qubits, die auf dem in dieser Arbeit vorgestellten BP-Ansatz zur Entwicklung eines PEPS-Tensornetzwerks (das wir als PEPS-BP bezeichnen) basieren und unter Verwendung des Tensornetzwerk- Python -Pakets „quimb“ berechnet wurden.

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

Wir verwenden ein hardwarebasiertes RZZR_{ZZ} layer mapping aus dem Heron-Bauelement, aus dem wir die Schichten entsprechend der Anzahl der zu simulierenden Qubits ausschneiden. Wir definieren Untergraphen für 10, 21, 28 und 35 Qubits, die die Struktur 2D beibehalten (es steht Ihnen frei, zu Ihrem bevorzugten Untergraphen zu wechseln):

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)

Output:

[[(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)]]

Nun visualisieren wir das Qubit-Layout auf dem Heron-Gerät für den ausgewählten Teilgraphen:

catalog = QiskitFunctionsCatalog(
    channel=channel,
    token=token,
    instance=instance,
)
backend = catalog.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()

Output:

Output of the previous code cell

Beachten Sie, dass die Konnektivität des gewählten Qubit-Layouts nicht notwendigerweise linear ist und je nach der gewählten Anzahl von Qubits große Bereiche des Heron-Bauelements abdecken kann.

Nun erzeugen wir den Trotter-Schaltkreis und die durchschnittliche Magnetisierung, die für die gewählte Anzahl von Qubits und Parametern beobachtet werden kann:

# 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: "
    f"{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]

Output:

Circuit 2q layers: 27

Circuit structure:
Output of the previous code cell
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])

Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware

QPU-Zeitschätzung mit und ohne OBP

Die Benutzer wollen in der Regel wissen, wie viel QPU-Zeit für ihr Experiment benötigt wird. Dies gilt jedoch als ein schwieriges Problem für klassische Computer.

QESEM bietet zwei Arten der Zeitabschätzung, um die Benutzer über die Durchführbarkeit ihrer Experimente zu informieren:

  1. Analytische Zeitschätzung - liefert eine sehr grobe Schätzung und erfordert keine QPU-Zeit. Damit kann getestet werden, ob ein Transpilationsdurchlauf die QPU-Zeit verkürzen könnte.
  2. Empirische Zeitschätzung (hier demonstriert) - liefert eine recht gute Schätzung und benötigt nur wenige Minuten QPU-Zeit.

In beiden Fällen gibt QESEM die geschätzte Zeit für das Erreichen der erforderlichen Genauigkeit für alle Observablen aus.

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={
        # "empirical" - gets actual time estimates without running full mitigation
        "estimate_time_only": "empirical",
        "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())

Output:

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']}"
)

Output:

Empirical time estimation (sec): 1200

Nun werden wir die Operator-Backpropagation (OBP) verwenden. (Weitere Informationen zum OBP-Qiskit-Add-on finden Sie in der OBP -Dokumentation.) Wir erstellen eine Funktion, die die Schaltungsschnitte für die Rückpropagierung generiert:

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, "
            f"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 "
            f"{metadata.accumulated_error(0):.3e}"
        )
        print("-----------------")
    return bp_circuit_vec, bp_observable_vec

Wir rufen die Funktion auf:

bp_circ_vec, bp_obs_vec = run_backpropagation([circ], observable, [steps])

Output:

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

Output:

The remaining circuit after backpropagation looks as follows:
Output of the previous code cell

Wir können sehen, dass die Backpropagation zwei Schichten des Schaltkreises reduziert. Nun, da wir unseren reduzierten Schaltkreis und die erweiterten Beobachtungswerte haben, können wir die Zeitschätzung für den Backpropagated-Schaltkreis durchführen:

# 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())

Output:

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']}"
)

Output:

Empirical time estimation (sec): 900

Wir sehen, dass OBP die Zeitkosten für die Entschärfung der Schaltung reduziert.


Schritt 3: Mit „ Qiskit primitives “ ausführen

Mit echtem Backend ausführen

Jetzt führen wir das vollständige Experiment auf einigen Trotter-Stufen durch. Die Anzahl der Qubits, die erforderliche Genauigkeit und die maximale QPU-Zeit können je nach den verfügbaren QPU-Ressourcen angepasst werden. Beachten Sie, dass sich die Beschränkung der maximalen QPU-Zeit auf die endgültige Genauigkeit auswirkt, wie Sie in der abschließenden Darstellung unten sehen können.

Wir analysieren vier Schaltungen mit 5, 7 und 9 Trotter-Schritten bei einer Genauigkeit von 0.05 und vergleichen ihre idealen, verrauschten und fehlerbereinigten Erwartungswerte:

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)

Auch hier führen wir OBP für jeden Schaltkreis durch, um die Laufzeit zu verkürzen:

bp_circ_vec_35, bp_obs_vec_35 = run_backpropagation(
    circ_vec, observable, steps_vec
)

Output:

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

Jetzt führen wir einen Stapel vollständiger QESEM-Aufträge aus. Wir begrenzen die maximale QPU-Laufzeit für jeden der Punkte, um das QPU-Budget besser kontrollieren zu können.

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)

Hier überprüfen wir den Status der einzelnen Aufträge:

for job in job_list:
    print(job.status())

Output:

DONE
DONE
DONE
DONE

Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format

Wenn alle Aufträge abgeschlossen sind, können wir ihre lauten und gemilderten Erwartungswerte vergleichen.

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)

Output:

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

Schließlich können wir die Magnetisierung in Abhängigkeit von der Anzahl der Schritte aufzeichnen. Dies fasst die Vorteile der QESEM Qiskit-Funktion für die vorspannungsfreie Fehlerminderung bei verrauschten Quantenbauelementen zusammen.

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

Output:

Text(0, 0.5, 'Magnetization')
Output of the previous code cell
Note

Der neunte Schritt weist einen großen statistischen Fehlerbalken auf, da wir die QPU-Zeit auf 5 Minuten begrenzt haben. Wenn Sie diesen Schritt 15 Minuten lang durchführen (wie es die empirische Zeitschätzung vorschlägt), erhalten Sie einen kleineren Fehlerbalken. Dadurch nähert sich der gemilderte Wert dem Idealwert an.

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.