Nota
Esta página fue generada a partir de tutorials/circuits_advanced/06_building_pulse_schedules.ipynb.
Construyendo Planificadores de Pulso¶
Las compuertas de pulso definen una representación exacta de bajo nivel para una compuerta de circuito. Se puede implementar una sola operación con un programa de pulsos, que se compone de múltiples instrucciones de bajo nivel. Para obtener más información sobre las compuertas de pulso, consulta la documentación aquí. Esta página detalla cómo crear programas de pulso.
Nota: Para los dispositivos IBM, los programas de pulso se utilizan como subrutinas para describir compuertas. Anteriormente, algunos dispositivos aceptaban programas completos en este formato, pero esto dejará de estar disponible en diciembre de 2021. Es posible que otros proveedores aún acepten programas completos en este formato. Independientemente de cómo se use el programa, la sintaxis para construir el programa es la misma. ¡Sigue leyendo para aprender!
Los programas de pulso, que se llaman Schedule
s, describen las secuencias de instrucciones para la electrónica de control. Construimos Schedule
s usando el Constructor de Pulso (Pulse Builder). Es fácil inicializar un planificador:
[20]:
from qiskit import pulse
with pulse.build(name='my_example') as my_program:
# Add instructions here
pass
my_program
[20]:
ScheduleBlock(, name="my_example", transform=AlignLeft())
Puedes ver que aún no hay instrucciones. La siguiente sección de esta página explicará cada una de las instrucciones que podrías agregar a un planificador, y la última sección describirá varios contextos de alineación, que determinan cómo se colocan las instrucciones en el tiempo en relación con las otras.
Instrucciones de Schedule
¶
Cada tipo de instrucción tiene su propio conjunto de operandos. Como puedes ver arriba, cada uno incluye al menos un Channel
para especificar dónde se aplicará la instrucción.
Los Canales son etiquetas para las líneas de señal desde el hardware de control hasta el chip cuántico.
DriveChannel
s son usados típicamente para dirigir rotaciones de un solo qubit,ControlChannel
s son usados típicamente para compuertas multiqubit o líneas de dirección adicionales para qubits ajustables,MeasureChannel
s son específicos para transmitir pulsos que estimulan la lectura, yAdquireChannel
s se utilizan para activar los digitizadores que recolectan señales de lectura.
DriveChannel
s, ControlChannel
s, y MeasureChannel
s son todos PulseChannel
s; esto significa que soportan transmitir pulsos, mientras que el AdquireChannel
es un canal solo de recepción y no puede reproducir formas de onda.
Para los siguientes ejemplos, crearemos una instancia de DriveChannel
para cada Instruction
que acepte un PulseChannel
. Los canales toman un argumento entero index
. Excepto para los ControlChannel
s, el índice mapea trivialmente a la etiqueta del qubit.
[21]:
from qiskit.pulse import DriveChannel
channel = DriveChannel(0)
El pulso Schedule
es independiente del backend en el que se ejecuta. Sin embargo, podemos construir nuestro programa en un contexto que sea consciente del backend objetivo suministrándolo a pulse.build
. Cuando sea posible, debes proporcionar un backend. Al usar los accesores del canal pulse.<type>_channel(<idx>)
podemos asegurarnos de que solo estamos utilizando recursos disponibles del dispositivo.
[22]:
from qiskit.providers.fake_provider import FakeValencia
backend = FakeValencia()
with pulse.build(backend=backend, name='backend_aware') as backend_aware_program:
channel = pulse.drive_channel(0)
print(pulse.num_qubits())
# Raises an error as backend only has 5 qubits
#pulse.drive_channel(100)
5
delay
¶
Una de las instrucciones más simples que podemos construir es delay
. Esta es una instrucción de bloqueo que le dice a la electrónica de control que no dé salida a ninguna señal en el canal dado durante el tiempo especificado. Es útil para controlar la sincronización de otras instrucciones.
La duración aquí y en otros lugares está en términos del tiempo de ciclo del backend (1 / tasa de muestreo), dt
. Debe tomar un valor entero.
Para agregar una instrucción delay
, pasamos una duración y un canal, donde channel
puede ser cualquier tipo de canal, incluyendo AcquireChannel
. Usamos pulse.build
para comenzar un contexto de Constructor de Pulso. Esto planifica automáticamente nuestro retraso en el programa delay_5dt
.
[23]:
with pulse.build(backend) as delay_5dt:
pulse.delay(5, channel)
Eso es todo al respecto. Cualquier instrucción agregada después de este retraso en el mismo canal se ejecutarán cinco pasos de tiempo más tarde de lo que lo habría hecho sin este retraso.
play
¶
La instrucción play
es responsable de ejecutar pulsos. Es sencillo añadir una instrucción de reproducción:
with pulse.build() as sched:
pulse.play(pulse, channel)
Aclaremos lo que es el argumento pulse
y exploremos algunas maneras diferentes de construir uno.
Pulsos¶
Un Pulse
especifica un pulso envolvente arbitrario. La frecuencia de modulación y la fase de la forma de onda de salida están controladas por las instrucciones set_frequency
y shift_phase
, que cubriremos a continuación.
La siguiente imagen puede proporcionar una idea de por qué se especifican por separado. Piensa en los pulsos que describen sus envolventes como entrada a un generador de forma de onda arbitraria (arbitrary waveform generator, AWG), un instrumento de laboratorio común; esto se muestra en la imagen de la izquierda. Observa que la frecuencia de muestreo limitada discretiza la señal. La señal producida por el AWG se puede mezclar con un generador de onda sinusoidal continua. La frecuencia de su salida se controla mediante instrucciones al generador de onda sinusoidal; observa la imagen de en medio. Finalmente, la señal enviada al qubit se muestra en el lado derecho de la imagen de abajo.
Nota: El hardware puede implementarse de otras formas, pero si mantenemos las instrucciones separadas, evitamos perder información explícita, como el valor de la frecuencia de modulación.
Disponemos de muchos métodos para construir pulsos. Nuestra library
dentro de Qiskit Pulse contiene métodos útiles para construir Pulse
s. Tomemos, por ejemplo, un pulso Gaussiano simple; un pulso con su envolvente descrita por una función Gaussiana muestreada. Elegimos arbitrariamente una amplitud de 1, desviación estándar \(\sigma\) de 10 y 128 puntos de muestra.
Nota: La norma de la amplitud está limitada arbitrariamente a 1.0
. Cada sistema de backend también puede imponer restricciones adicionales; por ejemplo, un tamaño de pulso mínimo de 64. Estas restricciones adicionales, si están disponibles, se proporcionarían a través de la BackendConfiguration
que se describe aquí.
[24]:
from qiskit.pulse import library
amp = 1
sigma = 10
num_samples = 128
Pulsos paramétricos¶
Construyamos nuestro pulso Gaussiano usando el pulso paramétrico Gaussian
. Un pulso paramétrico envía el nombre de la función y sus parámetros al backend, en lugar de cada muestra individual. El uso de pulsos paramétricos hace que los trabajos que envías al backend sean mucho más pequeños. Los backends de IBM Quantum limitan el tamaño máximo de trabajo que aceptan, por lo que los pulsos paramétricos pueden permitirte ejecutar programas más grandes.
Otros pulsos paramétricos en la library
incluyen GaussianSquare
, Drag
, y Constant
.
Nota: El backend es responsable de decidir exactamente cómo muestrear los pulsos paramétricos. Es posible dibujar pulsos paramétricos, pero no se garantiza que las muestras mostradas sean las mismas que las ejecutadas en el backend.
[25]:
gaus = pulse.library.Gaussian(num_samples, amp, sigma,
name="Parametric Gaus")
gaus.draw()
[25]:

Formas de onda de pulso descritas por muestras¶
Una Waveform
(forma de onda) es una señal de pulso especificada como un arreglo de amplitudes complejas ordenadas en el tiempo, o muestras. Cada muestra se reproduce para un ciclo, un paso en el tiempo dt
, determinado por el backend. Si queremos conocer la dinámica en tiempo real de nuestro programa, necesitamos conocer el valor de dt
. La muestra (indexada desde cero) \(i^{th}\) se interpretará a partir del tiempo i*dt
up to (i + 1)*dt
, modulada por la frecuencia del qubit.
[26]:
import numpy as np
times = np.arange(num_samples)
gaussian_samples = np.exp(-1/2 *((times - num_samples / 2) ** 2 / sigma**2))
gaus = library.Waveform(gaussian_samples, name="WF Gaus")
gaus.draw()
[26]:

Funciones de la biblioteca Pulse¶
Nuestra propia biblioteca de pulsos tiene métodos de muestreo para construir una Waveform
a partir de funciones comunes.
[27]:
gaus = library.gaussian(duration=num_samples, amp=amp, sigma=sigma, name="Lib Gaus")
gaus.draw()
[27]:

Independientemente del método que uses para especificar tu pulse
, play
se agrega a tu planificador de la misma manera:
[28]:
with pulse.build() as schedule:
pulse.play(gaus, channel)
schedule.draw()
[28]:

También puedes proporcionar una lista o matriz compleja directamente para play
[29]:
with pulse.build() as schedule:
pulse.play([0.001*i for i in range(160)], channel)
schedule.draw()
[29]:

La instrucción play
obtiene su duración de su Pulse
: la duración de un pulso parametrizado es un argumento explícito, y la duración de una Waveform
es el número de muestras de entrada.
set_frequency
¶
Como se explicó anteriormente, la envolvente de la forma de onda del pulso de salida también está modulada por una frecuencia y una fase. Cada canal tiene una frecuencia predeterminada listada en backend.defaults().
La frecuencia de un canal se puede actualizar en cualquier momento dentro de un Schedule
mediante la instrucción set_frequency
. Toma una frequency
de tipo flotante y un channel
tipo PulseChannel
como entrada. Todos los pulsos en un canal que siguen una instrucción set_frequency
serán modulados por la frecuencia dada hasta que se encuentre otra instrucción set_frequency
o hasta que finalice el programa.
La instrucción tiene una duración implícita de 0
.
Nota: Las frecuencias que se pueden solicitar están limitadas por el ancho de banda total y el ancho de banda instantáneo de cada canal de hardware. En el futuro, estos serán reportados por el backend
.
[30]:
with pulse.build(backend) as schedule:
pulse.set_frequency(4.5e9, channel)
shift_phase
¶
La instrucción shift_phase
aumentará la fase de la modulación de frecuencia por phase
. Al igual que set_frequency
, este cambio de fase afectará a todas las instrucciones siguientes en el mismo canal hasta que finalice el programa. Para deshacer el efecto de un shift_phase
, la phase
negativa se puede pasar a una nueva instrucción.
Como set_frequency
, la instrucción tiene una duración implícita de 0
.
[31]:
with pulse.build(backend) as schedule:
pulse.shift_phase(np.pi, channel)
acquire
¶
La instrucción acquire
activa la adquisición de datos para su lectura. Se necesita una duración, un AcquireChannel
que se asigna al qubit que se está midiendo y un MemorySlot
o un RegisterSlot
. El RegisterSlot
es una memoria clásica donde se almacenará el resultado de la lectura. El RegisterSlot
se asigna a un registro en la electrónica de control que almacena el resultado de la lectura para una respuesta rápida.
Las instrucciones acquire
también pueden tomar Discriminator
s y Kernel
s personalizados como palabras clave en los argumentos.
[32]:
from qiskit.pulse import Acquire, AcquireChannel, MemorySlot
with pulse.build(backend) as schedule:
pulse.acquire(1200, pulse.acquire_channel(0), MemorySlot(0))
Ahora que sabemos cómo agregar instrucciones Schedule
, aprendamos a controlar exactamente cuándo son ejecutadas.
Constructor de Pulso¶
Aquí, repasaremos las características más importantes del Constructor de Pulso para aprender a crear planificadores. Esto no es exhaustivo; Para obtener más detalles sobre lo que puede hacer con el Constructor de Pulso, consulta la referencia de la API de Pulse.
Contextos de alineación¶
El constructor tiene contextos de alineación que influyen en cómo se construye un planificador. Los contextos también se pueden anidar. Pruébalos y usa .draw()
para ver cómo se alinean los pulsos.
Independientemente del contexto de alineación, la duración del planificador resultante es lo más breve posible, al incluir todas las instrucciones y seguir las reglas de alineación. Esto todavía permite algunos grados de libertad para planificar instrucciones fuera del «camino más largo». Los ejemplos siguientes ilustran esto.
align_left
¶
El constructor tiene contextos de alineación que influyen en cómo se construye un planificador. El valor predeterminado es align_left
.
[33]:
with pulse.build(backend, name='Left align example') as program:
with pulse.align_left():
gaussian_pulse = library.gaussian(100, 0.5, 20)
pulse.play(gaussian_pulse, pulse.drive_channel(0))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
program.draw()
[33]:

Observa cómo no hay libertad de planificación para los pulsos en D1
. La segunda forma de onda comienza inmediatamente después de la primera. El pulso en D0
puede comenzar en cualquier momento entre t=0
y t=100
sin cambiar la duración del planificador general. El contexto align_left
establece la hora de inicio de este pulso en t=0
. Puedes pensar en esto como una justificación a la izquierda de un documento de texto.
align_right
¶
Como era de esperarse, align_right
hace lo contrario de align_left
. Elegirá t=100
en el ejemplo anterior para comenzar el pulso Gaussiano en D0
. La izquierda y la derecha también se denominan a veces planificación «lo antes posible» y «lo más tarde posible», respectivamente.
[34]:
with pulse.build(backend, name='Right align example') as program:
with pulse.align_right():
gaussian_pulse = library.gaussian(100, 0.5, 20)
pulse.play(gaussian_pulse, pulse.drive_channel(0))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
program.draw()
[34]:

align_equispaced(duration)
¶
Si se conoce la duración de un bloque en particular, también puedes usar align_equispaced
para insertar retardos de igual duración entre cada instrucción.
[35]:
with pulse.build(backend, name='example') as program:
gaussian_pulse = library.gaussian(100, 0.5, 20)
with pulse.align_equispaced(2*gaussian_pulse.duration):
pulse.play(gaussian_pulse, pulse.drive_channel(0))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
program.draw()
[35]:

align_sequential
¶
Este contexto de alineación no planifica instrucciones en paralelo. Cada instrucción comenzará al final de la instrucción agregada anteriormente.
[36]:
with pulse.build(backend, name='example') as program:
with pulse.align_sequential():
gaussian_pulse = library.gaussian(100, 0.5, 20)
pulse.play(gaussian_pulse, pulse.drive_channel(0))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
pulse.play(gaussian_pulse, pulse.drive_channel(1))
program.draw()
[36]:

Desplazamientos de fase y frecuencia¶
Podemos usar el constructor para ayudarnos a desplazar temporalmente la frecuencia o fase de nuestros pulsos en un canal.
[37]:
with pulse.build(backend, name='Offset example') as program:
with pulse.phase_offset(3.14, pulse.drive_channel(0)):
pulse.play(gaussian_pulse, pulse.drive_channel(0))
with pulse.frequency_offset(10e6, pulse.drive_channel(0)):
pulse.play(gaussian_pulse, pulse.drive_channel(0))
program.draw()
[37]:

Te recomendamos que visites la referencia de la API de Pulse para aprender más.
[38]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.22.3 |
qiskit-aer | 0.11.2 |
qiskit-ignis | 0.7.0 |
qiskit-ibmq-provider | 0.19.2 |
qiskit | 0.39.4 |
System information | |
Python version | 3.10.6 |
Python compiler | GCC 11.3.0 |
Python build | main, Nov 14 2022 16:10:14 |
OS | Linux |
CPUs | 4 |
Memory (Gb) | 3.7695083618164062 |
Thu Dec 22 18:42:20 2022 JST |
This code is a part of Qiskit
© Copyright IBM 2017, 2022.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.