Nota
Esta página foi gerada, a partir de tutorials/circuits_advanced/06_building_pulse_schedules.ipynb.
Execute, interativamente, no IBM Quantum lab.
Construindo planejamento de pulsos¶
Programas pulso, que são chamados de Schedule
s, descrevem as sequências de instruções para os aparelhos de controle eletrônico. Preparamos a Schedule
s usando o Construtor de Pulsos. É fácil inicializar um agendamento:
[1]:
from qiskit import pulse
with pulse.build(name='my_example') as my_program:
# Add instructions here
pass
my_program
[1]:
Schedule(, name="my_example")
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
¶
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.
[2]:
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.
[3]:
from qiskit.test.mock 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.
A duração aqui e, em outro lugar, é em termos de tempo de ciclo do backend (1 / taxa da amostra), dt
. Deve levar um valor inteiro.
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
.
[4]:
with pulse.build(backend) as delay_5dt:
pulse.delay(5, channel)
Isto é tudo o que há nisto. Qualquer instrução adicionada, após este atraso, no mesmo canal, executará cinco vezes mais tarde, do que teria sem esse atraso.
play
¶
A instrução Rodando
é responsável para executar pulsos. É simples adicionar uma instrução para rodar:
with pulse.build() as sched:
pulse.play(pulse, channel)
Vamos esclarecer o que o argumento pulse
é e explorar algumas maneiras diferentes de construir um.
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.
A imagem, abaixo, pode fornecer alguma intuição para o motivo pelo qual elas são especificadas, separadamente. Pense em pulsos que descrevem seus envelopes como entrada, para um gerador de onda arbitrária (AWG), um instrumento comum do laboratório – isto é retratado na imagem da esquerda. Observe que a taxa de amostragem limitada descreve o sinal. O sinal produzido pelo AWG pode ser misturado com um gerador de ondas senoidais contínuas. A frequência de sua saída é controlada por instruções para o gerador de ondas senoidais; veja a imagem do meio. Finalmente, o sinal enviado para o qubit é demonstrado pelo lado direito da imagem, abaixo.
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.

Há muitos métodos disponíveis para a construção de pulsos. A nossa library
, dentro do Qiskit Pulse, contém métodos úteis para a construção de Pulse
s. Tomemos, por exemplo, um pulso Gaussiano simples – um pulso com seu envelope, descrito por uma função gaussiana amostrada. Escolhemos, arbitrariamente, uma amplitude de 1, um desvio-padrão \(\sigma\) de 10, e 128 pontos de amostragem.
Nota: A norma de amplitude é, arbitrariamente, limitada a 1.0
. Cada sistema de backend, também pode impor mais restrições – por exemplo, um tamanho de pulso mínimo de 64. Estas restrições adicionais, se disponíveis, seriam fornecidas, através da BackendConfiguration
que é descrita aqui.
[5]:
from qiskit.pulse import library
amp = 1
sigma = 10
num_samples = 128
Pulsos paramétricos¶
Vamos construir nosso pulso gaussiano usando o pulso paramétrico Gaussian
. Um pulso paramétrico envia o nome da função e seus parâmetros para o backend, ao invés de cada amostra individual. Usar pulsos paramétricos torna muito menor o trabalho que você manda para o backend. Os backends IBM Quantum limitam o tamanho máximo do trabalho que eles aceitam, então pulsos paramétricos podem permitir que você execute programas maiores.
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.
[6]:
gaus = pulse.library.Gaussian(num_samples, amp, sigma,
name="Parametric Gaus")
gaus.draw()
[6]:

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.
[7]:
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()
[7]:

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.
[8]:
gaus = library.gaussian(duration=num_samples, amp=amp, sigma=sigma, name="Lib Gaus")
gaus.draw()
[8]:

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

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

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
¶
As explained previously, the output pulse waveform envelope is also modulated by a frequency and phase. Each channel has a default frequency listed in the backend.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
.
[11]:
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
.
[12]:
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.
[13]:
from qiskit.pulse import Acquire, AcquireChannel, MemorySlot
with pulse.build(backend) as schedule:
pulse.acquire(1200, pulse.acquire_channel(0), MemorySlot(0))
Agora que sabemos como adicionar instruções Schedule
, vamos aprender a controlar exatamente quando elas são executadas.
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.
Alignment contexts¶
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.
Independentemente do contexto do alinhamento, a duração do agendamento resultante é tão curta quanto pode ser enquanto inclui todas instruções e segue as regras do alinhamento. Isso ainda permite alguns graus de liberdade para agendar instruções fora do “caminho mais longo”. Os exemplos abaixo demonstram isso.
align_left
¶
O construtor tem contextos de alinhamento que influenciam como um agendamento é construído. O padrão é align_left
.
[14]:
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()
[14]:

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
¶
Não surpreendentemente, align_right
faz o oposto de align_left
. Ele escolherá t=100
no exemplo acima para começar o pulso gaussiano em D0
. Esquerdo e direito são às vezes também chamados de agendamentos “assim que possível” e de “tão tarde quanto possível”, respectivamente.
[15]:
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()
[15]:

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.
[16]:
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()
[16]:

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.
[17]:
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()
[17]:

Phase and frequency offsets¶
Podemos usar o construtor para nos ajudar a deslocar temporariamente a frequência ou fase dos nossos pulsos em um canal.
[18]:
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()
[18]:

Nós o encorajamos a visitar a Referência API Pulse para saber mais.
[19]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
/Users/thomas/opt/anaconda3/envs/qiskit-3.8/lib/python3.8/site-packages/qiskit/aqua/operators/operator_globals.py:48: DeprecationWarning: `from_label` is deprecated and will be removed no earlier than 3 months after the release date. Use Pauli(label) instead.
X = make_immutable(PrimitiveOp(Pauli.from_label('X')))
Version Information
Qiskit Software | Version |
---|---|
Qiskit | 0.23.6 |
Terra | 0.17.0 |
Aer | 0.7.5 |
Ignis | 0.5.2 |
Aqua | 0.8.2 |
IBM Q Provider | 0.11.1 |
System information | |
Python | 3.8.5 (default, Aug 5 2020, 03:39:04) [Clang 10.0.0 ] |
OS | Darwin |
CPUs | 8 |
Memory (Gb) | 32.0 |
Sat Feb 27 11:04:23 2021 AST |
This code is a part of Qiskit
© Copyright IBM 2017, 2021.
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.