Migrar a las primitivas V2 de Qiskit Runtime
Las primitivas originales (denominadas primitivas V1), V1 Sampler y V1 Estimator, han quedado obsoletas en qiskit-ibm-runtime 0.23.
Su soporte fue eliminado el 15 de agosto de 2024.
Con la obsolescencia de las primitivas V1, todo el código debe migrarse para usar las interfaces V2. Esta guía describe qué cambió en las primitivas V2 de Qiskit Runtime (disponibles con qiskit-ibm-runtime 0.21.0) y por qué, describe cada nueva primitiva en detalle y ofrece ejemplos para ayudarte a migrar código que usa las primitivas heredadas a las primitivas V2. Los ejemplos de la guía usan las primitivas de Qiskit Runtime, pero en general, los mismos cambios aplican a las demás implementaciones de primitivas. Las funciones exclusivas de Qiskit Runtime, como la mitigación de errores, siguen siendo exclusivas de Qiskit Runtime.
Para obtener información sobre los cambios en las primitivas de referencia de Qiskit (ahora llamadas primitivas statevector), consulta la sección qiskit.primitives en la página de cambios de características de Qiskit 1.0. Consulta StatevectorSampler y StatevectorEstimator para ver implementaciones de referencia de las primitivas V2.
Descripción general
La versión 2 de las primitivas se introduce con una nueva clase base para Sampler y Estimator (BaseSamplerV2 y BaseEstimatorV2), junto con nuevos tipos para sus entradas y salidas.
La nueva interfaz te permite especificar un único circuito y múltiples observables (si usas Estimator) y conjuntos de valores de parámetros para ese circuito, de modo que los barridos sobre conjuntos de valores de parámetros y observables se puedan especificar de forma eficiente. Antes, tenías que especificar el mismo circuito varias veces para que coincidiera con el tamaño de los datos a combinar. Además, aunque puedes seguir usando resilience_level (si usas Estimator) como ajuste simple, las primitivas V2 te dan la flexibilidad de activar o desactivar métodos individuales de mitigación/supresión de errores para personalizarlos según tus necesidades.
Para reducir el tiempo total de ejecución del trabajo, las primitivas V2 solo aceptan circuitos y observables que usen instrucciones admitidas por el QPU (unidad de procesamiento cuántico) de destino. Estos circuitos y observables se denominan circuitos y observables de arquitectura de conjunto de instrucciones (ISA). Las primitivas V2 no realizan operaciones de diseño, enrutamiento ni traducción. Consulta la documentación de transpilación para obtener instrucciones sobre cómo transformar circuitos.
Sampler V2 se simplifica para centrarse en su tarea principal: muestrear el registro de salida de la ejecución de circuitos cuánticos. Devuelve las muestras, cuyo tipo está definido por el programa, sin ponderaciones. Los datos de salida también están separados por los nombres de registro de salida definidos por el programa. Este cambio permite el soporte futuro de circuitos con flujo de control clásico.
Consulta la referencia de la API de EstimatorV2 y la referencia de la API de SamplerV2 para obtener todos los detalles.
Cambios principales
Importación
Por compatibilidad con versiones anteriores, debes importar explícitamente las primitivas V2. Especificar import <primitive>V2 as <primitive> no es obligatorio, pero facilita la transición del código a V2.
Cuando las primitivas V1 dejen de tener soporte, import <primitive> importará la versión V2 de la primitiva indicada.
- 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
Entrada y salida
Entrada
Tanto SamplerV2 como EstimatorV2 aceptan uno o más bloques unificados de primitivas (PUBs) como entrada. Cada PUB es una tupla que contiene un circuito y los datos emitidos a ese circuito, que pueden ser múltiples observables y parámetros. Cada PUB devuelve un resultado.
- Formato de PUB de Sampler V2: (
<circuito>,<valores de parámetros>,<disparos>), donde<valores de parámetros>y<disparos>son opcionales. - Formato de PUB de Estimator V2: (
<circuito>,<observables>,<valores de parámetros>,<precisión>), donde<valores de parámetros>y<precisión>son opcionales. Se usan las reglas de difusión de Numpy al combinar observables y valores de parámetros.
Además, se han realizado los siguientes cambios:
- Estimator V2 ha incorporado un argumento
precisionen el métodorun()que especifica la precisión objetivo de las estimaciones del valor esperado. - Sampler V2 tiene el argumento
shotsen su métodorun().
Ejemplos
Ejemplo de Estimator V2 que usa precision en run():
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
Ejemplo de Sampler V2 que usa shots en 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),
])
Salida
La salida ahora está en el formato PubResult. Un PubResult es el conjunto de datos y metadatos resultante de la ejecución de un único PUB.
-
Estimator V2 continúa devolviendo valores esperados.
-
La porción
datade un PubResult de Estimator V2 contiene tanto los valores esperados como los errores estándar (stds). La V1 devolvía la varianza en los metadatos. -
Sampler V2 devuelve mediciones por disparo en forma de cadenas de bits (bitstrings), en lugar de las distribuciones de cuasi-probabilidad de la interfaz V1. Las cadenas de bits muestran los resultados de la medición, preservando el orden de los disparos en que se midieron.
-
Sampler V2 cuenta con métodos de conveniencia como
get_counts()para facilitar la migración. -
Los objetos de resultado de Sampler V2 organizan los datos según los nombres de registro clásico de los circuitos de entrada, para compatibilidad con circuitos dinámicos. Por defecto, el nombre del registro clásico es
meas, como se muestra en el siguiente ejemplo. Al definir tu circuito, si creas uno o más registros clásicos con un nombre no predeterminado, usa ese nombre para obtener los resultados. Puedes encontrar el nombre del registro clásico ejecutando<nombre_del_circuito>.cregs. Por ejemplo,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
Ejemplos de Estimator (entrada y salida)
- 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)
Ejemplos de Sampler (entrada y salida)
- 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)
El formato de salida de V1 era un diccionario de cadenas de bits (como entero) como clave y cuasi-probabilidades como valor para cada circuito. El formato V2 usa la misma clave (pero como cadena de texto) y los conteos como valor. Para convertir el formato V2 al V1, divide los conteos por el número de disparos, donde el número de disparos seleccionado se describe en la guía Especificar opciones.
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()} )
Ejemplo que usa diferentes registros de salida
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()}")
Opciones
Las opciones se especifican de forma diferente en las primitivas V2:
SamplerV2yEstimatorV2ahora tienen clases de opciones separadas. Puedes ver las opciones disponibles y actualizar sus valores durante o después de la inicialización de la primitiva.- En lugar del método
set_options(), las opciones de las primitivas V2 disponen del métodoupdate(), que aplica los cambios al atributooptions. - Si no especificas un valor para una opción, se le asigna un valor especial
Unsety se usan los valores predeterminados del servidor. - En las primitivas V2, el atributo
optionses del tipo Pythondataclass. Puedes usar el método integradoasdictpara convertirlo en un diccionario.
Consulta la referencia de la API para ver la lista de opciones 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)
Mitigación y supresión de errores
-
Como Sampler V2 devuelve muestras sin postprocesamiento, no admite niveles de resiliencia.
-
Sampler V2 no admite
optimization_level. -
Estimator V2 eliminará el soporte para
optimization_levela partir del 30 de septiembre de 2024 aproximadamente. -
Estimator V2 no admite el nivel de resiliencia 3. Esto se debe a que el nivel 3 en V1 Estimator utiliza la Cancelación de Errores Probabilística (PEC, por sus siglas en inglés), que tiene demostrado dar resultados imparciales a costa de un tiempo de procesamiento exponencial. El nivel 3 fue eliminado para destacar ese compromiso. Sin embargo, todavía puedes usar PEC como método de mitigación de errores especificando la opción
pec_mitigation. -
Estimator V2 admite
resilience_leveldel 0 al 2, como se describe en la siguiente tabla. Estas opciones son más avanzadas que sus equivalentes en V1. También puedes activar o desactivar explícitamente métodos individuales de mitigación o supresión de errores.Nivel 1 Nivel 2 Twirling de medición Twirling de medición Mitigación de errores de lectura Mitigación de errores de lectura 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
Transpilación
Las primitivas V2 solo admiten circuitos que se ajusten a la Arquitectura de Conjunto de Instrucciones (ISA) de un backend en particular. Dado que las primitivas no realizan operaciones de asignación de qubits, enrutamiento ni traducción, las opciones de transpilación correspondientes de V1 no son compatibles.
Estado del trabajo
Las primitivas V2 cuentan con una nueva clase RuntimeJobV2, que hereda de BasePrimitiveJob. El método status() de esta nueva clase devuelve una cadena de texto en lugar de un enum JobStatus de Qiskit. Consulta la referencia de la API de RuntimeJobV2 para más detalles.
- 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}")
Pasos para migrar a Estimator V2
-
Reemplaza
from qiskit_ibm_runtime import Estimatorporfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Elimina cualquier instrucción
from qiskit_ibm_runtime import Options, ya que la claseOptionsno es utilizada por las primitivas V2. En su lugar, puedes pasar las opciones como un diccionario al inicializar la claseEstimatorV2(por ejemplo,estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), o configurarlas después de la inicialización:estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
Revisa todas las opciones admitidas y aplica los cambios necesarios.
-
Agrupa cada circuito que quieras ejecutar junto con los observables y valores de parámetros que deseas aplicar al circuito en una tupla (un PUB). Por ejemplo, usa
(circuit1, observable1, parameter_set1)si quieres ejecutarcircuit1conobservable1yparameter_set1. -
Es posible que necesites reorganizar tus arrays de observables o conjuntos de parámetros si deseas aplicar su producto exterior. Por ejemplo, un array de observables con forma (4, 1) y un array de conjuntos de parámetros con forma (1, 6) te dará un resultado de (4, 6) valores esperados. Consulta las reglas de broadcasting de NumPy para más detalles.
-
Opcionalmente, puedes especificar la precisión que deseas para ese PUB específico.
-
Actualiza el método
run()del estimator para pasar la lista de PUBs. Por ejemplo,run([(circuit1, observable1, parameter_set1)]). Opcionalmente, puedes especificar unaprecisionaquí, que se aplicaría a todos los PUBs. -
Los resultados de los trabajos de Estimator V2 están agrupados por PUBs. Puedes ver el valor esperado y el error estándar de cada PUB indexándolo. Por ejemplo:
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Ejemplos completos de Estimator
Ejecutar un único experimento
Usa Estimator para determinar el valor esperado de un único par circuito-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}")
Ejecutar múltiples experimentos en un único trabajo
Usa Estimator para determinar los valores esperados de múltiples pares circuito-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}")
Ejecutar circuitos parametrizados
Usa Estimator para ejecutar múltiples experimentos en un único trabajo, aprovechando los valores de parámetros para aumentar la reutilizaci ón de circuitos. En el siguiente ejemplo, observa que los pasos 1 y 2 son iguales para V1 y 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
Usar sesiones y opciones avanzadas
Explora sesiones y opciones avanzadas para optimizar el rendimiento de los circuitos en QPUs.
El siguiente bloque de código devolverá un error para los usuarios del Plan Abierto porque utiliza sesiones. Las cargas de trabajo del Plan Abierto solo pueden ejecutarse en modo job o modo 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}")
Pasos para migrar al Sampler V2
- Reemplaza
from qiskit_ibm_runtime import Samplerporfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Elimina cualquier instrucción
from qiskit_ibm_runtime import Options, ya que la claseOptionsno es utilizada por los primitivos V2. En su lugar, puedes pasar opciones como un diccionario al inicializar la claseSamplerV2(por ejemplo,sampler = Sampler(backend, options={"default_shots": 1024})), o configurarlas después de la inicialización:sampler = Sampler(backend)
sampler.options.default_shots = 1024 - Revisa todas las opciones compatibles y realiza las actualizaciones necesarias.
- Agrupa cada circuito que quieras ejecutar junto con los observables y los valores de parámetros que deseas aplicar al circuito en una tupla (un PUB). Por ejemplo, usa
(circuit1, parameter_set1)si quieres ejecutarcircuit1conparameter_set1. Opcionalmente puedes especificar los shots que deseas para ese PUB en particular. - Actualiza el método
run()del sampler para pasar la lista de PUBs. Por ejemplo,run([(circuit1, parameter_set1)]). Opcionalmente puedes especificarshotsaquí, lo cual se aplicaría a todos los PUBs. - Los resultados del job del Sampler V2 están agrupados por PUBs. Puedes ver los datos de salida de cada PUB indexándolo. Aunque el Sampler V2 devuelve muestras sin ponderar, la clase de resultado tiene un método de conveniencia para obtener conteos en su lugar. Por ejemplo:
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()}")
Necesitas el nombre del registro clásico para obtener los resultados. Por defecto, se llama meas cuando usas measure_all(). Al definir tu circuito, si creas uno o más registros clásicos con un nombre distinto al predeterminado, usa ese nombre para obtener los resultados. Puedes encontrar el nombre del registro clásico ejecutando <circuit_name>.cregs. Por ejemplo, qc.cregs.
Ejemplos completos de Sampler
Ejecutar un único experimento
Usa Sampler para determinar los conteos o la distribución de cuasi-probabilidad de un único circuito.
- 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}")
Ejecutar múltiples experimentos en un único job
Usa Sampler para determinar los conteos o las distribuciones de cuasi-probabilidad de múltiples circuitos en un solo job.
- 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}")
Ejecutar circuitos parametrizados
Ejecuta varios experimentos en un único job, aprovechando los valores de parámetros para aumentar la reutilización del circuito.
- 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}")
Usar sesiones y opciones avanzadas
Explora sesiones y opciones avanzadas para optimizar el rendimiento de los circuitos en QPUs.
El siguiente bloque de código devolverá un error para los usuarios del Plan Abierto porque utiliza sesiones. Las cargas de trabajo del Plan Abierto solo pueden ejecutarse en modo job o modo 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}")
Próximos pasos
- Aprende más sobre cómo configurar opciones en la guía Especificar opciones.
- Obtén más detalles sobre Entradas y salidas de los primitivos.
- Experimenta con el tutorial de la Desigualdad CHSH.