Kostenfunktionen
In dieser Lektion werden wir lernen, wie man eine Kostenfunktion auswertet:
- Zunächst werden wir uns mit den Grundelementen von „ IBM Quantum “ befassen.
- Definieren Sie eine Kostenfunktion . Dies ist eine problemspezifische Funktion, die das Ziel des Problems definiert, das der Optimierer minimieren (oder maximieren) soll
- Festlegung einer Messstrategie mit den „ IBM Quantum “-Primitiven zur Optimierung des Verhältnisses von Geschwindigkeit und Genauigkeit
Basiselemente
Alle physikalischen Systeme, ob klassisch oder quantenmechanisch, können in verschiedenen Zuständen existieren. So kann beispielsweise ein Auto auf einer Straße eine bestimmte Masse, Position, Geschwindigkeit oder Beschleunigung haben, die seinen Zustand charakterisieren. In ähnlicher Weise können auch Quantensysteme verschiedene Konfigurationen oder Zustände haben, aber sie unterscheiden sich von klassischen Systemen darin, wie wir mit Messungen und Zustandsentwicklung umgehen. Dies führt zu einzigartigen Eigenschaften wie Überlagerung und Verschränkung, die es nur in der Quantenmechanik gibt. So wie wir den Zustand eines Autos anhand physikalischer Eigenschaften wie Geschwindigkeit oder Beschleunigung beschreiben können, können wir auch den Zustand eines Quantensystems anhand von Observablen beschreiben, die mathematische Objekte sind.
In der Quantenmechanik werden Zustände durch normalisierte komplexe Spaltenvektoren oder kets ( ) dargestellt, und Beobachtungsgrößen sind lineare hermitesche Operatoren ( ), die auf die kets wirken. Ein Eigenvektor ( ) einer Beobachtungsgröße wird als Eigenzustand bezeichnet. Misst man eine Beobachtungsgröße für einen ihrer Eigenzustände ( ), so erhält man den entsprechenden Eigenwert ( ) als Messwert.
Wenn Sie sich fragen, wie man ein Quantensystem misst und was man messen kann, bietet Qiskit zwei Grundlegende und grundlegende Operationen oder Datentypen. Qiskit verfügt über die Primitive Sampler und Estimator, die als Bausteine für die einfache Konstruktion komplexer algorithmischer Workloads dienen. an, die Ihnen helfen können:
Sampler: Ausgehend von einem Quantenzustand ermittelt dieses Primitiv die Wahrscheinlichkeit jedes möglichen Berechnungsbasiszustands.Estimator: Bei einer Quantenbeobachtung und einem Zustand berechnet dieses Primitiv den Erwartungswert von .
Die Sampler-Primitive
Das Primitiv Sampler berechnet die Wahrscheinlichkeit, jeden möglichen Zustand aus der Berechnungsgrundlage zu erhalten, wenn eine Quantenschaltung den Zustand vorbereitet. Es errechnet
Dabei ist die Anzahl der Qubits und die ganzzahlige Darstellung jeder möglichen binären Ausgabezeichenfolge (d. h. Ganzzahlen zur Basis ).
Der „ IBM Quantum “ durchläuft Sampler den Schaltkreis mehrmals auf einem Quantenbauelement, führt bei jedem Durchlauf Messungen durch und rekonstruiert die Wahrscheinlichkeitsverteilung aus den wiederhergestellten Bitstrings. Je mehr Durchläufe (oder Berechnungen ) durchgeführt werden, desto genauer sind die Ergebnisse; dies erfordert jedoch mehr Zeit und Quantenressourcen.
Da jedoch die Anzahl der möglichen Ausgaben exponentiell mit der Anzahl der Qubits (d. h. ) wächst, muss auch die Anzahl der Schüsse exponentiell wachsen, um eine dichte Wahrscheinlichkeitsverteilung zu erfassen. Daher ist Sampler nur für spärliche Wahrscheinlichkeitsverteilungen effizient, wobei der Zielzustand als Linearkombination der Rechenbasiszustände ausgedrückt werden kann, wobei die Anzahl der Terme höchstens polynomiell mit der Anzahl der Qubits wächst:
Die Sampler kann auch so konfiguriert werden, dass sie Wahrscheinlichkeiten aus einem Teilbereich der Schaltung abruft, der eine Teilmenge der insgesamt möglichen Zustände darstellt.
Die Primitive „Estimator“
Das Primitiv Estimator berechnet den Erwartungswert einer Observablen für einen Quantenzustand ; wobei die Wahrscheinlichkeiten der Observablen als ausgedrückt werden können, wobei die Eigenzustände der Observablen sind. Der Erwartungswert ist dann definiert als der Durchschnitt aller möglichen Ergebnisse (d. h. der Eigenwerte der Observablen) einer Messung des Zustands , gewichtet mit den entsprechenden Wahrscheinlichkeiten:
Die Berechnung des Erwartungswerts einer Observablen ist jedoch nicht immer möglich, da wir ihre Eigenbasis oft nicht kennen. Bei der „ IBM Quantum “ Estimator wird ein komplexer algebraischer Prozess verwendet, um den Erwartungswert an einem realen Quantenbauelement zu schätzen, indem die Observable in eine Kombination anderer Observablen zerlegt wird, deren Eigenbasis uns bekannt ist.
Einfacher ausgedrückt, zerlegt Estimator jede Beobachtung, die es nicht zu messen weiß, in einfachere, messbare Beobachtungen, die Eine Gruppe von Matrizen, die in der Quanteninformatik häufig zur Darstellung und Manipulation von Quantenzuständen verwendet wird. Sie besteht aus der Identitätsmatrix und den drei Pauli-Matrizen (X, Y und Z). genannt werden.
Jeder Operator kann als eine Kombination von Pauli-Operatoren ausgedrückt werden.
sodass
dabei steht für die Anzahl der Qubits, für (d.h. ganze Zahlen zur Basis ) und .
Nach dieser Zerlegung leitet Estimator eine neue Schaltung für jede Beobachtungsgröße (aus der ursprünglichen Schaltung) ab, um die Pauli-Beobachtungsgröße in der Berechnungsgrundlage effektiv zu diagonalisieren und zu messen. Wir können Pauli Observablen leicht messen, weil wir im Voraus kennen, was bei anderen Observablen im Allgemeinen nicht der Fall ist.
Für jedes lässt Estimator den entsprechenden Schaltkreis auf einem Quantengerät mehrfach laufen, misst den Ausgangszustand in der Berechnungsbasis und berechnet die Wahrscheinlichkeit für jeden möglichen Ausgang . Dann sucht es den Eigenwert von , der jedem Ausgang entspricht, multipliziert mit und addiert alle Ergebnisse, um den Erwartungswert der Beobachtungsgröße für den gegebenen Zustand zu erhalten.
Da die Berechnung des Erwartungswerts von Paulis unpraktikabel ist (d. h. exponentiell anwächst), Estimator kann dies nur dann effizient sein, wenn eine große Anzahl von gleich Null ist (d. h. bei einer spärlichen statt einer dichten Pauli-Zerlegung). Formal ausgedrückt gilt: Damit diese Berechnung effizient gelöst werden kann, muss die Anzahl der von Null verschiedenen Terme höchstens polynominal mit der Anzahl der Qubits wachsen: .
Der Leser mag die implizite Annahme bemerken, dass die Wahrscheinlichkeit Der Vorgang, bei dem mehrere Messungen an einem oder mehreren Dingen vorgenommen werden. ebenfalls effizient sein muss, wie für Sampler erläutert, was bedeutet
Geführtes Beispiel zur Berechnung von Erwartungswerten
Nehmen wir den Ein-Qubit-Zustand an, und die Beobachtungsgröße
mit dem folgenden theoretischen Erwartungswert .
Da wir nicht wissen, wie wir diese Beobachtungsgröße messen können, können wir ihren Erwartungswert nicht direkt berechnen, sondern müssen ihn als neu ausdrücken. Es kann gezeigt werden, dass und zum gleichen Ergebnis führen.
Sehen wir uns an, wie man und direkt berechnen kann. Da und nicht kommutieren (d. h. nicht dieselbe Eigenbasis haben), können sie nicht gleichzeitig gemessen werden, weshalb wir die Hilfsschaltungen benötigen:
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
# The following code will work for any other initial single-qubit state and observable
original_circuit = QuantumCircuit(1)
original_circuit.h(0)
H = SparsePauliOp(["X", "Z"], [2, -1])
aux_circuits = []
for pauli in H.paulis:
aux_circ = original_circuit.copy()
aux_circ.barrier()
if str(pauli) == "X":
aux_circ.h(0)
elif str(pauli) == "Y":
aux_circ.sdg(0)
aux_circ.h(0)
else:
aux_circ.id(0)
aux_circ.measure_all()
aux_circuits.append(aux_circ)
original_circuit.draw("mpl")Output:
# Auxiliary circuit for X
aux_circuits[0].draw("mpl")Output:
# Auxiliary circuit for Z
aux_circuits[1].draw("mpl")Output:
Wir können die Berechnung nun manuell mit Sampler durchführen und die Ergebnisse auf Estimator überprüfen:
from qiskit.primitives import StatevectorSampler, StatevectorEstimator
from qiskit.result import QuasiDistribution
import numpy as np
## SAMPLER
shots = 10000
sampler = StatevectorSampler()
job = sampler.run(aux_circuits, shots=shots)
# Run the sampler job and step through results
expvals = []
for index, pauli in enumerate(H.paulis):
data_pub = job.result()[index].data
bitstrings = data_pub.meas.get_bitstrings()
counts = data_pub.meas.get_counts()
quasi_dist = QuasiDistribution(
{outcome: freq / shots for outcome, freq in counts.items()}
)
# Use the probabilities and known eigenvalues of Pauli operators to estimate
# the expectation value.
val = 0
if str(pauli) == "X":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)
if str(pauli) == "Y":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)
if str(pauli) == "Z":
val += 1 * quasi_dist.get(0, 0)
val += -1 * quasi_dist.get(1, 0)
expvals.append(val)
# Print expectation values
print("Sampler results:")
for pauli, expval in zip(H.paulis, expvals):
print(f" >> Expected value of {str(pauli)}: {expval:.5f}")
total_expval = np.sum(H.coeffs * expvals).real
print(f" >> Total expected value: {total_expval:.5f}")
# Use estimator for comparison
observables = [
*H.paulis,
H,
] # Note: run for individual Paulis as well as full observable H
estimator = StatevectorEstimator()
job = estimator.run([(original_circuit, observables)])
estimator_expvals = job.result()[0].data.evs
# Print results
print("Estimator results:")
for obs, expval in zip(observables, estimator_expvals):
if obs is not H:
print(f" >> Expected value of {str(obs)}: {expval:.5f}")
else:
print(f" >> Total expected value: {expval:.5f}")Output:
Sampler results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00420
>> Total expected value: 1.99580
Estimator results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00000
>> Total expected value: 2.00000
Mathematische Strenge (optional)
Wenn man in Bezug auf die Basis der Eigenzustände von , , ausdrückt, ergibt sich folgendes:
Da wir weder die Eigenwerte noch die Eigenzustände der Zielgrößen kennen, müssen wir zunächst deren Diagonalisierung betrachten. Da gleich Eine hermitsche Matrix ist eine quadratische Matrix, die gleich ihrer eigenen konjugierten Transponierung ist, oder ein linearer Operator, der selbstadjungiert ist. ist, gibt es eine unitäre Transformation , sodass gilt, wobei die Diagonalmatrix der Eigenwerte ist, sodass gilt, wenn ist, und .
Dies bedeutet, dass der Erwartungswert wie folgt umgeschrieben werden kann:
Wenn sich ein System im Zustand befindet, ist die Wahrscheinlichkeit, dass gemessen wird, . Der obige Erwartungswert kann wie folgt ausgedrückt werden:
Es ist sehr wichtig zu beachten, dass die Wahrscheinlichkeiten aus dem Zustand anstelle von entnommen werden. Deshalb ist die Matrix unbedingt erforderlich.
Sie fragen sich vielleicht, wie man die Matrix und die Eigenwerte erhält. Hätten Sie die Eigenwerte bereits, bräuchten Sie keinen Quantencomputer, denn das Ziel von Variationsalgorithmen ist es, diese Eigenwerte von zu finden.
Glücklicherweise gibt es einen Ausweg: Jede -Matrix lässt sich als Linearkombination von Tensorprodukten aus Pauli-Matrizen und Identitäten darstellen, die alle sowohl hermitisch als auch unitär sind und deren und bekannt sind. Genau das macht Estimator die IBM Quantum intern, indem sie jedes Objekt Operator in ein zerlegt SparsePauliOp.
Hier sind die Operatoren, die verwendet werden können:
Schreiben wir also in Bezug auf die Paulis und Identitäten um:
wobei für (d. h. die Basis ) und steht:
wobei und , sodass gilt: .
Kostenfunktionen
Im Allgemeinen werden Kostenfunktionen verwendet, um das Ziel eines Problems zu beschreiben und zu ermitteln, wie gut ein Versuchszustand im Hinblick auf dieses Ziel abschneidet. Diese Definition lässt sich auf verschiedene Beispiele in den Bereichen Chemie, maschinelles Lernen, Finanzen, Optimierung usw. anwenden.
Betrachten wir ein einfaches Beispiel für die Bestimmung des Grundzustands eines Systems. Unser Ziel ist es, den Erwartungswert der Beobachtungsgröße zu minimieren, die die Energie darstellt (Hamiltonian ):
Wir können die Estimator verwenden, um den Erwartungswert zu bewerten und diesen Wert zur Minimierung an einen Optimierer zu übergeben. Wenn die Optimierung erfolgreich ist, liefert sie einen Satz optimaler Parameterwerte , aus denen wir den vorgeschlagenen Lösungszustand konstruieren und den beobachteten Erwartungswert als berechnen können.
Beachten Sie, dass wir die Kostenfunktion nur für die begrenzte Anzahl von Zuständen minimieren können, die wir betrachten. Dies führt uns zu zwei verschiedenen Möglichkeiten:
- Unser Ansatz definiert den Lösungszustand nicht für den gesamten Suchraum : Wenn dies der Fall ist, wird unser Optimierer niemals die Lösung finden, und wir müssen mit anderen Ansätzen experimentieren, die unseren Suchraum vielleicht genauer darstellen können.
- Unser Optimierer ist nicht in der Lage, diese gültige Lösung zu finden : Die Optimierung kann global und lokal definiert werden. Was das bedeutet, werden wir in einem späteren Abschnitt erläutern.
Alles in allem werden wir eine klassische Optimierungsschleife durchführen, aber die Auswertung der Kostenfunktion einem Quantencomputer überlassen. Aus dieser Perspektive könnte man sich die Optimierung als ein rein klassisches Unterfangen vorstellen, bei dem wir jedes Mal, wenn der Optimierer die Kostenfunktion auswerten muss, die Seite Ein hypothetisches Gerät oder eine Softwarekomponente, das/die eine bestimmte Funktion ausführt, dessen interne Funktionsweise jedoch unbekannt ist. Ein Benutzer kennt nur die Eingaben und Ausgaben der Blackbox und weiß nicht, wie die Blackbox die Eingaben verarbeitet, um die Ausgaben zu erzeugen. aufrufen.
def cost_func_vqe(params, circuit, hamiltonian, estimator):
"""Return estimate of energy from estimator
Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance
Returns:
float: Energy estimate
"""
pub = (circuit, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return costfrom qiskit.circuit.library import TwoLocal
observable = SparsePauliOp.from_list([("XX", 1), ("YY", -3)])
reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)
variational_form = TwoLocal(
2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)
ansatz = reference_circuit.compose(variational_form)
theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
ansatz.decompose().draw("mpl")Output:
Wir werden dies zunächst mit einem Simulator durchführen: StatevectorEstimator. Dies ist normalerweise für die Fehlersuche ratsam, aber wir werden dem Fehlersuchlauf sofort eine Berechnung auf echter Quantenhardware folgen lassen. In zunehmendem Maße sind interessante Probleme nicht mehr klassisch simulierbar ohne modernste Supercomputing-Einrichtungen.
estimator = StatevectorEstimator()
cost = cost_func_vqe(theta_list, ansatz, observable, estimator)
print(cost)Output:
[-0.58744589]
Wir werden nun mit der Ausführung auf einem echten Quantencomputer fortfahren. Beachten Sie die Änderungen in der Syntax. Die Schritte im Zusammenhang mit dem pass_manager werden im nächsten Beispiel näher erläutert. Ein besonders wichtiger Schritt bei Variationsalgorithmen ist die Verwendung einer Sitzung des „ IBM Quantum “-Compute-Service. Durch das Starten einer Sitzung können Sie mehrere Iterationen eines Variationsalgorithmus ausführen, ohne bei jeder Aktualisierung der Parameter erneut in einer neuen Warteschlange warten zu müssen. Dies ist wichtig, wenn die Wartezeiten lang sind und/oder viele Durchläufe erforderlich sind. Bitte beachten Sie, dass Nutzer des Open-Plan-Tarifs keine Quantum-Compute-Sitzungen nutzen können. Falls Sie keinen Zugriff auf die Sitzungen haben, können Sie die Anzahl der Iterationen, die Sie zu einem bestimmten Zeitpunkt übermitteln, reduzieren und die zuletzt verwendeten Parameter für zukünftige Durchläufe speichern. Wenn Sie zu viele Iterationen übermitteln oder auf zu lange Wartezeiten stoßen, wird möglicherweise der Fehlercode 1217 angezeigt, der auf lange Verzögerungen zwischen den Auftragsübermittlungen hinweist.
# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor
# Load necessary packages:
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
# Select the least busy backend:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")
# Use a pass manager to transpile the circuit and observable for the specific backend being used:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_observable = observable.apply_layout(layout=isa_ansatz.layout)
# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
# Open a Quantum Compute session:
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)
session.close()
print(cost)Beachten Sie, dass die aus den beiden obigen Berechnungen resultierenden Werte sehr ähnlich sind. Auf Techniken zur Verbesserung der Ergebnisse wird weiter unten eingegangen.
Beispiel für die Zuordnung zu nicht-physikalischen Systemen
Das Maximal-Schnitt-Problem (Max-Cut-Problem) ist ein kombinatorisches Optimierungsproblem, bei dem es darum geht, die Knoten eines Graphen in zwei disjunkte Mengen zu unterteilen, sodass die Anzahl der Kanten zwischen den beiden Mengen maximiert wird. Etwas formeller ausgedrückt: Bei einem ungerichteten Graphen , wobei die Menge der Knoten und die Menge der Kanten ist, besteht das Max-Cut-Problem darin, die Knoten in zwei disjunkte Teilmengen und zu partitionieren, sodass die Anzahl der Kanten, bei denen ein Endpunkt in und der andere in liegt, maximiert wird.
Wir können Max-Cut zur Lösung verschiedener Probleme einsetzen, darunter: Clusterbildung, Netzwerkdesign, Phasenübergänge und so weiter. Zunächst erstellen wir ein Problemdiagramm:
import rustworkx as rx
from rustworkx.visualization import mpl_draw
n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)
mpl_draw(
G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)Output:
Dieses Problem kann als binäres Optimierungsproblem ausgedrückt werden. Für jeden Knoten , wobei die Anzahl der Knoten des Graphen ist (in diesem Fall ), wird die binäre Variable betrachtet. Diese Variable hat den Wert , wenn der Knoten zu einer der Gruppen gehört, die wir als bezeichnen, und , wenn er zu der anderen Gruppe gehört, die wir als bezeichnen. Wir bezeichnen auch als (Element der Adjazenzmatrix ) das Gewicht der Kante, die vom Knoten zum Knoten führt. Da der Graph ungerichtet ist, . Dann können wir unser Problem als Maximierung der folgenden Kostenfunktion formulieren:
Um dieses Problem mit einem Quantencomputer zu lösen, werden wir die Kostenfunktion als den Erwartungswert einer Beobachtungsgröße ausdrücken. Die Observablen, die Qiskit zulässt, bestehen jedoch von Natur aus aus Pauli-Operatoren, die Eigenwerte und anstelle von und haben. Aus diesem Grund werden wir den folgenden Variablenwechsel vornehmen:
Wobei . Wir können die Adjazenzmatrix verwenden, um bequem auf die Gewichte aller Kanten zuzugreifen. Auf diese Weise erhalten wir unsere Kostenfunktion:
Dies bedeutet, dass:
Die neue Kostenfunktion, die wir maximieren wollen, lautet also:
Außerdem besteht die natürliche Tendenz eines Quantencomputers darin, Minima (in der Regel die niedrigste Energie) anstelle von Maxima zu finden, so dass wir anstelle der Maximierung von die Minimierung anstreben:
Da wir nun eine zu minimierende Kostenfunktion haben, deren Variablen die Werte und annehmen können, können wir die folgende Analogie mit dem Pauli herstellen:
Mit anderen Worten, die Variable ist äquivalent zu einem Gate, das auf das Qubit wirkt. Außerdem:
Die Beobachtungsgröße, die wir betrachten wollen, ist also die folgende:
zu dem wir anschließend den unabhängigen Begriff hinzufügen müssen:
Der Operator ist eine Linearkombination von Termen mit Z-Operatoren auf Knoten, die durch eine Kante verbunden sind (das 0. Qubit ist am weitesten rechts): . Sobald der Operator konstruiert ist, kann der Ansatz für den QAOA-Algorithmus leicht mit Hilfe der QAOAAnsatz -Schaltung aus der Qiskit-Schaltungsbibliothek erstellt werden.
from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp
hamiltonian = SparsePauliOp.from_list(
[("IIZZ", 1), ("IZIZ", 1), ("IZZI", 1), ("ZIIZ", 1), ("ZZII", 1)]
)
ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")Output:
# Sum the weights, and divide by 2
offset = -sum(edge[2] for edge in edges) / 2
print(f"""Offset: {offset}""")Output:
Offset: -2.5
Da der „ IBM Quantum “-Estimator direkt einen Hamilton- und einen parametrisierten Ansatz berücksichtigt und die erforderliche Energie zurückgibt, ist die Kostenfunktion für eine QAOA-Instanz recht einfach:
def cost_func(params, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator
Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance
Returns:
float: Energy estimate
"""
pub = (ansatz, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
# cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]
return costimport numpy as np
x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)
estimator = StatevectorEstimator()
cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)
print(cost)Output:
1.473098768180865
# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24
# Load some necessary packages:
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
# Select the least busy backend:
backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")
# Use a pass manager to transpile the circuit and observable for the specific backend being used:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)
# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
# Open a Quantum Compute session:
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)
# Close session after done
session.close()
print(cost)Output:
1.1120776913677988
Wir werden dieses Beispiel in Anwendungen wieder aufgreifen, um zu untersuchen, wie ein Optimierer zur Iteration durch den Suchraum eingesetzt werden kann. Dazu gehören im Allgemeinen:
- Einsatz eines Optimierers zur Ermittlung optimaler Parameter
- Bindung optimaler Parameter an den Ansatz zur Ermittlung der Eigenwerte
- Übertragung der Eigenwerte auf unsere Problemstellung
Messstrategie: Geschwindigkeit versus Genauigkeit
Wie bereits erwähnt, verwenden wir einen verrauschten Quantencomputer als Blackbox-Orakel, bei dem Rauschen die abgerufenen Werte nicht deterministisch machen kann, was zu zufälligen Fluktuationen führt, die wiederum die Konvergenz bestimmter Optimierer zu einer vorgeschlagenen Lösung beeinträchtigen oder sogar ganz verhindern. Dies ist ein allgemeines Problem, mit dem wir uns auseinandersetzen müssen, wenn wir schrittweise den Quantennutzen erforschen und Fortschritte in Richtung Quantenvorteil machen:
Wir können die Optionen zur Fehlerunterdrückung und Fehlerminderung der „ IBM Quantum “-Primitive nutzen, um Rauschen zu bekämpfen und den Nutzen heutiger Quantencomputer zu maximieren.
Fehlerunterdrückung
Unter Fehlerunterdrückung versteht man Techniken, mit denen eine Schaltung während der Kompilierung optimiert und umgewandelt wird, um Fehler zu minimieren. Dies ist eine grundlegende Technik zur Fehlerbehandlung, die in der Regel zu einem gewissen Maß an klassischer Vorverarbeitungs Zusätzliche Kosten, die durch neue Techniken im Vergleich zu einer Basisimplementierung entstehen. t der gesamten Laufzeit führt. Der Mehraufwand umfasst das Transpiling von Schaltkreisen für die Ausführung auf Quantenhardware durch:
- Darstellung der Schaltung unter Verwendung der auf einem Quantensystem verfügbaren nativen Gatter
- Abbildung der virtuellen Qubits auf physikalische Qubits
- Hinzufügen von SWAPs auf der Grundlage von Konnektivitätsanforderungen
- Optimierung von 1Q und 2Q gates
- Dynamische Entkopplung inaktiver Qubits, um die Auswirkungen der Dekohärenz zu verhindern.
Primitive ermöglichen die Verwendung von Fehlerunterdrückungstechniken, indem die Option optimization_level gesetzt und erweiterte Transpilierungsoptionen ausgewählt werden. In einem späteren Kurs werden wir uns mit verschiedenen Methoden zum Aufbau von Schaltkreisen befassen, um die Ergebnisse zu verbessern, aber für die meisten Fälle empfehlen wir die Einstellung optimization_level=3.
Wir werden den Wert der zunehmenden Optimierung im Umsetzungsprozess anhand einer Beispielschaltung mit einem einfachen Idealverhalten veranschaulichen.
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
theta = Parameter("theta")
qc = QuantumCircuit(2)
qc.x(1)
qc.h(0)
qc.cp(theta, 0, 1)
qc.h(0)
observables = SparsePauliOp.from_list([("ZZ", 1)])
qc.draw("mpl")Output:
Die obige Schaltung kann sinusförmige Erwartungswerte der gegebenen Beobachtungsgröße liefern, vorausgesetzt, wir fügen Phasen ein, die ein geeignetes Intervall abdecken, wie z. B. .
## Setup phases
import numpy as np
phases = np.linspace(0, 2 * np.pi, 50)
# phases need to be expressed as a list of lists in order to work
individual_phases = [[phase] for phase in phases]Wir können einen Simulator verwenden, um die Nützlichkeit einer optimierten Transpilation zu zeigen. Wir werden weiter unten auf die Verwendung echter Hardware zurückkommen, um die Nützlichkeit der Fehlerbegrenzung zu demonstrieren. Wir verwenden QiskitRuntimeService, um ein reales Backend zu erhalten (in diesem Fall ibm_brisbane), und verwenden AerSimulator, um dieses Backend zu simulieren, einschließlich seines Rauschverhaltens.
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator
# get a real backend from the runtime service
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
# generate a simulator that mimics the real quantum system with the latest calibration results
backend_sim = AerSimulator.from_backend(backend)Wir können nun einen Pass-Manager verwenden, um die Schaltung in die „Befehlssatzarchitektur“ (ISA) des Backends zu transpilieren. Dies ist eine neue Anforderung im „ IBM Quantum Compute Service“: Alle an ein Backend übermittelten Schaltkreise müssen den Einschränkungen der Zielarchitektur des Backends entsprechen, d. h., sie müssen unter Berücksichtigung der ISA des Backends geschrieben sein – also des Befehlssatzes, den das Gerät verstehen und ausführen kann. Diese Zielbeschränkungen werden durch Faktoren wie die nativen Basisgatter des Geräts, seine Qubit-Konnektivität und – sofern relevant – seine Impuls- und sonstigen Befehlszeitvorgaben bestimmt.
Beachten Sie, dass wir dies im vorliegenden Fall zweimal tun werden: einmal mit Optimierungsgrad = 0 und einmal mit einem Wert von 3. Wir werden jedes Mal das Primitiv "Schätzer" verwenden, um die Erwartungswerte der Beobachtungsgröße bei verschiedenen Phasenwerten zu schätzen.
# Import estimator and specify that we are using the simulated backend:
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(mode=backend_sim)
circuit = qc# Use a pass manager to transpile the circuit and observable for the backend being simulated.
# Start with no optimization:
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
noisy_exp_values = []
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
noisy_exp_values = cost[0]
# Repeat above steps, but now with optimization = 3:
exp_values_with_opt_es = []
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
exp_values_with_opt_es = cost[0]Schließlich können wir die Ergebnisse grafisch darstellen und sehen, dass die Genauigkeit der Berechnung auch ohne Optimierung recht gut war, sich aber durch die Erhöhung der Optimierung auf Stufe 3 deutlich verbesserte. Beachten Sie, dass in tieferen, komplizierteren Schaltungen der Unterschied zwischen den Optimierungsstufen 0 und 3 wahrscheinlich größer ist. Dies ist eine sehr einfache Schaltung, die als Spielzeugmodell verwendet wird.
import matplotlib.pyplot as plt
plt.plot(phases, noisy_exp_values, "o", label="opt=0")
plt.plot(phases, exp_values_with_opt_es, "o", label="opt=3")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()Output:
Fehlerbehebung
Unter Fehlerminderung versteht man Techniken, mit denen Benutzer Schaltungsfehler reduzieren können, indem sie das Rauschen des Bausteins zum Zeitpunkt der Ausführung modellieren. In der Regel führt dies zu einem Aufwandsaufschlag bei der Quantenvorverarbeitung im Zusammenhang mit dem Modelltraining sowie zu einem Aufwandsaufschlag bei der klassischen Nachbearbeitung, um Fehler in den Rohdaten mithilfe des generierten Modells zu beheben.
Die Option resilience_level des Primitivs „ IBM Quantum “ legt fest, inwieweit das System gegen Fehler gewappnet sein soll. Höhere Stufen liefern genauere Ergebnisse, was jedoch aufgrund des durch die Quantenabtastung verursachten Mehraufwands mit längeren Verarbeitungszeiten einhergeht. Anhand der Resilienzstufen lässt sich das Verhältnis zwischen Kosten und Genauigkeit konfigurieren, wenn Sie bei Ihrer primitiven Abfrage eine Fehlerminderung anwenden.
Wenn wir eine Technik zur Fehlerbegrenzung anwenden, erwarten wir, dass die Eine systematische Abweichung in den gemessenen Größen, die in der Regel durch Fehler verursacht wird. in unseren Ergebnissen im Vergleich zur vorherigen, ungemilderten Verzerrung reduziert wird. In einigen Fällen kann die Verzerrung sogar verschwinden. Dies hat jedoch seinen Preis. In dem Maße, in dem wir die Verzerrung in unseren geschätzten Mengen reduzieren, wird die statistische Variabilität (d. h. die Varianz) zunehmen, was wir durch eine weitere Erhöhung der Anzahl der Schüsse pro Kreislauf in unserem Stichprobenverfahren berücksichtigen können. Dies führt zu einem Mehraufwand, der über den zur Reduzierung der Verzerrung erforderlichen Aufwand hinausgeht, und wird daher nicht standardmäßig durchgeführt. Wir können dieses Verhalten ganz einfach einstellen, indem wir die Anzahl der Schüsse pro Schaltkreis in options.executions.shots anpassen, wie im folgenden Beispiel gezeigt.
In diesem Kurs werden wir diese Modelle zur Fehlerminderung auf allgemeiner Ebene untersuchen, um zu veranschaulichen, wie die Primitive von „ IBM Quantum “ zur Fehlerminderung beitragen können, ohne dabei auf alle Implementierungsdetails einzugehen.
Twirled Readout Error Extinction (T-REx)
Twirled Readout Error Extinction (T-REx) verwendet eine Technik, die als Pauli-Twirling bekannt ist, um das Rauschen zu reduzieren, das während des Prozesses der Quantenmessung entsteht. Diese Technik setzt keine spezifische Form von Lärm voraus, was sie sehr allgemein und effektiv macht.
Gesamter Arbeitsablauf:
- Erfassen von Daten für den Nullzustand mit zufälligen Bitflips (Pauli X vor der Messung)
- Erfassen von Daten für den gewünschten (verrauschten) Zustand mit zufälligen Bitflips (Pauli X vor der Messung)
- Berechnen Sie die spezielle Funktion für jeden Datensatz und teilen Sie sie.
Wir können dies mit options.resilience_level = 1 einstellen, wie im folgenden Beispiel gezeigt wird.
Null-Rausch-Extrapolation
Bei der rauschfreien Extrapolation (ZNE) wird zunächst das Rauschen in der Schaltung, die den gewünschten Quantenzustand vorbereitet, verstärkt, es werden Messungen für verschiedene Rauschpegel durchgeführt, und aus diesen Messungen wird das rauschfreie Ergebnis abgeleitet.
Gesamter Arbeitsablauf:
- Verstärkung des Schaltungsrauschens für verschiedene Rauschfaktoren
- Jede geräuschverstärkte Schaltung ausführen
- Extrapolation zurück zur Null-Lärm-Grenze
Das können wir mit options.resilience_level = 2… einstellen. Wir könnten dies noch weiter optimieren, indem wir verschiedene noise_factors, noise_amplifiers, und untersuchen extrapolators, doch dies würde den Rahmen dieses Kurses sprengen. Wir empfehlen Ihnen, mit diesen Optionen wie hier beschrieben zu experimentieren.
Jede Methode ist mit einem gewissen Overhead verbunden: ein Kompromiss zwischen der Anzahl der erforderlichen Quantenberechnungen (Zeit) und der Genauigkeit unserer Ergebnisse:
Nutzung der Optionen in „Quantum Compute“ zur Risikominderung und Unterdrückung
So berechnen Sie einen Erwartungswert unter Verwendung der Fehlerminderung und -unterdrückung im „ IBM Quantum “ Compute Service. Wir können genau dieselbe Schaltung und dieselbe Observable wie zuvor verwenden, diesmal jedoch die Optimierungsstufe auf Stufe 2 festlegen und nun die Robustheit bzw. die eingesetzten Fehlerminderungstechniken anpassen. Dieser Fehlerminderungsprozess wird im Verlauf einer Optimierungsschleife mehrfach durchgeführt.
Wir führen diesen Teil auf echter Hardware durch, da die Fehlerminderung auf Simulatoren nicht möglich ist.
# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import (
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)
# We select the least busy backend
# Select the least busy backend
# backend = service.least_busy(
# operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
# )
# Or use a specific backend
backend = service.backend("ibm_brisbane")
# Initialize some variables to save the results from different runs:
exp_values_with_em0_es = []
exp_values_with_em1_es = []
exp_values_with_em2_es = []
# Use a pass manager to optimize the circuit and observables for the backend chosen:
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
# Open a session and run with no error mitigation:
estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em0_es = cost[0]
# Open a session and run with resilience = 1:
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em1_es = cost[0]
# Open a session and run with resilience = 2:
estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em2_es = cost[0]Wie zuvor können wir die resultierenden Erwartungswerte als Funktion des Phasenwinkels für die drei verwendeten Stufen der Fehlerminderung aufzeichnen. Mit großer Mühe kann man erkennen, dass die Fehlerbegrenzung die Ergebnisse leicht verbessert. Auch dieser Effekt ist in tieferen, komplizierteren Schaltkreisen viel stärker ausgeprägt.
import matplotlib.pyplot as plt
plt.plot(phases, exp_values_with_em0_es, "o", label="unmitigated")
plt.plot(phases, exp_values_with_em1_es, "o", label="resil = 1")
plt.plot(phases, exp_values_with_em2_es, "o", label="resil = 2")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()Output:
Zusammenfassung
In dieser Lektion haben Sie gelernt, wie man eine Kostenfunktion erstellt:
- Erstellen einer Kostenfunktion
- Wie man „ IBM Quantum “-Primitive nutzt, um Rauschen zu mindern und zu unterdrücken
- Wie man eine Messstrategie definiert, um Geschwindigkeit und Genauigkeit zu optimieren
Das ist unsere Arbeitsbelastung durch Variationen auf hohem Niveau:
Unsere Kostenfunktion läuft während jeder Iteration der Optimierungsschleife. In der nächsten Lektion wird untersucht, wie der klassische Optimierer unsere Kostenfunktionsbewertung zur Auswahl neuer Parameter verwendet.
import qiskit
import qiskit_ibm_runtime
print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())Output:
1.1.0
0.23.0