Note
Cette page a été générée à partir de tutorials/circuits_advanced/06_building_pulse_schedules.ipynb.
Construire des ordonnancements d’impulsions¶
Les portes d’impulsions définissent une représentation exacte de bas niveau pour une porte d’un circuit. Une opération unique peut être implémentée avec un programme d’impulsions, qui comprend plusieurs instructions de bas niveau. Pour en savoir plus sur les entrées d’impulsions, reportez-vous à la documentation ` ici <05_pulse_gates.ipynb> ` __. Cette page explique comment créer des programmes d’impulsions.
Remarque : pour les ordinateurs d’IBM, les programmes d’impulsions sont utilisés comme sous-programmes pour décrire les portes. Auparavant, certains appareils acceptaient des programmes complets dans ce format, mais ce ne sera plus le cas après décembre 2021. D’autres fournisseurs peuvent encore accepter des programmes complets dans ce format. Quelle que soit la façon dont le programme est utilisé, la syntaxe de création du programme est la même. Lisez plus bas pour apprendre comment !
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())
Vous pouvez voir qu’il n’y a pas encore d’instruction. La section suivante de cette page explique chacune des instructions que vous pouvez ajouter à un planning, et la dernière section décrit les différents contextes d’alignement, qui déterminent comment les instructions sont placées dans le temps les unes par rapport aux autres.
Ordonnancer
des Instructions¶
delay(duration, channel) <#delay>`__
acquire(duration, channel, mem_slot, reg_slot)
Chaque type d’instruction possède son propre ensemble d’opérandes. Comme vous pouvez le voir ci-dessus, ils incluent chacun au moins un Channel
pour spécifier où l’instruction sera appliquée.
Les Channels (Canaux) sont des étiquettes pour les lignes de signal depuis le matériel de contrôle jusqu’à la puce quantique.
DriveChannel
s sont généralement utilisés pour contrôler des rotations de qubits uniques,Les
ControlChannel
s sont généralement utilisés pour des portes multi-qubits ou les lignes de commande supplémentaires pour les qubits réglables,MeasureChannel
s sont spécifiques à la transmission des impulsions qui stimulent la lecture, etAcquireChannel
s sont utilisés pour déclencher des numériseurs qui collectent des signaux de lecture.
DriveChannel
s, ControlChannel
s, et MeasureChannel
s sont tous dans la catégorie des PulseChannel
s ; cela signifie qu’ils prennent en charge les impulsions de transmission, alors que AcquireChanne
est un canal de réception seulement et ne peut pas générer de formes d’ondes.
Pour les exemples suivants, nous allons créer une instance DriveChannel
pour chaque Instruction
qui accepte un PulseChannel
. Les canaux prennent un argument entier index
. A l’exception de ControlChannel
s, l’index correspond trivialement à l’étiquette du qubit.
[21]:
from qiskit.pulse import DriveChannel
channel = DriveChannel(0)
L’impulsion Schedule
est indépendante du backend sur lequel elle s’exécute. Cependant, nous pouvons construire notre programme dans un contexte conscient du backend cible en le fournissant à pulse.build
. Dans la mesure du possible, vous devriez fournir un backend. En utilisant les accesseurs de canaux pulse.<type>_channel(<idx>)
nous pouvons nous assurer que nous n’utilisons que les ressources disponibles de la machine.
[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
¶
L’une des instructions les plus simples que nous pouvons construire est delay
. Il s’agit d’une instruction de blocage qui indique à l’électronique de contrôle de ne générer aucun signal sur le canal donné, pour la durée spécifiée. Il est utile pour contrôler le ´´timing´´ des autres instructions.
The duration here and elsewhere is in terms of the backend’s cycle time (1 / sample rate), dt
. It must take an integer value.
Pour ajouter une instruction delay
, nous spécifions une durée et un canal, où channel
peut être n’importe quel type de canal, y compris AcquireChannel
. Nous utilisons pulse.build
pour commencer un contexte de générateur d’impulsions. Cela planifie automatiquement notre délai dans le planning 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.
Impulsions (Pulses
)¶
Un Pulse
permet de spécifier une enveloppe arbitraire d’impulsion. La fréquence de modulation et la phase de la forme d’onde de sortie sont contrôlées par les instructions SetFrequency
et ShiftPhase
, que nous couvrirons ensuite.
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.
Note : Le matériel peut être implémenté par d’autres moyens, mais si nous gardons les instructions séparées, nous évitons de perdre des informations, comme la valeur de la fréquence de modulation.
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
Impulsions paramétriques¶
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.
Les autres impulsions paramétriques de la librairie (library
) incluent GaussianSquare
, Drag
et Constant
.
** Note**: L’e système d’exécution (´´backend´´) est chargé de décider exactement comment échantillonner les impulsions paramétriques. Il est possible de dessiner des impulsions paramétriques, mais les échantillons affichés ne sont pas garantis d’être les mêmes que ceux exécutés sur le backend.
[25]:
gaus = pulse.library.Gaussian(num_samples, amp, sigma,
name="Parametric Gaus")
gaus.draw()
[25]:

Les formes d’onde des impulsions décrites par les échantillons¶
Une forme d’impulsion (Waveform
) est un signal défini par une liste de d’amplitudes complexes ordonnancées appelés samples (échantillons). Chaque sample est envoyé durant un temps de cycle dt
, determiné par le backend. Si nous voulons en savoir plus au sujet de la dynamique en temps réel de notre programme, il faut connaitre la valeur de dt
. L’échantillon \(i^{th}\) (l’index commençant à 0) sera envoyé depuis l’instant i*dt
jusqu’à l’instant (i + 1)*dt
, en modulation par la fréquence du 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]:

Fonctions de la bibliothèque (library
) d’impulsions¶
Notre propre bibliothèque d’impulsions a des méthodes d’échantillonnage pour construire un Waveform
à partir de fonctions communes.
[27]:
gaus = library.gaussian(duration=num_samples, amp=amp, sigma=sigma, name="Lib Gaus")
gaus.draw()
[27]:

Quelle que soit la méthode que vous utilisez pour spécifier votre impulsion (pulse
), play
est instanciée de la même manière :
[28]:
with pulse.build() as schedule:
pulse.play(gaus, channel)
schedule.draw()
[28]:

Vous pouvez également fournir une liste ou un tableau de complexes directement pour play
[29]:
with pulse.build() as schedule:
pulse.play([0.001*i for i in range(160)], channel)
schedule.draw()
[29]:

L’instruction play
récupère sa durée depuis son Pulse
: la durée d’une impulsion paramétrée est un argument explicite, et la durée d’un Waveform
est le nombre d’échantillons d’entrée.
set_frequency
¶
Comme nous l’avons expliqué précédemment, l’enveloppe de l’impulsion de sortie est également modulée par une fréquence et une phase. Chaque canal a une fréquence par défaut accessible par backend.defaults ().
La fréquence d’un canal peut être mise à jour à tout moment dans un Schedule
par l’instruction set_frequency
. Il prend une frequency
flottante et un PulseChannel
en entrée. Toutes les impulsions sur un canal suivant une instruction set_frequency
seront modulées par la fréquence donnée jusqu’à ce qu’une autre instruction set_frequency
soit rencontrée ou jusqu’à ce que le programme se termine.
L’instruction a une durée implicite de 0
.
Remarque: Les fréquences qui peuvent être demandées sont limitées par la bande passante totale et la bande passante instantanée de chaque canal matériel (hardware
). A l’avenir, ces informations seront rapportées par le backend
.
[30]:
with pulse.build(backend) as schedule:
pulse.set_frequency(4.5e9, channel)
shift_phase
¶
L’instruction shift_phase
augmentera la phase de la modulation de fréquence d’une quantité correspondant à la valeur de phase
. Comme pour set_frequency
, ce décalage de phase affectera toutes les instructions suivantes sur le même canal jusqu’à la fin du programme. Pour annuler l’effet d’une shift_phase
, il faudra en refaire une avec la quantité opposé comme valeur de phase
.
Comme set_frequency
, l’instruction a une durée implicite de 0
.
[31]:
with pulse.build(backend) as schedule:
pulse.shift_phase(np.pi, channel)
## acquire
¶
L’instruction acquire
déclenche l’acquisition de données pour effectuer une lecture. Il prend en argument une durée, un AcquireChannel
qui correspond au qubit mesuré, et un MemorySlot
ou un RegisterSlot
. Le MemorySlot
est une mémoire classique où le résultat de lecture sera stocké. Le RegisterSlot
correspond à un registre dans l’électronique de contrôle qui stocke le résultat de lecture pour un resultat rapide.
Les instructions acquire
peuvent également prendre des Discriminator
s personnalisés et des Kernel
s en tant qu’arguments de mot-clé.
[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.
Générateur d’impulsions¶
Ici, nous allons passer en revue les plus importantes fonctionnalités de Pulse Builder pour apprendre à construire des plannings. Ce n’est pas exhaustif et pour plus de détails sur ce que vous pouvez faire à l’aide de Pulse Builder, consultez la référence d’API Pulse <https://qiskit.org/documentation/apidoc/pulse.html> __.
Contextes d’alignement¶
Le générateur comporte des contextes d’alignement qui influencent la manière dont un planning est généré. Les contextes peuvent également être imbriqués. Essayez-les et utilisez la méthode .draw ()
pour voir comment les impulsions sont alignées.
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
¶
Le « Pulse Builder » possède des contextes d’alignement qui influencent la façon dont un planning est construit. La valeur par défaut est 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]:

Remarquez qu’il n’y a pas de liberté de programmation pour les impulsions sur D1
. La seconde impulsion d’onde commence immédiatement après la première. L’impulsion sur D0
peut commencer à tout moment entre t=0
et t=100
sans changer la durée de l’horaire global. Le contexte align_left
définit l’heure de début de cette impulsion à t=0
. Vous pouvez y penser comme la justification à gauche d’un document texte.
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)
¶
Si la durée d’un bloc particulier est connue, vous pouvez également utiliser align_equispaced
pour insérer des délais d’égale durée entre chaque instruction.
[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
¶
Ce contexte d’alignement ne prévoit pas d’instructions en parallèle. Chaque instruction commence à la fin de l’instruction précédemment ajoutée.
[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]:

Offsets de phase et de fréquence¶
Nous pouvons utiliser le « Pulse builder » pour nous aider à compenser temporairement la fréquence ou la phase de nos impulsions sur 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]:

Nous vous encourageons à consulter la référence pour l’API Pulse pour en savoir plus.
[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.