Migrar a las primitivas Qiskit Runtime V2
Las primitivas originales (conocidas como las primitivas « V1 »), V1 Sampler y V1 Estimator, han quedado obsoletas en qiskit-ibm-runtime0.23.
Su apoyo se retiró el 15 de agosto de 2024.
Con la desaparición de las primitivas V1, todo el código debe migrar para utilizar las interfaces V2. Esta guía describe qué ha cambiado en las primitivas Qiskit Runtime V2 (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 desde el uso de las primitivas heredadas a las primitivas V2. Todos los ejemplos de la guía utilizan las primitivas Qiskit Runtime, pero en general, los mismos cambios se 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 de vector de estado ), consulte la sección qiskit.primitives en la página de cambios de características de Qiskit 1.0. Véase StatevectorSampler y StatevectorEstimator para las implementaciones de referencia primitivas de V2.
Visión general
Se introduce la versión 2 de las primitivas con una nueva clase base tanto para el Muestreador como para el Estimador ( BaseSamplerV2 y BaseEstimatorV2 ), junto con nuevos tipos para sus entradas y salidas.
La nueva interfaz permite especificar un único circuito y múltiples observables (si se utiliza 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 puedan especificarse de manera eficiente. Antes había que especificar varias veces el mismo circuito para que coincidiera con el tamaño de los datos que se iban a combinar. Además, aunque puede seguir utilizando resilience_level (si utiliza Estimator) como mando simple, las primitivas de V2 le ofrecen la flexibilidad de activar o desactivar métodos individuales de mitigación/supresión de errores para personalizarlos según sus necesidades.
Para reducir el tiempo total de ejecución del trabajo, las primitivas V2 sólo aceptan circuitos y observables que utilicen instrucciones soportadas por la QPU (unidad de procesamiento cuántico) de destino. Dichos circuitos y observables se denominan circuitos y observables de arquitectura de conjunto de instrucciones (ISA). V2 no realizan operaciones de diseño, enrutamiento y traducción. Consulte la documentación de transpilación para obtener instrucciones para transformar circuitos.
El muestreador V2 se simplifica para centrarse en su tarea principal de muestrear el registro de salida de la ejecución de circuitos cuánticos. Devuelve las muestras, cuyo tipo define el programa, sin pesos. Los datos de salida también están separados por los nombres de los registros de salida definidos por el programa. Este cambio permite la futura compatibilidad con circuitos con flujo de control clásico.
Consulte la referencia de la API EstimatorV2 y la referencia de la API SamplerV2 para obtener detalles completos.
Cambios importantes
Importar
Por compatibilidad con versiones anteriores, debe importar explícitamente las primitivas V2. Especificar import <primitive>V2 as <primitive> no es necesario, pero facilita la transición del código a V2.
Cuando las primitivas V1 dejen de ser compatibles, import <primitive> importará la versión V2 de la primitiva especificada.
from qiskit_ibm_runtime import EstimatorV2 as Estimatorfrom qiskit_ibm_runtime import Estimatorfrom qiskit_ibm_runtime import SamplerV2 as Samplerfrom qiskit_ibm_runtime import SamplerEntrada y salida
Entrada
Tanto SamplerV2 como EstimatorV2 toman como entrada uno o varios bloques unificados primitivos (PUB). 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.
- Muestreador V2 PUB formato: (
<circuit>,<parameter values>,<shots>), donde<parameter values>y<shots>son opcionales. - Estimator V2 PUB format: (
<circuit>,<observables>,<parameter values>,<precision>), where<parameter values>and<precision>are optional. Numpy broadcasting rules are used when combining observables and parameter values.
Además, se han introducido los siguientes cambios:
- El estimador V2 ha ganado un argumento
precisionen el métodorun()que especifica la precisión objetivo de las estimaciones del valor de la expectativa. - El muestreador V2 tiene el argumento
shotsen su métodorun().
Ejemplos
Estimador V2 ejemplo que utiliza la precisión 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)Sampler V2 ejemplo que utiliza tomas 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),
])Resultado
La salida tiene ahora el formato PubResult formato. Un PubResult son los datos y metadatos resultantes de la ejecución de un único PUB.
-
El estimador V2 sigue devolviendo valores de expectativa.
-
La parte
datade un estimador V2 PubResult contiene tanto los valores de las expectativas como los errores estándar (stds). V1 devuelve la varianza en los metadatos. -
El muestreador «V2 » devuelve mediciones por disparo en forma de cadenas de bits , en lugar de las distribuciones de cuasiprobabilidad de la interfaz «V1 ». Las cadenas de bits muestran los resultados de las mediciones, conservando el orden en que se realizaron.
-
Sampler V2 dispone de métodos prácticos como
get_counts()para facilitar la migración. -
The Sampler V2 result objects organize data in terms of their input circuits' classical register names, for compatibility with dynamic circuits. By default, the classical register name is
meas, as shown in the following example. When defining your circuit, if you create one or more classical registers with a non-default name, use that name to get the results. You can find the classical register name by running<circuit_name>.cregs. For example,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 estimadores (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# 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 muestreo (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()# 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 » consistía en un diccionario en el que cada entrada tenía como clave una cadena de bits (representada como un entero) y como valor las cuasiprobabilidades correspondientes a cada circuito. El formato « V2 » tiene la misma clave (pero en forma de cadena) y utiliza el recuento como valor. Para convertir el formato « V2 » a « V1 », divide los recuentos entre el número de tomas; el número de tomas seleccionadas se describe en la guía de opciones del Sampler.
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 utiliza 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: "
f"{pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: "
f"{pub_result.data.beta.get_counts()}")Opciones
Las opciones se especifican de forma diferente en las primitivas V2 de estas maneras:
SamplerV2yEstimatorV2tienen ahora clases de opciones separadas. Puede ver las opciones disponibles y actualizar los valores de las opciones durante o después de la inicialización de la primitiva.- En lugar del método
set_options(), las opciones primitivas V2 tienen el métodoupdate()que aplica los cambios al atributooptions. - Si no se especifica un valor para una opción, se le da un valor especial de
Unsety se utilizan los valores predeterminados del servidor. - Para las primitivas V2, el atributo
optionses del tipodataclassPython. Puede utilizar el método incorporadoasdictpara convertirlo en un diccionario.
Consulte 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 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)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
-
Dado que Sampler V2 devuelve muestras sin postprocesado, no admite niveles de resiliencia.
-
El muestreador V2 no admite
optimization_level. -
Estimator V2 dejará de soportar
optimization_levelalrededor del 30 de septiembre de 2024. -
Estimator V2 no admite el nivel de resistencia 3. Esto se debe a que el nivel 3 de resiliencia en V1 Estimator utiliza la Cancelación Probabilística de Errores (PEC), que ha demostrado dar resultados insesgados a costa de un tiempo de procesamiento exponencial. El nivel 3 se suprimió para llamar la atención sobre esa compensación. No obstante, puede seguir utilizando PEC como método de mitigación de errores especificando la opción
pec_mitigation. -
Estimator V2 admite
resilience_level0-2, como se describe en la tabla siguiente. Estas opciones son más avanzadas que sus homólogas de V1. También puede activar/desactivar explícitamente métodos individuales de mitigación/supresión de errores.Nivel 1Nivel 2Giro de medidas Giro de medidas Mitigación de errores de lectura Mitigació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 = Truefrom qiskit_ibm_runtime import Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2from 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: "
f"{sampler.options.dynamical_decoupling.sequence_type}")from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2Transpilación
V2 sólo admiten circuitos que se adhieran a la arquitectura de conjunto de instrucciones (ISA) de un backend concreto. Dado que las primitivas no realizan operaciones de diseño, enrutamiento y traducción, las opciones de transpilación correspondientes de V1 no son compatibles.
Estado del trabajo
Las primitivas V2 tienen una nueva clase RuntimeJobV2 , que hereda de BasePrimitiveJob. El método status() de esta nueva clase devuelve una cadena en lugar de un enum JobStatus de Qiskit. Para más detalles, consulte la referencia de la API RuntimeJobV2.
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
-
Sustituya
from qiskit_ibm_runtime import Estimatorporfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Elimine cualquier declaración
from qiskit_ibm_runtime import Options, ya que la claseOptionsno es utilizada por las primitivas V2. En su lugar, puede pasar las opciones como un diccionario al inicializar la claseEstimatorV2(por ejemploestimator = Estimator(backend, options={“dynamical_decoupling”: {“enable”: True}})), o establecerlas después de la inicialización:estimator = Estimator(backend) estimator.options.dynamical_decoupling.enable = True -
Revise todas las opciones compatibles y actualícelas en consecuencia.
-
Agrupa cada circuito que quieras ejecutar con los observables y valores de parámetros que quieras aplicar al circuito en una tupla (un PUB ). Por ejemplo, utilice
(circuit1, observable1, parameter_set1)si desea ejecutarcircuit1conobservable1yparameter_set1. -
Es posible que tenga que remodelar sus matrices de observables o conjuntos de parámetros si desea aplicar su producto exterior. Por ejemplo, una matriz de observables de forma (4, 1) y una matriz de conjuntos de parámetros de forma (1, 6) le dará un resultado de (4, 6) valores de expectativa. Consulte las reglas de difusión de Numpy para obtener más detalles.
-
Opcionalmente, puede especificar la precisión que desea para ese PUB específico.
-
Actualiza el método de
run()Estimator para que admita la lista de PUB. Por ejemplo,run([(circuit1, observable1, parameter_set1)]). Si lo deseas, puedes especificar un valorprecisionaquí, que se aplicaría a todos los PUB. -
Estimador V2 los resultados de los trabajos se agrupan por PUBs. Puede 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 del estimador
Realizar un único experimento
Utilice el Estimador 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}")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 solo trabajo
Utilice el Estimador 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}")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
Utilice Estimator para ejecutar varios experimentos en un solo trabajo, aprovechando los valores de los parámetros para aumentar la reutilización de los circuitos. En el siguiente ejemplo, observe que los pasos 1 y 2 son los mismos 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}")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().valuesUtilizar sesiones y opciones avanzadas
Explora las sesiones y las opciones avanzadas para optimizar el rendimiento de los circuitos en las QPU.
El siguiente bloque de código devolverá un error para los usuarios en el Open Plan porque utiliza sesiones. Las cargas de trabajo en el Open Plan sólo pueden ejecutarse en modo trabajo o por lotes.
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: "
f"{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 a Sampler V2
- Sustituya
from qiskit_ibm_runtime import Samplerporfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Elimine cualquier declaración
from qiskit_ibm_runtime import Options, ya que la claseOptionsno es utilizada por las primitivas V2. En su lugar, puede pasar las opciones como un diccionario al inicializar la claseSamplerV2(por ejemplosampler = Sampler(backend, options={“default_shots”: 1024})), o establecerlas después de la inicialización:sampler = Sampler(backend) sampler.options.default_shots = 1024 - Revise todas las opciones compatibles y actualícelas en consecuencia.
- Agrupa en una tupla ( PUB ) cada circuito que quieras ejecutar junto con los observables y los valores de los parámetros que quieras aplicar al circuito. Por ejemplo, utiliza
(circuit1, parameter_set1)si quieres ejecutarcircuit1conparameter_set1. Si lo deseas, puedes indicar las tomas que quieres para ese « PUB » concreto. - Actualiza el método de
run()Sampler para que admita la lista de PUB. Por ejemplo,run([(circuit1, parameter_set1)]). Si lo deseas, puedes indicarloshotsaquí; esto se aplicaría a todos los PUB. - Muestreador V2 los resultados de los trabajos se agrupan por PUBs. Puede ver los datos de salida de cada PUB indexándolos. Mientras que Sampler V2 devuelve muestras no ponderadas, la clase resultante tiene un método conveniente para obtener recuentos 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()}")Necesita el nombre de registro clásico para obtener los resultados. Por defecto, se denomina meas cuando se utiliza measure_all(). Cuando definas tu circuito, si creas uno o más registros clásicos con un nombre no predeterminado, utiliza ese nombre para obtener los resultados. Puede encontrar el nombre de registro clásico ejecutando <circuit_name>.cregs. Por ejemplo, qc.cregs.
Ejemplos completos del muestreador
Realizar un único experimento
Utilice Sampler para determinar los recuentos o la distribución cuasi-probabilística de un circuito único.
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 solo trabajo
Utilice Sampler para determinar los recuentos o las distribuciones de cuasiprobabilidad de varios circuitos en un trabajo.
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
Ejecute varios experimentos en un solo trabajo, aprovechando los valores de los parámetros para aumentar la reutilización de los circuitos.
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: "
f"{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}")Utilizar sesiones y opciones avanzadas
Explora las sesiones y las opciones avanzadas para optimizar el rendimiento de los circuitos en las QPU.
El siguiente bloque de código devolverá un error para los usuarios en el Open Plan porque utiliza sesiones. Las cargas de trabajo en el Open Plan sólo pueden ejecutarse en modo trabajo o por lotes.
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
- Para obtener más información sobre la configuración de opciones, consulta las guías de opciones del Sampler, del Estimator y del Executor.
- Obtén más información sobre las entradas y salidas primitivas.
- Prueba el tutorial sobre la desigualdad de CHSH.