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:
- Preparación eficiente de estados cuánticos, como el estado GHZ, el estado W (para más información sobre el estado W, ver también "State preparation by shallow circuits using feed forward"), y una amplia clase de estados de productos matriciales
- Entrelazamiento de largo alcance eficiente entre qubits del mismo chip usando circuits superficiales
- Muestreo eficiente de circuits tipo IQP
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.
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.
- 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.
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: ═══════════════════
- 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.
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
ify 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_testenc0se consideran una transmisión porque el contenido dec0no cambió y, por lo tanto, no necesita retransmitirse. Elif_testenc1es una segunda transmisión. La primera transmite los tres bits enc0y 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_testdebe tener 32 bits o menos. Por lo tanto, si estás comparando unClassicalRegistercompleto, el tamaño de eseClassicalRegisterdebe ser de 32 bits o menos. Si solo estás comparando un solo bit de unClassicalRegister, sin embargo, eseClassicalRegisterpuede 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
crtiene 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".- No válido
- Válido
cr = ClassicalRegister(50)qr = QuantumRegister(50)circuit = QuantumCircuit(qr, cr)...circ.measure(qr, cr)with circ.if_test((cr, 15)):...cr = ClassicalRegister(50)qr = QuantumRegister(50)circuit = QuantumCircuit(qr, cr)...circ.measure(qr, cr)with circ.if_test((cr[5], 1)):... -
Los condicionales anidados no están permitidos. Por ejemplo, el siguiente bloque de código no funcionará porque tiene un
if_testdentro de otroif_test:- No válido
- Válido
c1 = ClassicalRegister(1, "c1")c2 = ClassicalRegister(2, "c2")...with circ.if_test((c1, 1)):with circ.if_test(c2, 1)):...cr = ClassicalRegister(2)...with circuit.if_test((cr, 0b11)):... -
Tener
reseto 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,whileyswitchno 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:
- Agrupa los términos de todos los observables en una partición. Esto se puede hacer usando la API
PauliList, por ejemplo.notaPuedes usar el atributo primitivo
BitArraypara calcular los valores de expectación de los observables proporcionados. - 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_basespara más información. Para más información, consulta la documentación del paquete de utilidades del complemento de Qiskit. - 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
- Aprende a implementar desacoplamiento dinámico preciso usando stretch.
- Revisa la guía de retroalimentación clásica y flujo de control.
- Usa la visualización del horario del circuit para depurar y optimizar tus circuits dinámicos.
- No todas las funciones son compatibles con los circuits dinámicos. Consulta la sección de compatibilidad de funciones de Sampler o Executor para más detalles.