Saltar al contenido principal

Ejemplos de Executor

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Recomendamos usar estas versiones o versiones más recientes.

qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.21.0
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic

Los ejemplos en esta sección ilustran algunas formas comunes de usar la primitiva Executor. Antes de ejecutar estos ejemplos, sigue las instrucciones en Instalar Qiskit y Inicio rápido con Executor.

Antes de comenzar​

Algunos de los ejemplos de código en esta página usan samplex, que es parte del paquete Samplomatic. Por lo tanto, antes de ejecutar esos bloques de código, debes instalar Samplomatic, como se muestra en el siguiente bloque de código. Para más información, consulta la documentación de Samplomatic.

pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]

Ejemplo: Circuito parametrizado​

Este ejemplo ilustra cómo agregar elementos de circuito con parámetros, así como cómo agregar elementos samplex. Consiste en estos pasos:

  1. Configurar el circuito: Generar y transpilar el circuito objetivo.
  2. Preparar un samplex: Agrupar los gates y las mediciones en cajas anotadas y generar el par de circuito template y samplex.
  3. Ejecutar: Agregar un elemento de circuito y un elemento samplex a un QuantumProgram y ejecutar ambos en un único job.

Configurar el circuito​

Preparar un estado GHZ de tres qubits, rotar los qubits alrededor del eje de Pauli-Z, y medir los qubits en la base computacional.

from qiskit.circuit import Parameter, QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Generate the circuit
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
circuit.rz(Parameter("lam"), 2)
circuit.measure_all()

Especificar el backend y transpilar el circuito para usar únicamente las instrucciones admitidas por el QPU (denominado circuito de arquitectura de conjunto de instrucciones (ISA)).

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Transpile the circuit to ISA
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3
)
isa_circuit = preset_pass_manager.run(circuit)

Preparar el samplex​

Usar la función de conveniencia generate_boxing_pass_manager y sus parámetros de twirling para agrupar los gates de dos qubits y las mediciones en cajas y aplicar anotaciones de twirling.

boxing_pm = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)

boxed_circuit = boxing_pm.run(isa_circuit)

Usar el método build para generar el circuito template y el samplex.

# Build the template circuit and the samplex
template_circuit, samplex = build(boxed_circuit)

Ejecutar los circuitos​

Executor ejecuta objetos QuantumProgram. Cada QuantumProgram puede contener varios elementos. Este ejemplo agrega un elemento de circuito y un elemento samplex para la ejecución. Para obtener todos los detalles, consulta Entrada y salida del Executor.

El primer paso es inicializar un programa vacío, solicitando 1024 shots para cada configuración de cada elemento.

# Generate a quantum program
program = QuantumProgram(shots=1024)

Agregar el elemento de circuito al QuantumProgram. Este elemento de circuito consta de dos partes: el circuito ISA y 10 conjuntos de sus valores de parámetros.

# Append the circuit and the parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, 3), # 10 sets of parameter values
)

Agregar el elemento samplex al QuantumProgram con estos argumentos:

  • El circuito template y el samplex generados por la función build
  • Diez conjuntos de valores de parámetros para el circuito original
  • El número de randomizaciones a realizar
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(
10, 3
), # 10 sets of parameter values
},
shape=(2, 14, 10),
)

Ejecutar el job de Executor​

# initialize an Executor with default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()

Recuperar el resultado para cada tarea.

# Access the results of the classical register of task #0, the CircuitItem
result_0 = result[0]["meas"]

# Access the results of the classical register of task #1, the SamplexItem
result_1 = result[1]["meas"]

Ejemplo: Realizar PEC​

Este ejemplo muestra cómo usar un elemento samplex para realizar la cancelación de errores probabilista (PEC) para la mitigación de errores.

Considera una versión espejo de un circuito con diez qubits y dos capas únicas de gates CX. Estas son las tareas principales:

El pipeline consiste en estos pasos:

  1. Configuración: Generar el circuito objetivo y agrupar sus operaciones en cajas.
  2. Aprendizaje: Aprender el ruido de las instrucciones que queremos mitigar con PEC.
  3. Ejecución: Ejecutar el circuito en un backend.
  4. Análisis: Post-procesar y analizar los resultados.

A modo de comparación, ejecutaremos este circuito espejo dos veces. Una vez con solo Pauli-twirling aplicado, y otra vez con la mitigación PEC aplicada.

nota

El uso para este ejemplo es de aproximadamente 10 minutos en un procesador Heron r2.

Configurar el circuito​

Elegir un backend y preparar un circuito de 10 qubits.

from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.transpiler import generate_preset_pass_manager
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic import build

# Initialize the service and choose a backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Prepare a circuit

num_qubits = 10
num_layers = 10

qubits = list(range(num_qubits))
circuit = QuantumCircuit(num_qubits)

for layer_idx in range(num_layers):
circuit.rx(Parameter(f"theta_{layer_idx}"), qubits)
for i in range(num_qubits // 2):
circuit.cz(qubits[2 * i], qubits[2 * i + 1])

circuit.rx(Parameter(f"phi_{layer_idx}"), qubits)
for i in range(num_qubits // 2 - 1):
circuit.cz(qubits[2 * i] + 1, qubits[2 * i + 1] + 1)

circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Combinar el circuito con su inverso para crear un circuito espejo.

mirror_circuit = circuit.compose(circuit.inverse())
mirror_circuit.measure_all()

mirror_circuit.draw("mpl", scale=0.35, fold=100)

Output of the previous code cell

Establecer algunos valores de parámetros:

import numpy as np

parameter_values = np.random.rand(mirror_circuit.num_parameters)

Usar el pass manager para transpilar el circuito a un circuito ISA.

preset_pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
)

isa_circuit = preset_pass_manager.run(mirror_circuit)

A continuación, agrupar los gates y las mediciones en cajas anotadas. Puedes hacerlo manualmente o usar la función generate_boxing_pass_manager de Samplomatic para mayor comodidad. El primer circuito solo tendrá twirling aplicado y por lo tanto solo necesita la anotación Twirl. El segundo circuito se ejecutará con mitigación PEC completa y necesita las anotaciones Twirl e InjectNoise.

# Pass manager used to create twirled-annotated boxes.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)

mirror_circuit_twirl = boxing_pm.run(isa_circuit)

# Pass manager used to create a new boxed circuit with
# both Twirl and InjectNoise annotations.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

mirror_circuit_pec = boxing_pm.run(isa_circuit)

Aprender el ruido​

Para minimizar el número de experimentos de aprendizaje del ruido, identificar las instrucciones únicas en el segundo circuito (el que tiene cajas anotadas con InjectNoise). Para definir la unicidad, dos instrucciones de caja son iguales si ambas de las siguientes son verdaderas:

  • Su contenido es igual, salvo por los gates de un solo qubit.
  • Su anotación Twirl es igual (cualquier otra anotación se ignora).

Esto lleva a tres instrucciones únicas, a saber las cajas de gates pares e impares, y la caja de medición final.

from samplomatic.utils import find_unique_box_instructions

unique_box_instructions = find_unique_box_instructions(
mirror_circuit_pec.data
)
assert len(unique_box_instructions) == 3

Inicializar un NoiseLearnerV3, elegir los parámetros de aprendizaje configurando sus opciones, y ejecutar un job de aprendizaje del ruido.

from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3

learner = NoiseLearnerV3(backend)

learner.options.shots_per_randomization = 128
learner.options.num_randomizations = 32
learner.options.layer_pair_depths = [0, 1, 2, 4, 16, 32]

learner_job = learner.run(unique_box_instructions)

learner_job.job_id()
learner_result = learner_job.result()

Convertir result al objeto requerido por el samplex usando el método result.to_dict.

noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)

Ejecutar los circuitos​

Executor ejecuta objetos QuantumProgram. Cada QuantumProgram puede contener varios elementos, que se añaden al programa. Cada elemento es una tarea que el programa debe realizar.

Inicializar un programa vacío, solicitando 1000 shots para cada configuración de cada elemento.

from qiskit_ibm_runtime.quantum_program import QuantumProgram

# Initialize an empty QuantumProgram
program = QuantumProgram(shots=1000)

A continuación, construir el circuito template y el samplex para mirror_circuit_twirl y añadirlos al programa. También solicitar 900 randomizaciones al samplex. Esto significa que el samplex generará 900 conjuntos de parámetros, y cada conjunto se ejecutará 1000 veces (el número de shots) en el QPU.

Esta es la primera tarea del programa (resultado 0).

template_twirl, samplex_twirl = build(mirror_circuit_twirl)

program.append_samplex_item(
template_twirl,
samplex=samplex_twirl,
samplex_arguments={"parameter_values": parameter_values},
shape=(900,),
)

Del mismo modo, añadir el circuito template y el samplex construidos para mirror_circuit_pec, solicitando 900 randomizaciones. Esta es la segunda tarea del programa (resultado 1).

template_pec, samplex_pec = build(mirror_circuit_pec)

program.append_samplex_item(
template_pec,
samplex=samplex_pec,
samplex_arguments={
"parameter_values": parameter_values,
"pauli_lindblad_maps": noise_maps,
"noise_scales": {
ref: -1.0 for ref in noise_maps
}, # Set the scales to -1 for PEC
},
shape=(900,),
)

Importar Executor y enviar un job.

from qiskit_ibm_runtime.executor import Executor

executor = Executor(backend)
executor_job = executor.run(program)

executor_job.job_id()

executor_results = executor_job.result()
executor_results

twirl_result = executor_results[0]

print(f"Twirl result keys:\n {list(twirl_result.keys())}\n")
print(f"Shape of results: {twirl_result['meas'].shape}")

pec_result = executor_results[1]

print(f"PEC result keys:\n {list(pec_result.keys())}\n")
print(f"Shape of results: {pec_result['meas'].shape}")
Twirl result keys:
['meas', 'measurement_flips.meas']

Shape of results: (900, 1000, 10)
PEC result keys:
['meas', 'measurement_flips.meas', 'pauli_signs']

Shape of results: (900, 1000, 10)

Analizar los resultados​

Por último, post-procesar los resultados para estimar los valores esperados de los operadores de Pauli-Z de un solo qubit actuando sobre cada uno de los diez qubits activos (valor esperado: 1.0).

# Undo measurement twirling
twirl_result_unflipped = (
twirl_result["meas"] ^ twirl_result["measurement_flips.meas"]
)

# Calculate the expectation values of single-qubit Z operators
exp_vals = 1 - 2 * twirl_result_unflipped.mean(axis=1).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.71
Qubit 1 -> 0.72
Qubit 2 -> 0.7
Qubit 3 -> 0.68
Qubit 4 -> 0.65
Qubit 5 -> 0.64
Qubit 6 -> 0.62
Qubit 7 -> 0.66
Qubit 8 -> 0.69
Qubit 9 -> 0.75
# Undo measurement twirling
pec_result_unflipped = (
pec_result["meas"] ^ pec_result["measurement_flips.meas"]
)

# Calculate the signs for PEC mitigation
signs = np.prod((-1) ** pec_result["pauli_signs"], axis=-1)
signs = signs.reshape((signs.shape[0], 1))

# Calculate the expectation values of single-qubit Z operators as required by
# PEC mitigation
exp_vals = 1 - (2 * pec_result_unflipped.mean(axis=1) * signs).mean(axis=0)

for qubit, val in enumerate(exp_vals):
print(f"Qubit {qubit} -> {np.round(val, 2)}")
Qubit 0 -> 0.98
Qubit 1 -> 1.0
Qubit 2 -> 0.99
Qubit 3 -> 0.97
Qubit 4 -> 0.97
Qubit 5 -> 0.97
Qubit 6 -> 0.96
Qubit 7 -> 0.97
Qubit 8 -> 0.97
Qubit 9 -> 0.98

Próximos pasos​

Recomendaciones