Nota
Esta página foi gerada, a partir de tutorials/circuits_advanced/06_building_pulse_schedules.ipynb.
Construindo planejamento de pulsos¶
Portas de pulso definem uma representação exata de baixo nível para um portão de circuito. Uma única operação pode ser implementada com um programa de pulso, composto por múltiplas instruções de baixo nível. Para aprender mais sobre portas pulse, consulte a documentação aqui. Esta página detalha como criar programas de pulso.
Nota: Para dispositivos IBM, os programas de pulso são usados como subrotinas para descrever portas. Anteriormente, alguns dispositivos aceitaram programas completos neste formato, mas isso está sendo descontinuado eté dezembro de 2021. Outros provedores podem ainda aceitar programas completos neste formato. Independentemente de como o programa é usado, a sintaxe para construção do programa é a mesma. Leia mais para aprender como!
Pulse programs, which are called Schedule
s, describe instruction sequences for the control electronics. We build Schedule
s using the Pulse Builder. It’s easy to initialize a schedule:
[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())
Você pode ver que ainda não há instruções. A próxima seção desta página explicará cada uma das instruções que você pode adicionar a um agendamento e a última seção descreverá vários contextos de alinhamento, que determinam como as instruções são posicionadas em relação ao tempo, umas com as outras.
Instruções da Schedule
¶
`delay(duration, channel)
<#delay>`__`play(pulse, channel)
<#play>`__`set_frequency(frequency, channel)
<#frequency>`__`shift_phase(phase, channel)
<#phase>`__`acquire(duration, channel, mem_slot, reg_slot)
<#acquire>`__
Cada tipo de instrução tem seu próprio conjunto de operações. Como você pode ver, acima, cada um deles inclui, pelo menos, um Canal
para especificar, onde a instrução será aplicada.
Canais são rótulos de linhas de sinal do hardware de controle para o processador quântico.
DriveChannel
s são usadas tipicamente para orientar rotações simples de qubit,ControlChannel
s são usadas tipicamente para portas multi-qubit ou linhas de direção adicionais para qubits ajustáveis,MeasureChannel
s são específicos para transmissão de pulsos, que estimulam a leitura eAcquireChannel
s são usados para acionar digitalizadores que recolhem sinais de leitura.
DriveChannel
s, ControlChannel
s, e MeasureChannel
s são todos PulseChannel
s; Isso significa que eles suportam pulsos transmissores, enquanto o AcquireChannel
é apenas um canal que apenas recebe e não pode executar formas de ondas.
Para os seguintes exemplos, vamos criar uma instância DriveChannel
para cada Instruction
que aceita um PulseChannel
. Canais recebem um argumento index
tipo inteiro. Exceto por ControlChannel
s, o índice mapeia trivialmente para a label qubit.
[21]:
from qiskit.pulse import DriveChannel
channel = DriveChannel(0)
O pulso Schedule
é independente do serviço em que é executado. No entanto, podemos construir nosso programa num contexto que está ciente do serviço alvo, fornecendo-o para pulse.build
. Quando possível, você deve fornecer um serviço. Usando os acessores de canal pulse.<type>_channel(<idx>)
nós podemos nos certificar que estamos utilizando apenas recursos de dispositivo disponíveis.
[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
¶
Uma das instruções mais simples que podemos construir é delay
. Esta é uma instrução de bloqueio, que diz para o controle eletrônico não produzir nenhum sinal no canal dado, durante a duração especificada. É útil para controlar o timing de outras instruções.
The duration here and elsewhere is in terms of the backend’s cycle time (1 / sample rate), dt
. It must take an integer value.
Para adicionar uma instrução de delay
passamos uma duração e um canal, onde canal
pode ser qualquer tipo de canal, incluindo AcquireChannel
. Usamos pulse.build
para começar um contexto Pulse Builder. Isto automaticamente agenda o atraso no agendamento de delay_5dt
.
[23]:
with pulse.build(backend) as delay_5dt:
pulse.delay(5, channel)
That’s all there is to it. Any instruction added after this delay on the same channel will execute five timesteps later than it would have without this delay.
play
¶
The play
instruction is responsible for executing pulses. It’s straightforward to add a play instruction:
with pulse.build() as sched:
pulse.play(pulse, channel)
Let’s clarify what the pulse
argument is and explore a few different ways to build one.
Pulsos¶
Um Pulse
especifica um pulso envelope arbitrário. A frequência de modulação e a fase da onda de saída são controladas pela instrução set_frequency
e shift_phase
, que abordaremos em seguida.
The image below may provide some intuition for why they are specified separately. Think of the pulses which describe their envelopes as input to an arbitrary waveform generator (AWG), a common lab instrument – this is depicted in the left image. Notice the limited sample rate discritizes the signal. The signal produced by the AWG may be mixed with a continuous sine wave generator. The frequency of its output is controlled by instructions to the sine wave generator; see the middle image. Finally, the signal sent to the qubit is demonstrated by the right side of the image below.
Nota: O hardware pode ser implementado de outras formas, mas se mantivermos as instruções separadas, evitamos perder informação explícita, como o valor da frequência de modulação.
There are many methods available to us for building up pulses. Our library
within Qiskit Pulse contains helpful methods for building Pulse
s. Let’s take for example a simple Gaussian pulse – a pulse with its envelope described by a sampled Gaussian function. We arbitrarily choose an amplitude of 1, standard deviation \(\sigma\) of 10, and 128 sample points.
Note: The amplitude norm is arbitrarily limited to 1.0
. Each backend system may also impose further constraints – for instance, a minimum pulse size of 64. These additional constraints, if available, would be provided through the BackendConfiguration
which is described here.
[24]:
from qiskit.pulse import library
amp = 1
sigma = 10
num_samples = 128
Pulsos paramétricos¶
Let’s build our Gaussian pulse using the Gaussian
parametric pulse. A parametric pulse sends the name of the function and its parameters to the backend, rather than every individual sample. Using parametric pulses makes the jobs you send to the backend much smaller. IBM Quantum backends limit the maximum job size that they accept, so parametric pulses may allow you to run larger programs.
Outros pulsos paramétricos na library
incluem GaussianSquare
, Drag
, e Constant
.
Nota: O backend é responsável por decidir exatamente como amostrar os pulsos paramétricos. É possível desenhar pulsos paramétricos, mas não se garante que as amostras exibidas são as mesmas que são executadas no backend.
[25]:
gaus = pulse.library.Gaussian(num_samples, amp, sigma,
name="Parametric Gaus")
gaus.draw()
[25]:

Formas de ondas de pulso descritas pelas amostras¶
Uma Waveform
é um sinal de pulso especificado, como um array de amplitudes complexas e ordenadas no tempo, ou amostras. Cada amostra é executada por um ciclo, um timestep dt
, determinado pelo backend. Se queremos saber a dinâmica do nosso programa em tempo real, precisamos saber o valor de dt
. A amostra \(i^{th}\) (zero-indexed) irá executar do passo i*dt
até o (i + 1)*dt
, modulada pela frequência do 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]:

Funções da biblioteca de pulsos¶
Nossa própria biblioteca pulso tem métodos de amostragem para construir uma Waveform
, a partir de funções comuns.
[27]:
gaus = library.gaussian(duration=num_samples, amp=amp, sigma=sigma, name="Lib Gaus")
gaus.draw()
[27]:

Independente do método que você usar para especificar seu pulse
, play
é adicionado ao seu agendamento da mesma forma:
[28]:
with pulse.build() as schedule:
pulse.play(gaus, channel)
schedule.draw()
[28]:

Você também pode fornecer uma lista complexa ou matriz diretamente para play
[29]:
with pulse.build() as schedule:
pulse.play([0.001*i for i in range(160)], channel)
schedule.draw()
[29]:

A instrução play
obtém a duração de seu Pulse
: a duração de um pulso parametrizado é um argumento explícito e a duração de uma Waveform
é o número de amostras de entrada.
set_frequency
¶
Como explicado anteriormente, o envelope da forma de onda do pulso de saída também é modulado por uma frequência e fase. Cada canal possui uma frequência padrão listada no ``backend.defaults()` <09_gathering_system_information.ipynb#Defaults>`__.
A frequência de um canal pode ser atualizada, a qualquer momento, dentro de uma Schedule
pela instrução set_frequency
. Leva uma frequency
do tipo flutuante e um PulseChannel
canal
como entrada. Todos os pulsos em um canal na sequência de uma instrução set_frequency
serão modulados pela frequência determinada até que outra instrução set_frequency
seja encontrada ou até que o programa termine.
A instrução tem uma duração implícita de 0
.
Nota: as frequências que podem ser solicitadas são limitadas pela largura de banda total e pela largura de banda instantânea de cada canal de hardware. No futuro, estes serão reportados pelo backend
.
[30]:
with pulse.build(backend) as schedule:
pulse.set_frequency(4.5e9, channel)
shift_phase
¶
A instrução shift_phase
aumentará a fase da modulação de frequência por phase
. Como set_frequency
, essa alteração de fase afetará todas as instruções seguintes no mesmo canal até que o programa termine. Para desfazer o efeito de um shift_phase
, o negativo da phase
pode ser passado para uma nova instrução.
Como set_frequency
, a instrução tem uma duração implícita de 0
.
[31]:
with pulse.build(backend) as schedule:
pulse.shift_phase(np.pi, channel)
acquire
¶
A instrução acquire
dispara a aquisição de dados para leitura. Tem a duração, um AcquireChannel
que mapeia o qubit sendo medido, e um MemorySlot
ou um RegisterSlot
. O MemorySlot
é a memória clássica onde o resultado da leitura será armazenado. O RegisterSlot
mapeia para um registro em um controle eletrônico que armazena o resultado de leitura para rápido feedback.
As instruções acquire
também podem considerar Discriminator
s personalizados e Kernel
s como argumentos de palavras-chave.
[32]:
from qiskit.pulse import Acquire, AcquireChannel, MemorySlot
with pulse.build(backend) as schedule:
pulse.acquire(1200, pulse.acquire_channel(0), MemorySlot(0))
Now that we know how to add Schedule
instructions, let’s learn how to control exactly when they’re played.
Construtor de pulso¶
Aqui, vamos analisar os recursos mais importantes do Pulse Builder para aprender como criar agendamentos. Isto não aborda à exaustão; para mais detalhes sobre o que você pode fazer usando o Construtor de Pulso, confira a Referência da API Pulso.
Contextos de alinhamento¶
O construtor tem contextos de alinhamento que influenciam a forma como um agendamento é construído. Os contextos também podem ser aninhados. Experimente-os usando .draw()
para ver como os pulsos se alinham.
Regardless of the alignment context, the duration of the resulting schedule is as short as it can be while including every instruction and following the alignment rules. This still allows some degrees of freedom for scheduling instructions off the «longest path». The examples below illuminate this.
align_left
¶
O construtor tem contextos de alinhamento que influenciam como um agendamento é construído. O padrão é 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]:

Observe como não há liberdade de agendamento para os pulsos no D1
. A segunda forma de onda começa imediatamente após a primeira. O pulso em D0
pode começar em qualquer momento entre t=0
e t=100
sem mudar a duração do agendamento geral. O contexto align_left
define o horário de início deste pulso para t=0
. Você pode pensar nisto como a justificação à esquerda de um documento de texto.
align_right
¶
Unsurprisingly, align_right
does the opposite of align_left
. It will choose t=100
in the above example to begin the gaussian pulse on D0
. Left and right are also sometimes called «as soon as possible» and «as late as possible» scheduling, respectively.
[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)
¶
Se a duração de um determinado bloco é conhecida, você também pode usar align_equispaced
para inserir atrasos de duração iguais entre cada instrução.
[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 alinhamento não agenda as instruções em paralelo. Cada instrução começará ao final da instrução previamente adicionada.
[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]:

Deslocamentos de fase e frequência¶
Podemos usar o construtor para nos ajudar a deslocar temporariamente a frequência ou fase dos nossos pulsos em um 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]:

Nós o encorajamos a visitar a Referência API Pulse para saber mais.
[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.