Saltar al contenido principal

Migrar a las primitivas V2 de Qiskit Runtime

aviso

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.

aviso

Cuando las primitivas V1 dejen de tener soporte, import <primitive> importará la versión V2 de la primitiva indicada.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as 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 precision en el método run() que especifica la precisión objetivo de las estimaciones del valor esperado.
  • Sampler V2 tiene el argumento shots en su método run().
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 data de 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)

# 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

Ejemplos de Sampler (entrada y salida)

  # 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()

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:

  • SamplerV2 y EstimatorV2 ahora 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étodo update(), que aplica los cambios al atributo options.
  • Si no especificas un valor para una opción, se le asigna un valor especial Unset y se usan los valores predeterminados del servidor.
  • En las primitivas V2, el atributo options es del tipo Python dataclass. Puedes usar el método integrado asdict para convertirlo en un diccionario.

Consulta la referencia de la API para ver la lista de opciones disponibles.

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

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_level a 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_level del 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 1Nivel 2
    Twirling de mediciónTwirling de medición
    Mitigación de errores de lecturaMitigación de errores de lectura
    ZNE
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 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}")

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.

job = estimator.run(...)

# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")

Pasos para migrar a Estimator V2

  1. Reemplaza from qiskit_ibm_runtime import Estimator por from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. Elimina cualquier instrucción from qiskit_ibm_runtime import Options, ya que la clase Options no es utilizada por las primitivas V2. En su lugar, puedes pasar las opciones como un diccionario al inicializar la clase EstimatorV2 (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
  3. Revisa todas las opciones admitidas y aplica los cambios necesarios.

  4. 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 ejecutar circuit1 con observable1 y parameter_set1.

  5. 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.

  6. Opcionalmente, puedes especificar la precisión que deseas para ese PUB específico.

  7. Actualiza el método run() del estimator para pasar la lista de PUBs. Por ejemplo, run([(circuit1, observable1, parameter_set1)]). Opcionalmente, puedes especificar una precision aquí, que se aplicaría a todos los PUBs.

  8. 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.

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

Ejecutar múltiples experimentos en un único trabajo

Usa Estimator para determinar los valores esperados de múltiples pares circuito-observable.

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

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.

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

Usar sesiones y opciones avanzadas

Explora sesiones y opciones avanzadas para optimizar el rendimiento de los circuitos en QPUs.

precaución

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.

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

Pasos para migrar al Sampler V2

  1. Reemplaza from qiskit_ibm_runtime import Sampler por from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. Elimina cualquier instrucción from qiskit_ibm_runtime import Options, ya que la clase Options no es utilizada por los primitivos V2. En su lugar, puedes pasar opciones como un diccionario al inicializar la clase SamplerV2 (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
  3. Revisa todas las opciones compatibles y realiza las actualizaciones necesarias.
  4. 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 ejecutar circuit1 con parameter_set1. Opcionalmente puedes especificar los shots que deseas para ese PUB en particular.
  5. Actualiza el método run() del sampler para pasar la lista de PUBs. Por ejemplo, run([(circuit1, parameter_set1)]). Opcionalmente puedes especificar shots aquí, lo cual se aplicaría a todos los PUBs.
  6. 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()}")
nota

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.

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()

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.

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

Ejecutar circuitos parametrizados

Ejecuta varios experimentos en un único job, aprovechando los valores de parámetros para aumentar la reutilización del circuito.

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

Usar sesiones y opciones avanzadas

Explora sesiones y opciones avanzadas para optimizar el rendimiento de los circuitos en QPUs.

precaución

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.

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

Próximos pasos

Recomendaciones