Migrer vers les primitives Qiskit Runtime V2
Les primitives originales (appelées primitives V1), V1 Sampler et V1 Estimator, ont été dépréciées dans qiskit-ibm-runtime 0.23.
Le support pour ces versions a été retiré le 15 août 2024.
Avec la dépréciation des primitives V1, tout code doit être migré pour utiliser les interfaces V2. Ce guide décrit ce qui a changé dans les primitives Qiskit Runtime V2 (disponibles avec qiskit-ibm-runtime 0.21.0) et pourquoi, décrit chaque nouvelle primitive en détail, et te donne des exemples pour t'aider à migrer ton code de l'utilisation des primitives héritées vers les primitives V2. Les exemples du guide utilisent tous les primitives Qiskit Runtime, mais en général, les mêmes changements s'appliquent aux autres implémentations de primitives. Les fonctions uniques à Qiskit Runtime telles que l'atténuation d'erreurs restent uniques à Qiskit Runtime.
Pour obtenir des informations sur les changements apportés aux primitives de référence Qiskit (maintenant appelées primitives statevector), consulte la section qiskit.primitives sur la page des changements de fonctionnalités de Qiskit 1.0. Consulte StatevectorSampler et StatevectorEstimator pour les implémentations de référence des primitives V2.
Aperçu
La version 2 des primitives est introduite avec une nouvelle classe de base pour Sampler et Estimator (BaseSamplerV2 et BaseEstimatorV2), ainsi que de nouveaux types pour leurs entrées et sorties.
La nouvelle interface te permet de spécifier un circuit unique et plusieurs observables (si tu utilises Estimator) et des ensembles de valeurs de paramètres pour ce circuit, de sorte que les balayages sur les ensembles de valeurs de paramètres et les observables puissent être efficacement spécifiés. Auparavant, tu devais spécifier le même circuit plusieurs fois pour correspondre à la taille des données à combiner. De plus, bien que tu puisses toujours utiliser resilience_level (si tu utilises Estimator) comme réglage simple, les primitives V2 te donnent la flexibilité d'activer ou désactiver les méthodes individuelles d'atténuation/suppression d'erreurs pour les personnaliser selon tes besoins.
Pour réduire le temps total d'exécution des tâches, les primitives V2 n'acceptent que les circuits et observables qui utilisent les instructions supportées par l'unité de traitement quantique (QPU) cible. Ces circuits et observables sont appelés circuits et observables d'architecture d'ensemble d'instructions (ISA). Les primitives V2 n'effectuent pas d'opérations de layout, routing et translation. Consulte la documentation de transpilation pour obtenir des instructions sur la façon de transformer les circuits.
Sampler V2 est simplifié pour se concentrer sur sa tâche principale : échantillonner le registre de sortie de l'exécution de circuits quantiques. Il retourne les échantillons, dont le type est défini par le programme, sans poids. Les données de sortie sont également séparées par les noms de registre de sortie définis par le programme. Ce changement permet la prise en charge future des circuits avec contrôle de flux classique.
Consulte la référence API EstimatorV2 et la référence API SamplerV2 pour plus de détails.
Changements majeurs
Import
Pour la compatibilité rétroactive, tu dois importer explicitement les primitives V2. Spécifier import <primitive>V2 as <primitive> n'est pas obligatoire, mais rend plus facile la transition du code vers V2.
Une fois que les primitives V1 ne seront plus supportées, import <primitive> importera la version V2 de la primitive spécifiée.
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import Sampler
Entrée et sortie
Entrée
À la fois SamplerV2 et EstimatorV2 acceptent un ou plusieurs blocs unifiés de primitives (PUBs) en entrée. Chaque PUB est un tuple qui contient un circuit et les données diffusées à ce circuit, qui peuvent être plusieurs observables et paramètres. Chaque PUB retourne un résultat.
- Format de PUB Sampler V2 : (
<circuit>,<valeurs de paramètres>,<shots>), où<valeurs de paramètres>et<shots>sont optionnels. - Format de PUB Estimator V2 : (
<circuit>,<observables>,<valeurs de paramètres>,<précision>), où<valeurs de paramètres>et<précision>sont optionnels. Les règles de broadcasting de NumPy sont utilisées lors de la combinaison des observables et des valeurs de paramètres.
De plus, les changements suivants ont été apportés :
- Estimator V2 a acquis un argument
precisiondans la méthoderun()qui spécifie la précision ciblée des estimations de valeur d'expectation. - Sampler V2 possède l'argument
shotsdans sa méthoderun().
Exemples
Exemple d'Estimator V2 qui utilise precision dans run() :
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
Exemple de Sampler V2 qui utilise shots dans run() :
# Sample two circuits at 128 shots each.
sampler.run([circuit1, 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([
(circuit1, None, 123),
(circuit2, None, 456),
])
Sortie
La sortie est maintenant au format PubResult. Un PubResult est l'ensemble des données et métadonnées résultant de l'exécution d'un seul PUB.
-
Estimator V2 continue de retourner les valeurs d'expectation.
-
La portion
datad'un PubResult d'Estimator V2 contient à la fois les valeurs d'expectation et les erreurs standards (stds). V1 retournait la variance dans les métadonnées. -
Sampler V2 retourne les mesures par shot sous la forme de chaînes de bits (bitstrings), au lieu des distributions de quasi-probabilité de l'interface V1. Les chaînes de bits montrent les résultats de la mesure, en préservant l'ordre des shots dans lesquels ils ont été mesurés.
-
Sampler V2 dispose de méthodes de commodité comme
get_counts()pour faciliter la migration. -
Les objets de résultat de Sampler V2 organisent les données en fonction des noms de registre classique des circuits d'entrée, pour la compatibilité avec les circuits dynamiques. Par défaut, le nom du registre classique est
meas, comme le montre l'exemple suivant. Lorsque tu définis ton circuit, si tu crées un ou plusieurs registres classiques avec un nom non-par défaut, utilise ce nom pour obtenir les résultats. Tu peux trouver le nom du registre classique en exécutant<circuit_name>.cregs. Par exemple,qc.cregs.# Define a 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
Exemples Estimator (entrée et sortie)
- 1 circuit, 4 observables
- 1 circuit, 4 observables, 2 parameter sets
- 2 circuits, 2 observables
# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
Exemples Sampler (entrée et sortie)
- 1 circuit, 3 parameter sets
- 2 circuits, 1 parameter set
- Convert V2 output to V1 format
# Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists
# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
Le format de sortie V1 était un dictionnaire de chaînes de bits (comme entier) comme clé et des quasi-probabilités comme valeur pour chaque circuit. Le format V2 utilise la même clé (mais comme chaîne) et les comptages comme valeur. Pour convertir le format V2 au V1, divise les comptages par le nombre de shots, où le nombre de shots sélectionné est décrit dans le guide Spécifier les options.
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
Exemple qui utilise différents registres de sortie
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)
circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")
Options
Les options sont spécifiées différemment dans les primitives V2 de ces manières :
SamplerV2etEstimatorV2ont maintenant des classes d'options distinctes. Tu peux voir les options disponibles et mettre à jour les valeurs des options pendant ou après l'initialisation de la primitive.- Au lieu de la méthode
set_options(), les options des primitives V2 ont la méthodeupdate()qui applique les changements à l'attributoptions. - Si tu ne spécifies pas une valeur pour une option, elle reçoit une valeur spéciale
Unsetet les valeurs par défaut du serveur sont utilisées. - Pour les primitives V2, l'attribut
optionsest du type Pythondataclass. Tu peux utiliser la méthode intégréeasdictpour la convertir en dictionnaire.
Consulte la référence API pour la liste des options disponibles.
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})
# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})
# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True
# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)
Atténuation et suppression d'erreurs
-
Comme Sampler V2 retourne des échantillons sans post-traitement, il ne supporte pas les niveaux de résilience.
-
Sampler V2 ne supporte pas
optimization_level. -
Estimator V2 supprimera le support pour
optimization_levelautour du 30 septembre 2024. -
Estimator V2 ne supporte pas le niveau de résilience 3. En effet, le niveau 3 dans Estimator V1 utilise la Probabilistic Error Cancellation (PEC), qui s'est avérée donner des résultats sans biais au prix d'un temps de traitement exponentiel. Le niveau 3 a été supprimé pour attirer l'attention sur ce compromis. Tu peux cependant toujours utiliser PEC comme méthode d'atténuation d'erreurs en spécifiant l'option
pec_mitigation. -
Estimator V2 supporte
resilience_level0-2, comme décrit dans le tableau suivant. Ces options sont plus avancées que leurs homologues V1. Tu peux également activer/désactiver explicitement les méthodes individuelles d'atténuation ou suppression d'erreurs.Niveau 1 Niveau 2 Measurement twirling Measurement twirling Readout error mitigation Readout error mitigation ZNE
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend)
# Set resilience_level to 0
estimator.options.resilience_level = 0
# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
Transpilation
Les primitives V2 ne supportent que les circuits qui adhèrent à l'architecture d'ensemble d'instructions (ISA) d'un backend particulier. Comme les primitives n'effectuent pas d'opérations de layout, routing et translation, les options de transpilation correspondantes de V1 ne sont pas supportées.
État du travail
Les primitives V2 ont une nouvelle classe RuntimeJobV2, qui hérite de BasePrimitiveJob. La méthode status() de cette nouvelle classe retourne une chaîne au lieu d'une énumération JobStatus de Qiskit. Consulte la référence API RuntimeJobV2 pour plus de détails.
- V2 primitives
- V1 primitives
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
Étapes pour migrer vers Estimator V2
-
Remplace
from qiskit_ibm_runtime import Estimatorparfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Supprime toute déclaration
from qiskit_ibm_runtime import Options, puisque la classeOptionsn'est pas utilisée par les primitives V2. Tu peux plutôt passer les options comme dictionnaire lors de l'initialisation de la classeEstimatorV2(par exempleestimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), ou les configurer après l'initialisation :estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
Consulte toutes les options supportées et apporte les mises à jour nécessaires.
-
Groupe chaque circuit que tu veux exécuter avec les observables et valeurs de paramètres que tu veux appliquer au circuit dans un tuple (un PUB). Par exemple, utilise
(circuit1, observable1, parameter_set1)si tu veux exécutercircuit1avecobservable1etparameter_set1. -
Tu pourrais avoir besoin de remodeler tes arrays d'observables ou d'ensembles de paramètres si tu veux appliquer leur produit externe. Par exemple, un array d'observables de shape (4, 1) et un array d'ensembles de paramètres de shape (1, 6) te donnera un résultat de (4, 6) valeurs d'expectation. Consulte les règles de broadcasting NumPy pour plus de détails.
-
Tu peux optionnellement spécifier la précision que tu veux pour ce PUB spécifique.
-
Mets à jour la méthode
run()de l'estimateur pour passer la liste des PUBs. Par exemple,run([(circuit1, observable1, parameter_set1)]). Tu peux optionnellement spécifier uneprecisionici, qui s'appliquerait à tous les PUBs. -
Les résultats des travaux d'Estimator V2 sont groupés par PUBs. Tu peux voir la valeur d'expectation et l'erreur standard de chaque PUB en y indexant. Par exemple :
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Exemples complets d'Estimator
Exécuter une seule expérience
Utilise Estimator pour déterminer la valeur d'expectation d'une seule paire circuit-observable.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
Exécuter plusieurs expériences dans un seul travail
Utilise Estimator pour déterminer les valeurs d'expectation de plusieurs paires circuit-observable.
- Estimator V2
- Estimator (V1)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run(isa_circuits, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
Exécuter des circuits paramétrés
Utilise Estimator pour exécuter plusieurs expériences dans un seul travail, en tirant parti des valeurs de paramètres pour augmenter la réutilisabilité du circuit. Dans l'exemple suivant, remarque que les étapes 1 et 2 sont les mêmes pour V1 et V2.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import EstimatorV2 as Estimator
# Step 3: Execute using Qiskit primitives.
# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))
estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
Utiliser des sessions et des options avancées
Explore les sessions et les options avancées pour optimiser la performance des circuits sur les QPUs.
Le bloc de code suivant retournera une erreur pour les utilisateurs du plan ouvert car il utilise des sessions. Les charges de travail du plan ouvert ne peuvent s'exécuter que dans mode job ou mode batch.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator()
estimator.options.resilience_level = 1
job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
job = estimator.run(isa_circuit, isa_observable)
another_job = estimator.run(another_isa_circuit, another_isa_observable)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
Étapes pour migrer vers Sampler V2
- Remplace
from qiskit_ibm_runtime import Samplerparfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Supprime toute déclaration
from qiskit_ibm_runtime import Options, puisque la classeOptionsn'est pas utilisée par les primitives V2. Tu peux plutôt passer les options comme dictionnaire lors de l'initialisation de la classeSamplerV2(par exemplesampler = Sampler(backend, options={"default_shots": 1024})), ou les configurer après l'initialisation :sampler = Sampler(backend)
sampler.options.default_shots = 1024 - Consulte toutes les options supportées et apporte les mises à jour nécessaires.
- Groupe chaque circuit que tu veux exécuter avec les valeurs de paramètres que tu veux appliquer au circuit dans un tuple (un PUB). Par exemple, utilise
(circuit1, parameter_set1)si tu veux exécutercircuit1avecparameter_set1. Tu peux optionnellement spécifier les shots que tu veux pour ce PUB spécifique. - Mets à jour la méthode
run()du sampler pour passer la liste des PUBs. Par exemple,run([(circuit1, parameter_set1)]). Tu peux optionnellement spécifiershotsici, qui s'appliquerait à tous les PUBs. - Les résultats des travaux de Sampler V2 sont groupés par PUBs. Tu peux voir les données de sortie de chaque PUB en y indexant. Bien que Sampler V2 retourne des échantillons non pondérés, la classe de résultat a une méthode de commodité pour obtenir les comptages à la place. Par exemple :
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
Tu as besoin du nom du registre classique pour obtenir les résultats. Par défaut, il s'appelle meas lorsque tu utilises measure_all(). Lorsque tu définis ton circuit, si tu crées un ou plusieurs registres classiques avec un nom différent du défaut, utilise ce nom pour obtenir les résultats. Tu peux trouver le nom du registre classique en exécutant <circuit_name>.cregs. Par exemple, qc.cregs.
Exemples complets de Sampler
Exécuter une seule expérience
Utilise Sampler pour déterminer les comptages ou la distribution de quasi-probabilité d'un seul circuit.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Exécuter plusieurs expériences dans un seul travail
Utilise Sampler pour déterminer les comptages ou les distributions de quasi-probabilité de plusieurs circuits dans un seul travail.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()
for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
Exécuter des circuits paramétrés
Exécute plusieurs expériences dans un seul travail, en tirant parti des valeurs de paramètres pour augmenter la réutilisabilité du circuit.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 5
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Utiliser des sessions et des options avancées
Explore les sessions et les options avancées pour optimiser la performance des circuits sur les QPUs.
Le bloc de code suivant retournera une erreur pour les utilisateurs du plan ouvert car il utilise des sessions. Les charges de travail du plan ouvert ne peuvent s'exécuter que dans mode job ou mode batch.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
service = QiskitRuntimeService()
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")
# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
Prochaines étapes
- Apprends-en plus sur la configuration des options dans le guide Spécifier les options.
- Obtiens plus de détails sur Entrées et sorties des primitives.
- Expérimente avec le tutoriel Inégalité CHSH.