Saltar al contenido principal

Ejecutar circuits dinámicos

Versiones de paquetes

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

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1

Los circuits dinámicos son herramientas poderosas con las que puedes medir qubits en medio de una ejecución de circuit cuántico y luego realizar operaciones de lógica clásica dentro del circuit, basándose en el resultado de esas mediciones de medio circuit. Este proceso también se conoce como retroalimentación clásica. Aunque estos son los primeros días de comprensión de cómo aprovechar mejor los circuits dinámicos, la comunidad de investigación cuántica ya ha identificado una serie de casos de uso, como los siguientes:

Estas mejoras aportadas por los circuits dinámicos, sin embargo, conllevan compensaciones. Las mediciones de medio circuit y las operaciones clásicas típicamente tienen un tiempo de ejecución más largo que los gates de dos qubits, y este aumento en el tiempo podría anular los beneficios de la reducida profundidad del circuit. Por lo tanto, reducir la longitud de las mediciones de medio circuit es un área de mejora en la que IBM Quantum® lanza la nueva versión de los circuits dinámicos. Para otras restricciones cuando se usan circuits dinámicos, consulta la tabla de compatibilidad de funciones de Estimator o Sampler.

La especificación de OpenQASM 3 define una serie de estructuras de flujo de control, pero Qiskit Runtime actualmente solo admite la instrucción condicional if. En el SDK de Qiskit, esto corresponde al método if_test en QuantumCircuit. Este método devuelve un gestor de contexto y se usa típicamente en una instrucción with. Esta guía describe cómo usar esta instrucción condicional.

nota

Los ejemplos de código en esta guía usan la instrucción de medición estándar para las mediciones de medio circuit. Sin embargo, se recomienda que uses la instrucción MidCircuitMeasure en su lugar, si el backend la admite. Consulta la sección de mediciones de medio circuit para más detalles.

Encontrar backends que admitan circuits dinámicos

Para encontrar todos los backends a los que tu cuenta puede acceder y que admiten circuits dinámicos, ejecuta código como el siguiente. Este ejemplo asume que has guardado tus credenciales de inicio de sesión. También podrías especificar credenciales explícitamente al inicializar tu cuenta del servicio Qiskit Runtime. Esto te permitiría ver los backends disponibles en una instancia o tipo de plan específico, por ejemplo.

Notas
  • Los backends disponibles para la cuenta dependen de la instancia especificada en las credenciales.
  • La nueva versión de los circuits dinámicos ahora está disponible para todos los usuarios en todos los backends. Consulta el anuncio para más detalles.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# This cell is hidden from users. It hides all those "...instance was not set..." warnings.
import warnings

warnings.filterwarnings("ignore", message=".*Instance was not set*")
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_kingston')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_boston')>]

Mediciones de medio circuit

Antes de qiskit-ibm-runtime v0.43.0, measure era la única instrucción de medición en Qiskit. Las mediciones de medio circuit, sin embargo, tienen requisitos de ajuste diferentes a las mediciones terminales (mediciones que ocurren al final de un circuit). Por ejemplo, debes considerar la duración de la instrucción al ajustar una medición de medio circuit porque las instrucciones más largas causan circuits con más ruido. No necesitas considerar la duración de la instrucción para las mediciones terminales porque no hay instrucciones después de las mediciones terminales.

nota

La instrucción MidCircuitMeasure se mapea a la instrucción measure_2 reportada en supported_instructions del backend. Sin embargo, measure_2 no es compatible con todos los backends. Usa service.backends(filters=lambda b: "measure_2" in b.supported_instructions) para encontrar backends que la admitan. Es posible que se agreguen nuevas mediciones en el futuro, pero esto no está garantizado.

Método MidCircuitMeasure

En qiskit-ibm-runtime v0.43.0, se introdujo la instrucción MidCircuitMeasure. Como su nombre sugiere, es una nueva instrucción de medición que está optimizada para medio circuit en QPUs de IBM®. Aunque puedes usar QuantumCircuit.measure para una medición de medio circuit, debido a su diseño, MidCircuitMeasure suele ser una mejor opción. Por ejemplo, agrega menos sobrecarga a tu circuit que cuando se usa QuantumCircuit.measure.

from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.circuit import MidCircuitMeasure

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, dynamic_circuits=True
)

circ = QuantumCircuit(2, 2)
circ.x(0)
circ.append(MidCircuitMeasure(), [0], [0])
# circ.measure([0], [0])
# circ.measure_all()
print(circ.draw(cregbundle=False))
┌───┐┌────────────┐
q_0: ┤ X ├┤0 ├
└───┘│ │
q_1: ─────┤ Measure_2 ├
│ │
c_0: ═════╡0 ╞
└────────────┘
c_1: ═══════════════════
Notas importantes
  • Debe haber al menos un registro clásico para usar las mediciones.
  • El primitivo Sampler requiere mediciones del circuit. Puedes agregar mediciones del circuit con el primitivo Estimator, pero se ignoran.

Store

Con qiskit-ibm-runtime versión 0.47.0 o posterior, puedes usar la instrucción store para guardar el resultado de una expresión clásica, si esa expresión se va a usar repetidamente. Las operaciones se paralelizan automáticamente, lo que hace que tu código sea significativamente más eficiente en tiempo de ejecución.

Para más información, consulta la guía de Retroalimentación clásica y flujo de control.

nota

Cuando usas store para guardar un valor en un registro clásico en un backend real, ese valor solo se guarda en memoria durante la ejecución y no se copia ni se devuelve en el resultado del job.

Por ejemplo, en el siguiente código, temp tiene el mismo valor que creg durante la ejecución, y el if_test funciona como se espera. Pero después de que el job termine, el temp BitArray devuelto en el resultado del job no contiene el valor de creg. Es decir, job.result()[0].data.temp es 0.

creg = ClassicalRegister(3, "c")
temp = ClassicalRegister(3, "temp")
...
qc.store(temp, creg)
with circuit.if_test((temp, 0b001)):
...

Ejemplo completo

El siguiente código crea y ejecuta un circuit dinámico en hardware de IBM®.

from qiskit_ibm_runtime import SamplerV2, QiskitRuntimeService
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.transpiler import generate_preset_pass_manager

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, dynamic_circuits=True
)

# Create a dynamic circuit

qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
qc = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits

qc.h(q0)
qc.measure(q0, c0)
with qc.if_test((c0, 1)):
qc.x(q0)
qc.measure(q0, c0)

# Convert to an ISA circuit for the given backend

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)

# Generate samplers for backend targets
sampler = SamplerV2(backend)

# Submit jobs
sampler_job = sampler.run([isa_circuit])
result = sampler_job.result()

print(
f">>> {' Job ID:':<10} {sampler_job.job_id()} ({sampler_job.status()})"
)
>>> Job ID: d88cakp789is7391vq0g (DONE)

Limitaciones de Qiskit Runtime

Ten en cuenta las siguientes restricciones cuando ejecutes circuits dinámicos en Qiskit Runtime.

  • Debido a la limitada memoria física en la electrónica de control, también hay un límite en el número de instrucciones if y el tamaño de sus operandos. Este límite es una función del número de transmisiones y el número de bits transmitidos en un trabajo (no en un circuit).

    Al procesar una condición if, los datos de medición deben transferirse a la lógica de control para realizar esa evaluación. Una transmisión es una transferencia de datos clásicos únicos, y los bits transmitidos es el número de bits clásicos que se transfieren. Considera lo siguiente:

    c0 = ClassicalRegister(3)
    c1 = ClassicalRegister(5)
    ...
    with circuit.if_test((c0, 1)) ...
    with circuit.if_test((c0, 3)) ...
    with circuit.if_test((c1[2], 1)) ...

    En el ejemplo de código anterior, los primeros dos objetos if_test en c0 se consideran una transmisión porque el contenido de c0 no cambió y, por lo tanto, no necesita retransmitirse. El if_test en c1 es una segunda transmisión. La primera transmite los tres bits en c0 y la segunda transmite solo un bit, lo que totaliza cuatro bits transmitidos.

    Actualmente, si transmites 60 bits cada vez, el trabajo puede tener aproximadamente 300 transmisiones. Si solo transmites un bit cada vez, sin embargo, el trabajo puede tener 2400 transmisiones.

  • El operando utilizado en una instrucción if_test debe tener 32 bits o menos. Por lo tanto, si estás comparando un ClassicalRegister completo, el tamaño de ese ClassicalRegister debe ser de 32 bits o menos. Si solo estás comparando un solo bit de un ClassicalRegister, sin embargo, ese ClassicalRegister puede ser de cualquier tamaño (ya que el operando es solo un bit).

    Por ejemplo, el bloque de código "No válido" no funciona porque cr tiene más de 32 bits. Sin embargo, puedes usar un registro clásico de más de 32 bits si solo estás probando un bit, como se muestra en el bloque de código "Válido".

    cr = ClassicalRegister(50)
    qr = QuantumRegister(50)
    circuit = QuantumCircuit(qr, cr)
    ...
    circ.measure(qr, cr)
    with circ.if_test((cr, 15)):
    ...
  • Los condicionales anidados no están permitidos. Por ejemplo, el siguiente bloque de código no funcionará porque tiene un if_test dentro de otro if_test:

    c1 = ClassicalRegister(1, "c1")
    c2 = ClassicalRegister(2, "c2")
    ...
    with circ.if_test((c1, 1)):
    with circ.if_test(c2, 1)):
    ...
  • Tener reset o mediciones dentro de condicionales no está admitido.

  • Las operaciones aritméticas no están admitidas.

  • Consulta la tabla de funciones de OpenQASM 3 para determinar qué funciones de OpenQASM 3 son admitidas en Qiskit y Qiskit Runtime.

  • Cuando OpenQASM 3 (en lugar de QuantumCircuit) se usa como formato de entrada para pasar circuits a los primitivos de Qiskit Runtime, solo se admiten instrucciones que puedan cargarse en Qiskit. Las operaciones clásicas, por ejemplo, no están admitidas porque no se pueden cargar en Qiskit. Consulta Importar un programa de OpenQASM 3 en Qiskit para más información.

  • Las instrucciones for, while y switch no están admitidas.

Usar circuits dinámicos con Estimator

Dado que Estimator no admite circuits dinámicos, puedes usar Sampler y construir tus propios circuits de medición en su lugar.

Para replicar el comportamiento de Estimator, sigue este proceso:

  1. Agrupa los términos de todos los observables en una partición. Esto se puede hacer usando la API PauliList, por ejemplo.
    nota

    Puedes usar el atributo primitivo BitArray para calcular los valores de expectación de los observables proporcionados.

  2. Ejecuta un circuit de cambio de base por partición (cualquier cambio de base que deba hacerse para cada partición). Consulta la utilidad del complemento de Measurement bases módulo measurement_bases para más información. Para más información, consulta la documentación del paquete de utilidades del complemento de Qiskit.
  3. Suma de nuevo los resultados para cada partición.

Restricciones

Revisa cualquier tabla de compatibilidad de funciones para comprender las restricciones cuando se usan circuits dinámicos. Ten en cuenta que la compatibilidad de funciones no depende del primitivo.

Próximos pasos

Recomendaciones