Skip to main content
IBM Quantum Platform

Experiment im Versorgungsmaßstab I

Note

Tamiya Onodera (5. Juli 2024)

Laden Sie die PDF-Datei der Originalvorlesung herunter. Beachten Sie, dass einige Codeschnipsel veraltet sein könnten, da es sich um statische Bilder handelt.

Die ungefähre QPU-Zeit für die Durchführung dieses Experiments beträgt 45 Sekunden.


1. Einführung in das Utility Paper

In dieser Lektion führen wir eine Schaltung im Versorgungsbereich durch, die in der Zeitschrift Nature Vol. 618 vom 15. Juni 2023 veröffentlicht wurde. Der Beitrag befasst sich mit der zeitlichen Entwicklung des 2D Transversalfeld-Ising-Modells. Sie berücksichtigen insbesondere die zeitliche Dynamik des Hamiltonianers,

H=HZZ+HX=−J∑(i,j)ZiZj+h∑iXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

wobei J>0J > 0 die Kopplung der nächstgelegenen Spins mit i<ji < j und hh das globale Transversalfeld ist.

Sie simulieren die Spindynamik ausgehend von einem Anfangszustand mit Hilfe der Trotter-Zerlegung erster Ordnung des Zeitentwicklungsoperators,

exp⁡(−iHZZδt)=∏(i,j)exp⁡(iJδtZiZj)=∏(i,j)RZiZj(−2Jδt)exp⁡(−iHXδt)=∏iexp⁡(−ihδtXi)=∏iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

wobei die Entwicklungszeit TT in T/δtT / \delta t Trotter-Schritte diskretisiert ist und RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) und RXi(θh)\mathrm{R}_{X_i}(\theta_h) ZZZZ bzw. XX Rotationstore sind.

Sie führten Experimente auf einem IBM Quantum® Eagle-Prozessor durch, einem 127-Qubit-Gerät mit Heavy-Hex-Konnektivität, und wendeten XX Wechselwirkungen auf alle Qubits und ZZZZ Wechselwirkungen auf alle Kanten der Kopplungskarte an. Beachten Sie, dass aufgrund der "Datenabhängigkeit" nicht alle ZZZZ Interaktionen gleichzeitig angewendet werden können. Daher färben sie die Kopplungskarte ein, um sie in Schichten zu gruppieren. Die Elemente einer Ebene erhalten die gleiche Farbe und können parallel angewendet werden.

Außerdem konzentrierten sie sich der Einfachheit halber auf den Fall θJ=−π/2\theta_J=-\pi /2.

Der neuartige Beitrag der Arbeit besteht darin, dass sie Quantenschaltungen in einem Maßstab gebaut haben, der über die Zustandsvektorsimulation hinausgeht, sie auf verrauschten Quantencomputern betrieben haben und es ihnen gelungen ist, zuverlässige Ergebnisse zu erzielen. Das heißt, sie haben den Nutzen von verrauschten Quantencomputern demonstriert. Dabei wendeten sie die Null-Rausch-Extrapolation (ZNE) mit probabilistischer Fehlerverstärkung (PEA) an, um Fehler durch verrauschte Geräte abzuschwächen.

Von da an nannten wir solche Experimente und Schaltungen "utility-scale".

1.1 Ihr Ziel

Das Ziel dieser Lektion ist es, einen Stromkreis zu bauen und ihn mit einem Eagle-Prozessor zu betreiben. Es würde den Rahmen dieses Notizbuchs sprengen, verlässliche Ergebnisse zu liefern, zum einen, weil PEA zum Zeitpunkt der Erstellung dieses Dokuments eine experimentelle Funktion von Qiskit ist, und zum anderen, weil die Anwendung von ZNE mit PEA ziemlich viel Zeit in Anspruch nehmen wird.

Konkret werden Sie gebeten, die Schaltung, die der Abbildung 4b des Papiers entspricht, zu bauen und zu betreiben und die "unmitigated" Punkte selbst zu zeichnen. Wie Sie sehen, handelt es sich um eine 127-Qubit-Schaltung ×\times mit 60 Schichten (20 Trotter-Schritte) und der ⟨Z62⟩\langle Z_{62} \rangle als Beobachtungsgröße.

image.png

Klingt das nach einer großen Sache? Keine Sorge. Die letzten drei Lektionen dieses Kurses sind Sprungbretter. Zu Beginn werden wir ein kleineres Experiment vorführen, bei dem ein 27-Qubit-Schaltkreis ×\times mit 6 Schichten (2 Trotter-Schritte) und der ⟨Z13⟩\langle Z_{13} \rangle als Beobachtungsgröße auf einem gefälschten Gerät aufgebaut und betrieben wird.

Das war's mit der Einführung. Begeben wir uns auf ein Abenteuer im Versorgungsbereich!

import qiskit

qiskit.__version__

Output:

'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    fake_provider,
    EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()

2. Vorbereitung

2.1 Konstruiere RZZ(- π\pi /2)

Zunächst ist zu beachten, dass das RZZ-Gatter im Allgemeinen zwei CXCX -Gatter benötigt.

from qiskit.circuit.library import RZZGate

θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")

Output:

Output of the previous code cell

Wie bereits erwähnt, konzentrieren wir uns in diesem Experiment auf das RZZ-Gate mit einem bestimmten Winkel, - π\pi / 2. Wie in der Arbeit gezeigt wird, kann dies mit nur einem CXCX Gate realisiert werden.

qc2 = QuantumCircuit(2)

qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])

qc2.draw("mpl")

Output:

Output of the previous code cell

Wir definieren ein Gatter in Bezug auf diese Schaltung für zukünftige Referenzen.

rzz = qc2.to_gate(label="RZZ")

Lassen Sie uns eine zufällige Verwendung der neu definierten rzz vornehmen.

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output:

Output of the previous code cell

Bevor wir dies weiter verwenden, wollen wir die logische Äquivalenz von qc1 (das RZZ-Gatter) für -pi/2 und unser neu definiertes rzz oder qc2 Gatter überprüfen:

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)

Output:

True

2.2 Färben Sie die Kopplungskarte ein

Lassen Sie uns untersuchen, wie wir die Kopplungskarte eines Backends einfärben. Dies ist erforderlich, um die Interaktionen von ZZZZ in Schichten zu gruppieren.

Lassen Sie uns zunächst die Kopplungskarte eines Backends visualisieren. Beachten Sie, dass die Kopplungskarten für alle aktuellen IBM Quantum-Geräte schwer-hexagonal sind.

backend = service.least_busy(operational=True, simulator=False)

backend.coupling_map.draw()

Output:

Output of the previous code cell

Zum Einfärben einer Kopplungskarte verwenden wir rustworkx, ein Python -Paket für die Arbeit mit Graphen und komplexen Netzwerken. Es bietet mehrere Färbungsalgorithmen, die alle heuristisch sind und daher nicht garantieren, dass sie eine minimale Färbung finden.

Da Heavy-Hex-Graphen bipartit sind, greifen wir auf graph_bipartite_edge_color zurück, der eine minimale Färbung für diese Graphen finden sollte.

def color_coupling_map(backend):
    graph = backend.coupling_map.graph
    undirected_graph = graph.to_undirected(multigraph=False)
    edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
    if edge_color_map is None:
        edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
    # build a map from color to a list of edges
    edge_index_map = undirected_graph.edge_index_map()
    color_edges_map = {color: [] for color in edge_color_map.values()}
    for edge_index, color in edge_color_map.items():
        color_edges_map[color].append(
            (edge_index_map[edge_index][0], edge_index_map[edge_index][1])
        )
    return edge_color_map, color_edges_map

Schwere sechseckige Diagramme sollten in drei Farben gestrichen werden. Überprüfen wir dies für die obige Kopplungskarte.

edge_color_map, color_edges_map = color_coupling_map(backend)
print(
    f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)

Output:

ibm_strasbourg, 127-qubit device, 3 colors assigned.

Ja, das ist sie!

Lassen Sie uns zum Spaß die Kopplungskarte mit der erhaltenen Färbung malen, indem wir die Funktion der Rustworks-Visualisierung verwenden.

color_str_map = {0: "green", 1: "red", 2: "blue"}

undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
    undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
        edge_color_map[i]
    ]

rx.visualization.graphviz_draw(
    undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

Output:

Output of the previous code cell

3. Lösen Sie die Trotterisierte Zeitentwicklung eines Ising-Modells mit der Methode „ 2D “.

Definieren wir eine Routine zur Konstruktion eines Schaltkreises des Gebrauchspapiers für die zeitliche Entwicklung eines 2D Ising-Modells. Die Routine benötigt drei Parameter, ein Backend, eine ganze Zahl, die die Anzahl der Trotter-Schritte angibt, und einen booleschen Wert, der das Einfügen der Barriere steuert.

def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
    num_qubits = backend.num_qubits
    _, color_edges_map = color_coupling_map(backend)
    θ_h = Parameter("$\\theta_h$")
    qc = QuantumCircuit(num_qubits)

    for i in range(num_steps):
        qc.rx(θ_h, range(num_qubits))

        for _, edge_list in color_edges_map.items():
            for edge in edge_list:
                qc.append(rzz, edge)

        if barrier:
            qc.barrier()
    return qc

Bitte beachten Sie, dass wir die Zuordnung und das Routing der Qubits für die konstruierte Schaltung bereits manuell vorgenommen haben. Wenn wir also die Schaltung später transpilieren, müssen (sollten ) wir den Transpiler nicht bitten, das Qubit-Mapping und Routing zu übernehmen. Wie Sie gleich sehen werden, rufen wir es mit der Optimierungsstufe 1 und der Layoutmethode "trivial" auf.

Als Nächstes definieren wir eine einfache Routine, um die Informationen über die konstruierte Schaltung für eine schnelle Überprüfung zu erhalten.

def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
    qc0 = qc.decompose(reps=reps)
    return (
        f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
        + ",  "
        + f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
    )

Lassen Sie uns diese Routinen üben. Sie sollten einen Schaltkreis mit 27 Qubits sehen ×\times 15 Schichten (5 Trotter-Schritte). Da das gefälschte Gerät 28 Kanten hat, sollte es 28*5 Verschränkungslehren geben.

backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)

display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

Output:

Output of the previous code cell
27 qubits × 15 layers (20-depth),  Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth),  Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5

4. Lösen Sie die 27-Qubit-Version des Problems.

Wir demonstrieren nun eine kleinere Version des Nutzenexperiments. Wir bauen einen 27-Qubit ×\times 6-Schicht-Schaltkreis (2 Trotter-Schritte) mit ⟨Z13⟩\langle Z_{13} \rangle als Observable und lassen ihn sowohl auf AerSimulator als auch auf einem gefälschten Gerät laufen.

Natürlich folgen wir unserem vierstufigen Workflow, dem "Qiskit-Muster", das aus Map, Optimize, Execute und Post-Process besteht. Konkreter,

  • Zuordnung klassischer Eingaben zu einer Quantenberechnung.
  • Optimierung von Schaltungen für Quantenberechnungen.
  • Ausführen von Schaltungen mit Hilfe von Primitiven.
  • Nachbearbeitung und Rückgabe der Ergebnisse im klassischen Format.

Im Folgenden wird der Map-Schritt zur Erstellung einer Schaltung für ein kleineres Experiment beschrieben. Wir haben dann einen Satz von Optimize and Execute für AerSimulator und einen anderen für ein falsches Gerät. Schließlich folgt der Post-Process-Schritt zur Darstellung der Ergebnisse.

4.1 Schritt 1: Karte

backend = fake_provider.FakeTorontoV2()  # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [13], 1)], num_qubits=backend.num_qubits
)  # Falcon
angles = [
    0,
    0.1,
    0.2,
    0.3,
    0.4,
    0.5,
    0.6,
    0.7,
    0.8,
    1.0,
    np.pi / 2,
]  # We try 11 angles for theta_h.

4.2 Schritte 2 und 3: Optimieren und Ausführen (Simulator)

backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
    qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth),  Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28

Ein Nutzer führte die nächste Zelle mit einem MacBook Pro mit 2.3 GHz Quad-Core Intel Core i7 Prozessor aus, der mit 32GB 3LPDDR4X RAM ausgestattet ist, auf dem macOS 14.5 läuft. Es dauerte 161ms in Wandzeit. Jeder Laptop ist etwas anders.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()

Output:

CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 Schritte 2 und 3: Optimieren und ausführen (gefälschtes Gerät)

backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
    qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))

Output:

27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth),  Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14

Als derselbe Benutzer die nächste Zelle mit der gleichen Umgebung wie oben ausführte, benötigte er 2min 19s in Wall Time. Die Ausführung einer Schaltung auf einem gefälschten Gerät führt zu einer verrauschten Simulation, die viel mehr Zeit in Anspruch nimmt als eine exakte Simulation. Wir empfehlen, keine größeren Schaltungen (z. B. eine 27-Qubit-Schaltung ×\times 9 Schichten mit 3 Trotter-Schritten) auf einem gefälschten Gerät auszuführen.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()

Output:

CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 Schritt 4: Nachbearbeitung

Wir stellen die Ergebnisse von exakten und verrauschten Simulationen dar. Sie sehen die gravierenden Auswirkungen von Lärm auf FakeToronto.

plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

5. Lösen Sie die 127-Qubit-Version des Problems

Ihr Ziel ist es, das eingangs erwähnte Experiment im Versorgungsmaßstab durchzuführen. Sie werden eine 127-Qubit- und 60-Schichten-Schaltung (20 Trotter-Schritte) mit ⟨Z62⟩\langle Z_{62} \rangle als Observable erstellen und ausführen. Wir empfehlen Ihnen, dies selbst zu versuchen und dabei gegebenenfalls den Code für die 27-Qubit-Version zu verwenden. Aber die Lösung ist hier zu finden.

Lösung:

5.1 Schritt 1: Karte

# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)

num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
    [("Z", [62], 1)], num_qubits=backend_map.num_qubits
)  # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]

5.2 Schritte 2 und 3: Optimieren und ausführen

Wir stellen fest, dass die Kopplungskarte des Eagle-Prozessors 144 Kanten hat.

# backend = service.backend("ibm_brisbane")
backend = backend_map

transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))

Output:

156 qubits × 60 layers (221-depth),  Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth),  Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])

job_id = job.job_id()
print(f"job id={job_id}")

Output:

job id=d1479n6qf56g0081sxa0

5.3 Nachbearbeitung

Die Werte für die "entschärften" Punkte finden Sie in Abbildung 4b im Utility Paper. Stellen Sie diese zusammen mit Ihren Ergebnissen dar.

result_paper = [
    1.0171,
    1.0044,
    0.9563,
    0.9602,
    0.8394,
    0.8120,
    0.5466,
    0.4556,
    0.1953,
    0.0141,
    0.0117,
]

# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)

plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()

Output:

Output of the previous code cell

Sind Ihre Ergebnisse ähnlich wie die "ungemilderten" in Abbildung 4b? Je nach Gerät und dessen Zustand zum Zeitpunkt des Experiments können sie sehr unterschiedlich sein. Machen Sie sich keine Gedanken über die Ergebnisse selbst. Wir prüfen, ob Sie die Kodierung korrekt vorgenommen haben. Wenn Sie das getan haben, herzlichen Glückwunsch, dann haben Sie die Startlinie der Ära der Versorgungsunternehmen erreicht.

Wie in der Studie "Utility" haben Wissenschaftler aus aller Welt einen enormen Einfallsreichtum an den Tag gelegt, um selbst bei Rauschen aussagekräftige Ergebnisse zu erhalten. Das Endziel dieser kollektiven Bemühungen ist der Quantenvorteil: ein Zustand, in dem Quantencomputer einige Probleme, die in der Industrie gebraucht werden, schneller, mit höherer Genauigkeit oder billiger lösen können als klassische Computer. Dabei handelt es sich wahrscheinlich nicht um ein einmaliges Ereignis, sondern eher um eine Ära, in der die klassische Reproduktion von Quantenergebnissen immer länger dauert, bis irgendwann die Quantenvorlaufzeit von entscheidender Bedeutung wird. In Bezug auf den Quantenvorteil ist eines klar: Wir erreichen ihn nur durch Experimente im praktischen Maßstab. Wenn dieser Kurs dazu führt, dass Sie sich an der Suche beteiligen, die voller Herausforderungen und Spaß ist, würden wir uns sehr freuen.


Referenz

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