French
Languages
English
Bengali
French
German
Japanese
Korean
Portuguese
Spanish
Tamil

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 Schedules, describe instruction sequences for the control electronics. We build Schedules 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

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.

  • DriveChannels sont généralement utilisés pour contrôler des rotations de qubits uniques,

  • Les ControlChannels sont généralement utilisés pour des portes multi-qubits ou les lignes de commande supplémentaires pour les qubits réglables,

  • MeasureChannels sont spécifiques à la transmission des impulsions qui stimulent la lecture, et

  • AcquireChannels sont utilisés pour déclencher des numériseurs qui collectent des signaux de lecture.

DriveChannels, ControlChannels, et MeasureChannels sont tous dans la catégorie des PulseChannels ; 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.

alt text

There are many methods available to us for building up pulses. Our library within Qiskit Pulse contains helpful methods for building Pulses. 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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_12_0.png

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

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

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

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

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 Discriminators personnalisés et des Kernels 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]:
../../_images/tutorials_circuits_advanced_06_building_pulse_schedules_28_0.png

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

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

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

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

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 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.