Spanish
Idiomas
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

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 Schedules, describen las secuencias de instrucciones para la electrónica de control. Construimos Schedules 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.

  • DriveChannels son usados típicamente para dirigir rotaciones de un solo qubit,

  • ControlChannels son usados típicamente para compuertas multiqubit o líneas de dirección adicionales para qubits ajustables,

  • MeasureChannels son específicos para transmitir pulsos que estimulan la lectura, y

  • AdquireChannels se utilizan para activar los digitizadores que recolectan señales de lectura.

DriveChannels, ControlChannels, y MeasureChannels son todos PulseChannels; 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 ControlChannels, 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.

alt text

Disponemos de muchos métodos para construir pulsos. Nuestra library dentro de Qiskit Pulse contiene métodos útiles para construir Pulses. 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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_12_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_14_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_16_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_18_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_20_0.png

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 Discriminators y Kernels 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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_28_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_30_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_32_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_34_0.png

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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_36_0.png

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 SoftwareVersion
qiskit-terra0.22.3
qiskit-aer0.11.2
qiskit-ignis0.7.0
qiskit-ibmq-provider0.19.2
qiskit0.39.4
System information
Python version3.10.6
Python compilerGCC 11.3.0
Python buildmain, Nov 14 2022 16:10:14
OSLinux
CPUs4
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.