Aller au contenu principal

Simulation exacte avec les primitives du SDK Qiskit

Versions des packages

Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

qiskit[all]~=2.3.0

Les primitives de référence du SDK Qiskit effectuent des simulations locales par vecteur d'état. Ces simulations ne prennent pas en charge la modélisation du bruit des appareils, mais elles sont utiles pour prototyper rapidement des algorithmes avant de se pencher sur des techniques de simulation plus avancées (avec Qiskit Aer) ou d'exécuter sur de vrais appareils (les primitives Qiskit Runtime).

La primitive Estimator peut calculer des valeurs d'espérance de circuits, et la primitive Sampler peut échantillonner à partir des distributions de sortie de circuits.

Les sections suivantes montrent comment utiliser les primitives de référence pour exécuter ton workflow localement.

Utiliser l'Estimator de référence

L'implémentation de référence de EstimatorV2 dans qiskit.primitives qui s'exécute sur des simulateurs locaux par vecteur d'état est la classe StatevectorEstimator. Elle accepte des circuits, des observables et des paramètres en entrée, et retourne les valeurs d'espérance calculées localement.

Le code suivant prépare les entrées qui seront utilisées dans les exemples ci-dessous. Le type d'entrée attendu pour les observables est qiskit.quantum_info.SparsePauliOp. Note que le circuit dans l'exemple est paramétré, mais tu peux aussi exécuter l'Estimator sur des circuits non paramétrés.

remarque

Tout circuit passé à un Estimator ne doit pas inclure de mesures.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# circuit for which you want to obtain the expected value
circuit = QuantumCircuit(2)
circuit.ry(Parameter("theta"), 0)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw("mpl", style="iqp")

Output of the previous code cell

from qiskit.quantum_info import SparsePauliOp
import numpy as np

# observable(s) whose expected values you want to compute

observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])

# value(s) for the circuit parameter(s)
parameter_values = [[0], [np.pi / 6], [np.pi / 2]]
Transpiler vers des circuits et observables ISA

Le workflow des primitives Qiskit Runtime requiert que les circuits et les observables soient transformés pour n'utiliser que les instructions prises en charge par le QPU (ce que l'on appelle des circuits et observables de jeu d'instructions (ISA)). Les primitives de référence acceptent toujours des instructions abstraites, car elles s'appuient sur des simulations locales par vecteur d'état, mais transpiler le circuit peut tout de même être bénéfique en termes d'optimisation.

# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)

Initialiser l'Estimator

Instancie un qiskit.primitives.StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Exécuter et obtenir les résultats

Cet exemple n'utilise qu'un seul circuit (de type QuantumCircuit) et un seul observable.

Lance l'estimation en appelant la méthode StatevectorEstimator.run, qui retourne une instance d'un objet PrimitiveJob. Tu peux obtenir les résultats du job (sous forme d'objet qiskit.primitives.PrimitiveResult) avec la méthode qiskit.primitives.PrimitiveJob.result.

job = estimator.run([(circuit, observable, parameter_values)])
result = job.result()
print(f" > Result class: {type(result)}")
> Result class: <class 'qiskit.primitives.containers.primitive_result.PrimitiveResult'>

Obtenir la valeur d'espérance depuis le résultat

Les résultats des primitives retournent un tableau d'objets PubResult, où chaque élément du tableau est un objet PubResult qui contient dans ses données le tableau des évaluations correspondant à chaque combinaison circuit-observable dans le PUB.

Pour récupérer les valeurs d'espérance et les métadonnées pour la première (et dans ce cas, seule) évaluation de circuit, on doit accéder aux data d'évaluation du PUB 0 :

print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
> Expectation value: [4.         3.73205081 2.        ]
> Metadata: {'target_precision': 0.0, 'circuit_metadata': {}}

Configurer les options d'exécution de l'Estimator

Par défaut, l'Estimator de référence effectue un calcul exact par vecteur d'état en s'appuyant sur la classe quantum_info.Statevector. Il est toutefois possible de le modifier pour introduire l'effet du surcoût d'échantillonnage (aussi appelé « bruit de grenaille »).

L'Estimator accepte un argument precision qui exprime les barres d'erreur que l'implémentation primitive doit cibler pour les estimations de valeurs d'espérance. Il s'agit du surcoût d'échantillonnage, défini exclusivement dans la méthode .run(). Cela te permet d'affiner l'option jusqu'au niveau PUB.

# Estimate expectation values for two PUBs, both with 0.05 precision.
precise_job = estimator.run(
[(circuit, observable, parameter_values)], precision=0.05
)

Pour un exemple complet, consulte la page Exemples de primitives.

Utiliser le Sampler de référence

L'implémentation de référence de SamplerV2 dans qiskit.primitives est la classe StatevectorSampler. Elle accepte des circuits et des paramètres en entrée, et retourne les résultats de l'échantillonnage des distributions de probabilité de sortie sous forme de distribution quasi-probabiliste des états de sortie.

Le code suivant prépare les entrées utilisées dans les exemples ci-dessous. Note que ces exemples exécutent un seul circuit paramétré, mais tu peux aussi exécuter le Sampler sur des circuits non paramétrés.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw("mpl", style="iqp")

Output of the previous code cell

remarque

Tout circuit quantique passé à un Sampler doit inclure des mesures.

Transpiler vers des circuits et observables ISA

Le workflow des primitives Qiskit Runtime requiert que les circuits soient transformés pour n'utiliser que les instructions prises en charge par le QPU (ce que l'on appelle des circuits ISA). Les primitives de référence acceptent toujours des instructions abstraites, car elles s'appuient sur des simulations locales par vecteur d'état, mais transpiler le circuit peut tout de même être bénéfique en termes d'optimisation.

# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(qc)

Initialiser SamplerV2

Instancie qiskit.primitives.StatevectorSampler :

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()

Exécuter et obtenir les résultats

# execute 1 circuit with Sampler
job = sampler.run([circuit])
pub_result = job.result()[0]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

Les primitives acceptent plusieurs PUBs en entrée, et chaque PUB obtient son propre résultat. Tu peux donc exécuter différents circuits avec diverses combinaisons de paramètres et d'observables, et récupérer les résultats des PUBs :

from qiskit.transpiler import generate_preset_pass_manager

# create two circuits
circuit1 = circuit.copy()
circuit2 = circuit.copy()

# transpile circuits
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit1 = pm.run(circuit1)
isa_circuit2 = pm.run(circuit2)
# execute 2 circuits using Sampler
job = sampler.run([(isa_circuit1), (isa_circuit2)])
pub_result_1 = job.result()[0]
pub_result_2 = job.result()[1]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

Obtenir la distribution de probabilité ou le résultat de mesure

Les échantillons de résultats de mesure sont retournés sous forme de chaînes de bits ou de comptages. Les chaînes de bits montrent les résultats de mesure en préservant l'ordre des shots dans lequel ils ont été mesurés. Les objets de résultat du Sampler organisent les données en fonction des noms de registre classique des circuits d'entrée, pour assurer la compatibilité avec les circuits dynamiques.

remarque

Le nom du registre classique est par défaut "meas". Ce nom sera utilisé plus loin pour accéder aux chaînes de bits de mesure.

# Define quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
┌───┐      ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
# Transpile circuit
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
# Run using sampler
result = sampler.run([circuit]).result()
# Access result data for PUB 0
data_pub = result[0].data
# Access bitstring for the classical register "meas"
bitstrings = data_pub.meas.get_bitstrings()
print(f"The number of bitstrings is: {len(bitstrings)}")
# Get counts for the classical register "meas"
counts = data_pub.meas.get_counts()
print(f"The counts are: {counts}")
The number of bitstrings is: 1024
The counts are: {'11': 515, '00': 509}

Modifier les options d'exécution

Par défaut, le Sampler de référence effectue un calcul exact par vecteur d'état en s'appuyant sur la classe quantum_info.Statevector. Il est toutefois possible de le modifier pour introduire l'effet du surcoût d'échantillonnage (aussi appelé « bruit de grenaille »). Pour faciliter la gestion de ce surcoût, l'interface du Sampler accepte un argument shots qui peut être défini au niveau PUB.

Cet exemple suppose que tu as défini deux circuits.

# Sample two circuits at 128 shots each.
sampler.run([isa_circuit1, isa_circuit2], shots=128)
# Sample two circuits at different amounts of shots. The "None"s are necessary
# as placeholders
# for the lack of parameter values in this example.
sampler.run([(isa_circuit1, None, 123), (isa_circuit2, None, 456)])
<qiskit.primitives.primitive_job.PrimitiveJob at 0x7fa430e39dd0>

Pour un exemple complet, consulte la page Exemples de primitives.

Prochaines étapes

Recommandations