Fonctions de coût
Dans cette leçon, nous apprendrons à évaluer une fonction de coût :
- D'abord, nous découvrirons les primitives de Qiskit Runtime
- Nous définirons une fonction de coût . C'est une fonction spécifique au problème qui définit l'objectif que l'optimiseur doit minimiser (ou maximiser)
- Nous définirons une stratégie de mesure avec les primitives de Qiskit Runtime pour équilibrer vitesse et précision
Primitives
Tous les systèmes physiques, qu'ils soient classiques ou quantiques, peuvent exister dans différents états. Par exemple, une voiture sur une route peut avoir une certaine masse, position, vitesse ou accélération qui caractérisent son état. De la même manière, les systèmes quantiques peuvent aussi avoir différentes configurations ou états, mais ils diffèrent des systèmes classiques dans la façon dont nous traitons les mesures et l'évolution de l'état. Cela donne lieu à des propriétés uniques comme la superposition et l'intrication, exclusives à la mécanique quantique. Tout comme nous pouvons décrire l'état d'une voiture en utilisant des propriétés physiques comme la vitesse ou l'accélération, nous pouvons aussi décrire l'état d'un système quantique en utilisant des observables, qui sont des objets mathématiques.
En mécanique quantique, les états sont représentés par des vecteurs colonnes complexes normalisés, ou kets (), et les observables sont des opérateurs linéaires hermitiens () qui agissent sur les kets. Un vecteur propre () d'un observable est connu comme un état propre. Mesurer un observable pour l'un de ses états propres () nous donnera la valeur propre correspondante () comme résultat.
Si tu te demandes comment mesurer un système quantique et ce que tu peux mesurer, Qiskit offre deux primitives qui peuvent t'aider :
Sampler: étant donné un état quantique , cette primitive obtient la probabilité de chaque état possible de la base computationnelle.Estimator: étant donné un observable quantique et un état , cette primitive calcule la valeur d'espérance de .
La primitive Sampler
La primitive Sampler calcule la probabilité d'obtenir chaque état possible de la base computationnelle, étant donné un circuit quantique qui prépare l'état . Elle calcule
o ù est le nombre de qubits, et la représentation entière de toute chaîne binaire de sortie possible (c'est-à-dire des entiers en base ).
Le Sampler de Qiskit Runtime exécute le circuit plusieurs fois sur un dispositif quantique, effectue des mesures à chaque exécution et reconstruit la distribution de probabilité à partir des chaînes de bits obtenues. Plus il y a d'exécutions (ou shots), plus les résultats seront précis, mais cela nécessite plus de temps et de ressources quantiques.
Cependant, étant donné que le nombre de sorties possibles croît exponentiellement avec le nombre de qubits (c'est-à-dire ), le nombre de shots devra également croître exponentiellement pour capturer une distribution de probabilité dense. Par conséquent, Sampler n'est efficace que pour des distributions de probabilité éparses ; où l'état cible doit pouvoir s'exprimer comme une combinaison linéaire des états de la base computationnelle, avec le nombre de termes croissant au plus polynomialement avec le nombre de qubits :
Le Sampler peut également être configuré pour récupérer les probabilités d'une sous-section du circuit, ce qui représente un sous-ensemble du total des états possibles.
La primitive Estimator
La primitive Estimator calcule la valeur d'espérance d'un observable pour un état quantique ; où les probabilités de l'observable peuvent s'exprimer comme , étant les états propres de l'observable . La valeur d'espérance est alors définie comme la moyenne de tous les résultats possibles (c'est-à-dire les valeurs propres de l'observable) d'une mesure de l'état , pondérée par les probabilités correspondantes :
Cependant, calculer la valeur d'espérance d'un observable n'est pas toujours possible, car nous ne connaissons souvent pas sa base propre. L'Estimator de Qiskit Runtime utilise un processus algébrique complexe pour estimer la valeur d'espérance sur un dispositif quantique réel, en décomposant l'observable en une combinaison d'autres observables dont nous connaissons la base propre.
En termes plus simples, Estimator décompose tout observable qu'il ne sait pas mesurer en observables plus simples et mesurables appelés opérateurs de Pauli.
Tout opérateur peut s'exprimer comme une combinaison de opérateurs de Pauli.
tel que
où est le nombre de qubits, pour (c'est-à-dire des entiers en base ), et .
Après avoir effectué cette décomposition, Estimator dérive un nouveau circuit pour chaque observable (à partir du circuit original), afin de diagonaliser effectivement l'observable de Pauli dans la base computationnelle et de le mesurer. Nous pouvons facilement mesurer les observables de Pauli car nous connaissons à l'avance, ce qui n'est pas le cas en général pour d'autres observables.
Pour chaque , l'Estimator exécute le circuit correspondant sur un dispositif quantique plusieurs fois, mesure l'état de sortie dans la base computationnelle et calcule la probabilité d'obtenir chaque sortie possible . Il cherche ensuite la valeur propre de correspondant à chaque sortie , multiplie par et additionne tous les résultats pour obtenir la valeur d'espérance de l'observable pour l'état donné .
Étant donné que calculer la valeur d'espérance de Paulis est peu pratique (c'est-à-dire que cela croît exponentiellement), Estimator ne peut être efficace que lorsqu'une grande quantité de sont nuls (c'est-à-dire une décomposition de Pauli éparse plutôt que dense). Formellement, nous disons que, pour que ce calcul soit efficacement soluble, le nombre de termes non nuls doit croître au plus polynomialement avec le nombre de qubits :
Le lecteur peut noter l'hypothèse implicite que l'échantillonnage de probabilités doit également être efficace, comme expliqué pour Sampler, ce qui signifie
Exemple guidé pour calculer des valeurs d'espérance
Supposons l'état d'un qubit , et l'observable
avec la valeur d'espérance théorique suivante
Comme nous ne savons pas comment mesurer cet observable, nous ne pouvons pas calculer sa valeur d'espérance directement et nous devons le réexprimer comme . On peut montrer que cela donne le même résultat en notant que et .
Voyons comment calculer et directement. Comme et ne commutent pas (c'est-à-dire qu'ils ne partagent pas la même base propre), ils ne peuvent pas être mesurés simultanément, nous avons donc besoin des circuits auxiliaires :
# 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")
Nous pouvons maintenant effectuer le calcul manuellement en utilisant Sampler et vérifier les résultats avec Estimator :
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
Rigueur mathématique (optionnel)
En exprimant par rapport à la base des états propres de , , on déduit :
Comme nous ne connaissons pas les valeurs propres ni les états propres de l'observable cible , nous devons d'abord considérer sa diagonalisation. Étant donné que est hermitien, il existe une transformation unitaire tel que où est la matrice diagonale des valeurs propres, de sorte que si , et .
Cela implique que la valeur d'espérance peut être réécrite comme :