Kostenfunktionen
In dieser Lektion lernst du, wie du eine Kostenfunktion auswertest:
- Zunächst lernst du die Qiskit Runtime Primitives kennen
- Du definierst eine Kostenfunktion . Das ist eine problemspezifische Funktion, die das Ziel des Problems vorgibt, das der Optimierer minimieren (oder maximieren) soll
- Du legst eine Messstrategie mit den Qiskit Runtime Primitives fest, um Geschwindigkeit und Genauigkeit gegeneinander abzuwägen
Primitives
Alle physikalischen Systeme – ob klassisch oder quantenmechanisch – können in verschiedenen Zuständen vorliegen. Ein Auto auf einer Straße hat zum Beispiel eine bestimmte Masse, Position, Geschwindigkeit oder Beschleunigung, die seinen Zustand beschreiben. Quantensysteme können ebenfalls verschiedene Konfigurationen oder Zustände annehmen, unterscheiden sich aber von klassischen Systemen darin, wie Messungen und Zustandsentwicklung behandelt werden. Das führt zu einzigartigen Eigenschaften wie Superposition und Verschränkung, die ausschließlich in der Quantenmechanik auftreten. Genauso wie sich der Zustand eines Autos durch physikalische Größen wie Geschwindigkeit oder Beschleunigung beschreiben lässt, kann auch der Zustand eines Quantensystems durch Observablen beschrieben werden – mathematische Objekte, die messbare Größen repräsentieren.
In der Quantenmechanik werden Zustände durch normierte komplexe Spaltenvektoren, sogenannte Kets (), dargestellt, und Observablen sind hermitesche lineare Operatoren (), die auf die Kets wirken. Ein Eigenvektor () einer Observablen wird als Eigenzustand bezeichnet. Misst man eine Observable in einem ihrer Eigenzustände (), erhält man den zugehörigen Eigenwert () als Messergebnis.
Wenn du dich fragst, wie und was man an einem Quantensystem messen kann: Qiskit bietet zwei Primitives an:
Sampler: Gegeben einen Quantenzustand , ermittelt dieses Primitive die Wahrscheinlichkeit jedes möglichen Rechenbasisszustands.Estimator: Gegeben eine quantenmechanische Observable und einen Zustand , berechnet dieses Primitive den Erwartungswert von .
Das Sampler-Primitive
Das Sampler-Primitive berechnet die Wahrscheinlichkeit, jeden möglichen Zustand aus der Rechenbasis zu erhalten, wenn ein Quantenkreis den Zustand präpariert. Es berechnet
wobei die Anzahl der Qubits ist und die ganzzahlige Darstellung einer möglichen binären Ausgabezeichenkette (also ganze Zahlen zur Basis ).
Der Qiskit Runtime Sampler führt den Circuit mehrfach auf einem Quantengerät aus, nimmt bei jedem Durchlauf Messungen vor und rekonstruiert die Wahrscheinlichkeitsverteilung aus den erhaltenen Bitstrings. Je mehr Durchläufe (sogenannte Shots) durchgeführt werden, desto genauer sind die Ergebnisse – allerdings auf Kosten von mehr Zeit und Quantenressourcen.
Da die Anzahl möglicher Ausgaben jedoch exponentiell mit der Qubit-Anzahl wächst (nämlich ), muss auch die Anzahl der Shots exponentiell wachsen, um eine dichte Wahrscheinlichkeitsverteilung abzubilden. Deshalb ist Sampler nur für sparse Wahrscheinlichkeitsverteilungen effizient; der Zielzustand muss als Linearkombination von Rechenbasisszuständen ausdrückbar sein, wobei die Anzahl der Terme höchstens polynomiell mit der Qubit-Anzahl wächst:
Der Sampler kann auch so konfiguriert werden, dass er Wahrscheinlichkeiten nur für einen Teilbereich des Circuits abruft, was einem Teilmenge aller möglichen Zustände entspricht.
Das Estimator-Primitive
Das Estimator-Primitive berechnet den Erwartungswert einer Observablen für einen Quantenzustand ; die Wahrscheinlichkeiten der Observablen können als ausgedrückt werden, wobei die Eigenzustände der Observablen sind. Der Erwartungswert ist dann als Durchschnitt aller möglichen Messergebnisse (also der Eigenwerte der Observablen) des Zustands definiert, gewichtet mit den entsprechenden Wahrscheinlichkeiten:
Den Erwartungswert einer Observablen zu berechnen ist jedoch nicht immer möglich, da die Eigenbasis oft unbekannt ist. Der Qiskit Runtime Estimator verwendet einen komplexen algebraischen Prozess, um den Erwartungswert auf einem echten Quantengerät zu schätzen, indem er die Observable in eine Kombination anderer Observablen zerlegt, deren Eigenbasis bekannt ist.
Vereinfacht gesagt zerlegt Estimator jede Observable, die er nicht direkt messen kann, in einfachere, messbare Observablen – sogenannte Pauli-Operatoren.
Jeder Operator lässt sich als Kombination von Pauli-Operatoren ausdrücken.
sodass
wobei die Anzahl der Qubits ist, für (also ganze Zahlen zur Basis ) und .
Nach dieser Zerlegung leitet Estimator für jede Observable einen neuen Circuit aus dem ursprünglichen Circuit ab, um die Pauli-Observable in der Rechenbasis effektiv zu diagonalisieren und zu messen. Pauli-Observablen lassen sich leicht messen, weil im Voraus bekannt ist – was bei anderen Observablen im Allgemeinen nicht der Fall ist.
Für jedes führt Estimator den entsprechenden Circuit mehrfach auf einem Quantengerät aus, misst den Ausgangszustand in der Rechenbasis und berechnet die Wahrscheinlichkeit , jede mögliche Ausgabe zu erhalten. Anschließend sucht es den Eigenwert von für jede Ausgabe , multipliziert ihn mit und addiert alle Ergebnisse, um den Erwartungswert der Observablen für den gegebenen Zustand zu erhalten.
Da die Berechnung des Erwartungswerts von Paulis unpraktisch wäre (da sie exponentiell wächst), kann Estimator nur dann effizient sein, wenn ein Großteil der gleich null ist (also eine sparse statt dichte Pauli-Zerlegung). Formal gesagt muss für eine effizient lösbare Berechnung die Anzahl der von null verschiedenen Terme höchstens polynomiell mit der Qubit-Anzahl wachsen:
Der aufmerksame Leser bemerkt die implizite Annahme, dass auch das Wahrscheinlichkeits-Sampling effizient sein muss, wie es für Sampler erläutert wurde, was bedeutet:
Geführtes Beispiel zur Berechnung von Erwartungswerten
Nehmen wir den Ein-Qubit-Zustand und die Observable
mit dem theoretischen Erwartungswert
Da wir nicht wissen, wie wir diese Observable direkt messen können, müssen wir sie umschreiben zu . Dass dies zum gleichen Ergebnis führt, ergibt sich daraus, dass und .
Schauen wir uns an, wie man und direkt berechnet. Da und nicht kommutieren (sie teilen also keine gemeinsame Eigenbasis), können sie nicht gleichzeitig gemessen werden. Deshalb brauchen wir Hilfsschaltkreise:
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
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")
# Auxiliary circuit for X
aux_circuits[0].draw("mpl")
# Auxiliary circuit for Z
aux_circuits[1].draw("mpl")
Jetzt können wir die Berechnung manuell mit Sampler durchführen und die Ergebnisse mit 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}")
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 Grundlagen (optional)
Wenn man bezüglich der Eigenzustandsbasis von ausdrückt, , ergibt sich:
Da wir die Eigenwerte oder Eigenzustände der Ziel-Observablen nicht kennen, müssen wir zunächst ihre Diagonalisierung betrachten. Da hermitesch ist, gibt es eine unitäre Transformation mit , wobei die diagonale Eigenwertmatrix ist, sodass für und .
Daraus folgt, dass sich der Erwartungswert umschreiben lässt zu: