Skip to main content
IBM Quantum Platform

Simulation exacte avec des primitives d' Qiskit SDK

  • Le code de cette page a été développé en tenant compte des exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

    qiskit[all]~=2.5.1
    

Les primitives de référence de l' Qiskit SDK e effectuent des simulations de vecteurs d'état locaux. Ces simulations ne permettent pas de modéliser le bruit des appareils, mais elles sont utiles pour prototyper rapidement des algorithmes avant de se tourner vers des techniques de simulation plus avancées ( à l'aide de Qiskit Aer ) ou de les exécuter sur de vrais appareils ( primitives IBM Quantum ).

La primitive Estimateur peut calculer les valeurs d'espérance des circuits, et la primitive Échantillonneur peut échantillonner les distributions de sortie des circuits des distributions de sortie des circuits.

Les sections suivantes montrent comment utiliser les primitives de référence pour exécuter votre flux de travail localement.


Utilisez l'estimateur de référence

L'implémentation de référence de EstimatorV2 dans qiskit.primitives , qui fonctionne sur un simulateur local de vecteur d'état, est la suivante est la classe StatevectorEstimator classe. Il peut prendre des circuits, des observables et des paramètres en entrée et renvoie les valeurs d'espérance calculées localement.

Le code suivant prépare les données d'entrée qui seront utilisées dans les exemples suivants. Le type d'entrée attendu pour les observables est qiskit.quantum_info.SparsePauliOp. Notez que le circuit de l'exemple est paramétré, mais vous pouvez également utiliser Estimator sur des circuits non paramétrés.

Note

Tout circuit transmis à un estimateur ne doit inclure aucune mesure.

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:

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]]
Transpile vers les circuits ISA et les observables

Le workflow des primitives « IBM Quantum » nécessite que les circuits et les observables soient transformés de manière à n'utiliser que des instructions prises en charge par le QPU (on parle alors de circuits et d'observables « ISA » (architecture de jeu d'instructions) ). Les primitives de référence acceptent toujours les instructions abstraites, car elles s'appuient sur des simulations de vecteurs d'état locaux, mais la transcompilation du circuit pourrait tout de même s'avérer bénéfique en termes d'optimisation du circuit.

# 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'estimateur

Instancier un qiskit.primitives.StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Courir et obtenir des résultats

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

Exécutez l'estimation en appelant la méthode StatevectorEstimator.run qui renvoie une instance d'un objet PrimitiveJob objet. Vous pouvez obtenir les résultats du travail (sous la forme d'un qiskit.primitives.PrimitiveResult objet) avec la méthode qiskit.primitives.PrimitiveJob.result méthode.

job = estimator.run([(circuit, observable, parameter_values)])
result = job.result()
print(f" > Result class: {type(result)}")

Output:

 > Result class: <class 'qiskit.primitives.containers.primitive_result.PrimitiveResult'>

Obtenir la valeur attendue à partir du résultat

Le résultat des primitives génère un tableau PubResult d'objets, où chaque élément du tableau est un PubResult objet qui contient dans ses données le tableau des évaluations correspondant à chaque combinaison observable du circuit dans l' PUB e.

Pour récupérer les valeurs d'attente et les métadonnées de la première (et dans ce cas, de la seule) évaluation du circuit, nous devons accéder à l'évaluation data pour PUB 0 :

print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")

Output:

 > Expectation value: [4.         3.73205081 2.        ]
 > Metadata: {'target_precision': 0.0, 'circuit_metadata': {}}

Définir les options d'exécution de l'estimateur

Par défaut, l'estimateur de référence effectue un calcul exact du vecteur d'état sur la base de l'estimateur de référence quantum_info.Statevector classe. Toutefois, cette méthode peut être modifiée pour introduire l'effet de la surcharge d'échantillonnage (également connue sous le nom de "bruit de tir").

Estimator accepte un argument precision qui exprime les barres d'erreur que l'implémentation primitive doit cibler pour les estimations des valeurs d'espérance devrait cibler pour les estimations des valeurs d'espérance. Il s'agit de la charge d'échantillonnage, définie exclusivement dans la méthode .run() . Cela vous 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, consultez la page d'exemples d'Estimator.


Utilisez l'échantillonneur de référence

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

Le code suivant prépare les données d'entrée utilisées dans les exemples qui suivent. Notez que ces exemples exécutent un seul circuit paramétré, mais vous pouvez également utiliser Sampler avec 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:

Output of the previous code cell
Note

Tout circuit quantique transmis à un échantillonneur doit comporter des mesures.

Transpile vers les circuits ISA et les observables

Le workflow des primitives « IBM Quantum » nécessite que les circuits soient transformés de manière à n'utiliser que des instructions prises en charge par le QPU (appelés «circuits ISA»). Les primitives de référence acceptent toujours les instructions abstraites, car elles s'appuient sur des simulations de vecteurs d'état locaux, mais la transcompilation du circuit pourrait tout de même s'avérer bénéfique en termes d'optimisation du circuit.

# 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

Instancier qiskit.primitives.StatevectorSampler:

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()

Courir et obtenir des résultats

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

Output:

 > Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

Les primitives acceptent plusieurs PUB comme entrées, et chaque PUB obtient son propre résultat. Par conséquent, vous pouvez faire fonctionner différents circuits avec diverses combinaisons de paramètres/observables et récupérer les résultats sur le site PUB :

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)}")

Output:

 > Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>

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

Les échantillons de résultats de mesure sont renvoyés sous forme de chaînes de bits ou de décomptes. Les chaînes de bits indiquent les résultats des mesures, en conservant l'ordre des tirs dans lequel ils ont été mesurés. Les objets de résultat de l'échantillonneur organisent les données en termes de noms de registres classiques de leurs circuits d'entrée, pour des raisons de compatibilité avec les circuits dynamiques.

Le nom du registre classique est par défaut "meas". Ce nom sera utilisé ultérieurement pour accéder aux chaînes de bits des mesures.

# Define quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()

Output:

        ┌───┐      ░ ┌─┐   
   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}")

Output:

The number of bitstrings is: 1024
The counts are: {'00': 538, '11': 486}

Modifier les options d'exécution

Par défaut, l'échantillonneur de référence effectue un calcul exact du vecteur d'état en se basant sur la méthode quantum_info.Statevector classe. Toutefois, cette méthode peut être modifiée pour introduire l'effet de la surcharge d'échantillonnage (également connue sous le nom de "bruit de tir"). Pour aider à gérer cette surcharge, l'interface de l'échantillonneur accepte un argument shots qui peut être défini au niveau PUB.

Cet exemple suppose que vous avez 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)])

Output:

<qiskit.primitives.primitive_job.PrimitiveJob at 0x7f94d59db010>

Pour un exemple complet, consultez la page d'exemples de Sampler.


Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.