The Qiskit Backend Specifications document provides a description of a standardized data format for OpenQASM (which Terra currently supports) and a new protocol for performing pulse level experiments on backends through OpenPULSE. Certain IBMQ backends have begun to support the ability to perform OpenPulse experiments, however, experiment pulse Qobjs must currently be built by hand. In the same way that Terra provides a convenient API for building circuits, Terra should also provide a simple, but powerful interface to construct pulse QObjs.
This document contains a working proposal for a Pulse API, with the aim of engaging the Qiskit community in the development process of this powerful new Terra feature.
The accepted solution should be:
[x] Easy to use.
[x] Easy to extend.
[x] Scalable to hundreds of qubits.
[ ] Support conversion of QASM to PULSE experiments.
Pulse Overview
The Backend specification provides a succinct description of the endpoint changes required to run Pulse experiments. There are three major components:
Configuration information supplied to the user required for bootstrapping the construction of pulses. This includes additional fields in the configuration.json file supplied by the backend endpoints such as the pulse drive and measurement sample discretization times dt and dtm and an additional backend endpoint defaults which supplies a JSON file that contains default information about the backend including the estimated qubit and measurement frequencies, and example gate to pulse mappings through the cmd_def and pulse_library.
[x] Support for defaults endpoint.
Submitting a PULSEtype QObj rather than a QASM which is how QuantumCircuits are submitted to backends. Pulse commands are applied to channels, these channels may be thought of as a logical abstraction of a microwave channel (although this may vary depending on the type of quantum hardware). The specification gives drive, measurement, and u-channels. For PULSE experiments the supported commands are sample pulses, fc, pv, and acquire.
[x] Support for PULSE QObj construction.
Pulse results can be in one of three forms depending on whether measurement level is 0,1,2. Support must be given for the measurement levels.
[x] 0: Corresponds to the raw pulse results from the device.
[x] 1: Corresponds to the kernel integrated values returned from the discriminator.
[x] 2: Discriminated kernel integrated values. This is the analog to the shots returned from the device for the current QASM path.
Furthermore, pulses may specify avg or single which determines whether the average shot data should be returned or if the individual shot data should be given.
Pulse Channels
Channels are a discrete time ordered lists of commands. Channel commands have an initial time (t0) and duration (which may be implicit). Non zero duration commands may not overlap on a channel.
[x] Base time ordered channel
d
Every qubit has an associated drive channel.
[x] Drive channels
m
Every qubit has an associated measurement drive channel.
[x] Measurement channels
u
Additional control channels may be given as U channels. The action of these channels will be described by the Hamiltonian returned by the backend.
[x] u channels
acquire
Acquire commands do not have run time output, but they do have an initial time and duration. Future hardware may support multiple acquires. A channel should exist for each entry in the meas_map.
[x] acquire channels
snapshot
Simulator backends may support snapshots. These may be scheduled on a snapshot channel.
[ ] snapshot channel
Pulse Commands
Pulse Qobjs have a new set of supported commands. Commands may be conditionally executed depending on the state of a register (for hardware that supports it)
[x] Base pulse command
[ ] Conditional execution support
Sample Pulse
Sample pulse commands reference a pulse in the pulse library with their name parameter. The pulse has an initial time t0 and its duration is implicitly defined by the length of the pulse it references.
[x] Sample pulse
fc
The frame change pulse specifies an advance of all future pulses on the pulse channel. The frame change has a time t0 and its duration is implicitly 0.
[x] Frame change command
pv
The persistent value pulse holds its input value from the initial time t0 until the next pulse on the channel, which implicitly defines its duration.
[x] Persistent value pulse
acquire
The acquire command specifies an acquisition of measurement data. It does not include the measurement stimulus, which may independently managed with pulses applied to the m channels. The output data from the acquire command depends on the meas_level set in the Qobj configuration.
Acquire statements also take kernels and discriminators data structures that specify the kernel integration of the acquired data to be applied (if meas_level= 1 or 2) and the discriminator function to apply to the kernel integrated data (if meas_level=2) respectively.
Acquire may store their results into a memory_slot for meas_level 0,1 and 2.
Acquire may store their discriminated result into a register it and operations may later be done conditionally on the register state. (for simulators/hardware that supports)
[x] Acquire support across meas maps.
[x] Measurement kernel configuration.
[x] Discriminator configuration.
[x] memory_slot storage.
[x] register storage.
Pulse Library
Sample pulses reference a pulse in the pulse_library.
[x] Pulses are a complex vector of n samples with norm bounded on [-1.,1.].
[ ] Backends limitations on pulses.
[x] Support reference pulse_library which will be generated by the backend with pulses that are used in the cmd_def.
Command Definitions
[ ] The command definition or (cmd_def) is a structure that is returned from the backend. It is used to relate QASM gates with their corresponding PULSE decomposition.
[ ] Command definitions may be parameterized.
Pulse Construction
Each Pulse Qobj will have many experiments, and each experiment will consist of a set of channels that contain time ordered commands. These pulse commands will be constructed to target a specific backend.
[x] There should be a clean API interface to build up these arbitrary command sets.
QASM to PULSE
Backends provide command definitions which provide a direct mapping between QASM gates and sequences of PULSE commands.
[ ] Terra should provide the functionality to convert a QASM QObj to a PULSE QObj through this mapping.
Proposed Solution
The proposed solution below is a bottom up API for constructing a Python object oriented representation of a QObj with an easy to use builder interface.
Schedule API (simple v0.8, refined v0.9)
This document will use sample code to describe the pulse API. We will build from commands up to the scheduler and establish the API hierarchy. A separate proposal will detail the internal representation of the Scheduler (ie., DAG).
The specific example will be the case of performing a spin locking experiment. The given pulse sequence involves a single qubit. The pulse sequence consists of
An initial calibrated y90p=ry(pi/2) pulse to initialize qubit in transverse plane of Bloch sphere.
A long spin locking pulse with amplitude A and phase \phi for time $\tau$.
Another calibrated pulse y90m=ry(-pi/2)
A measurement pulse measure.
An acquisition statement to measure the qubit.
Sweep \phi and fit data to obtain phase of x quadrature with respect to calibrated pulses.
Building our pulse sweep.
Initializing Channels (v0.8)
First obtain our backend
backend = IBMQ.get_backend('pulse_backend')
We then must initialize the available channels from our backend
from qi import pulse
# this is the analog of
# qc = qi.QuantumCircuit(n_qubits)
channel_bank = pulse.ChannelBank(backend)
# alternatively we could provide override parameters
channel_bank = pulse.ChannelBank(backend, n_qubits=10)
note we are very much open to a better name than ChannelBank. The channel bank is a wrapper collection over collections of different types of channels.
A channel collection is analogous in interface usage to a Quantum/ClassicalRegister (in fact this was initially name ChannelRegister and we are still open to this in order to maintain consistency with the circuit api).
Note that by default there is an acquire channel for each qubit. Non-trivial meas_maps will be enforced at compile time. It will be up to the backend to determine its behavior for qubits tied in the same meas_map entry.
The Schedule is a timed scheduled collection of commands across channels and is the pulse equivalent of the QuantumCircuit. Schedules are immutable (useful for tracking schedule reuse among other things). Internally a Schedule will be described by a control flow graph (CFG) and directed acyclic graph (DAG), these implementations will be covered in a later document and the underlying machinery should be shared between QuantumCircuit and Schedule (require collaboration with Transpiler team).
A schedule maintains information about the Commands it contains such as
exp_schedule.t0 # time of first command
exp_schedule.tf # time of last command
exp_schedule.duration # total duration of schedule
exp_schedule.children # get children Schedules that Schedule is composed of
exp_schedule.get_commands(filter_expr=None, type=None) # get all commands that satisfy given constraint. Useful for building commands such as below
exp_schedule.get_sample_pulses() # used to construct pulse library for qobj.
Building Commands(v0.8)
The PulseCommand is the base object within the pulse API. To build our pulse schedule for the phase identification experiment we will use a combination of commands supplied by the backend and commands that we create ourselves.
We could first create a sample pulse by directly creating a complex list of samples
The above DiscreteFunction decorator creates a DiscreteFunction and is not yet a command. We must call square with a duration and val to create a DiscreteFunctionCommand
spin_lock_1 = square(n, 1., name='spin_lock_1')
We will also create a library of standard pulses so alternatively we could use the builtin version
Both SamplePulse, DiscretePulse, AnalyticPulse will support dunder methods to manipulate and create new pulse objects such as __mul__, __exp__ etc. Optionally we could expose them as one dimensional numpy arrays and treat them as such.
Pulse Filters (v0.9)
to be completed
Scipy filtering backbone
Command Definitions (v0.8)
The command definitions from the backend will be converted into a CommandDefinition and will be available in backend.defaults. A given command for a set of qubits will be accesed by
The schedules returned are parameterized (except for cx). This will be expanded on later with types and parameters. This will also touch on hardware support analytic pulses. Parameters may be shared across pulses and schedules. durations cannot be parameterized as this would allow scheduling constraints to be violated. A parameterized schedule may be evaluated to generate an absolute schedule.
x_q0 = u3_q0(pi, 0, pi)
The CommandDefinition will have available all gates available in qiskit.extensions that may be constructed from the supplied entries in cmd_def and will be dynamically created and then cached. We can therefore obtain our desired initialization pulse with
We could optionally supply a Kernel or Discriminator to the Acquire command which will be used depending on the meas_level of the compiled schedule.
Building Schedules (#1919, v0.8)
A schedule has a handle on
Now that we have all of our pulses we may build our experiment schedule. The schedule supports two ways to add commands.
_insert_command which has signature def _insert_command(self, block: Union[PulseCommand, Schedule], t0: int, *args, **kwargs)
_append_command which has signature def _append_command(self, block: Union[PulseCommand, Schedule], *args, **kwargs) and appends after Schedule.tf.
Typically the first args will be
Command a channel(s)/registers(s) to operate on.
Schedule no options required.
Which are more pythonically called using the schedule channel handles. Below our simple experiment is created.
On insertion appending of a schedule all commands will be added to the new schedule and command overlaps will be enforced.
Note we do not currently allow broadcasting as a given command is almost always unique to a specific channel.
To create more complicated pulses we may compose Schedules by inserting/appending a Schedule to another Schedule just as we would with a command to a channel.
We may optionally provide a PassManager which will contain ReSchedulePasses (among other pulses for other compilation steps). A ReschedulePass takes Schedules to Schedules. For instance these may be decoupling passes. The PassManager is input to pulse.reschedule. The actual implementation of this functionality will be covered in more detail at a later point in time.
The schedule pass will be added as an optional path in the compile method alongside the internal call to transpile.
Running Job
Given scheduled qobj, running is as simple as
job = backend.run(spin_lock_qobj)
result = job.result()
The experiment memory for a given experiment may be accessed as a numpy array for measurement levels 0 and 1
result.experiments[0].get_memory()
and counts for measurement level 2.
result.experiments[0].get_counts()
In the above example we have shown how to build a phase estimation experiment, schedule/create a pulse Qobj and then run that Qobj on a backend to obtain results.
Scheduling (QuantumCircuit to Schedule) (simple v0.8, passmanager v0.9)
The most opportunity for rearranging the order of pulses exists when mapping logically ordered gates to absolutely time-ordered commands as the gate logic maps may be used to optimize pulse scheduling (care must also be taken when scheduling classical logic).
pulse.schedule will use passes of type SchedulePass and these will be designed to exploit the underlying logic of the gates to intelligently place pulses.
Transpilation, scheduling and rescheduling may be accomplished in a single step with default passes
Note that conditionals themselves have a duration and may be represented as a basic block within a control graph that is then split after (covered in later document). The backend must supply the duration of conditionals (as well as other operations)
Jumps (V0.10)
Unconditional jumps are implemented simply by appending schedules. In the future when named blocks are supported in Qobj if a given schedule is appended twice to another schedule an automatic block will be created. This cuts down on command duplication and makes optimization more efficient.
Conditional jumps can be implemented just like a conditional Command but on a Schedule
exp_cond.append(sched1).c_if(reg_0, 1)
Analysis (V0.9)
Signal
Variety of signal analysis methods
to be completed
Kerneling
Provide framework and basic implementation
to be completed
Discrimination
Provide framework and basic implementation
to be completed
Terra: Pulse API
Objective
The Qiskit Backend Specifications document provides a description of a standardized data format for OpenQASM (which Terra currently supports) and a new protocol for performing pulse level experiments on backends through OpenPULSE. Certain IBMQ backends have begun to support the ability to perform OpenPulse experiments, however, experiment pulse Qobjs must currently be built by hand. In the same way that Terra provides a convenient API for building circuits, Terra should also provide a simple, but powerful interface to construct pulse QObjs.
This document contains a working proposal for a Pulse API, with the aim of engaging the Qiskit community in the development process of this powerful new Terra feature.
The accepted solution should be:
Pulse Overview
The Backend specification provides a succinct description of the endpoint changes required to run Pulse experiments. There are three major components:
Configuration information supplied to the user required for bootstrapping the construction of pulses. This includes additional fields in the
configuration.json
file supplied by the backend endpoints such as the pulse drive and measurement sample discretization timesdt
anddtm
and an additional backend endpointdefaults
which supplies a JSON file that contains default information about the backend including the estimated qubit and measurement frequencies, and example gate to pulse mappings through thecmd_def
andpulse_library
.defaults
endpoint.Submitting a
PULSE
type
QObj rather than aQASM
which is howQuantumCircuit
s are submitted to backends. Pulse commands are applied to channels, these channels may be thought of as a logical abstraction of a microwave channel (although this may vary depending on the type of quantum hardware). The specification gives drive, measurement, and u-channels. ForPULSE
experiments the supported commands are sample pulses, fc, pv, and acquire.PULSE
QObj construction.Pulse results can be in one of three forms depending on whether measurement level is 0,1,2. Support must be given for the measurement levels.
Furthermore, pulses may specify
avg
orsingle
which determines whether the average shot data should be returned or if the individual shot data should be given.Pulse Channels
Channels are a discrete time ordered lists of commands. Channel commands have an initial time (
t0
) andduration
(which may be implicit). Non zero duration commands may not overlap on a channel.d
Every qubit has an associated drive channel.
m
Every qubit has an associated measurement drive channel.
u
Additional control channels may be given as
U
channels. The action of these channels will be described by the Hamiltonian returned by the backend.acquire
Acquire commands do not have run time output, but they do have an initial time and duration. Future hardware may support multiple acquires. A channel should exist for each entry in the
meas_map
.snapshot
Simulator backends may support snapshots. These may be scheduled on a snapshot channel.
Pulse Commands
Pulse Qobjs have a new set of supported commands. Commands may be conditionally executed depending on the state of a
register
(for hardware that supports it)Sample Pulse
Sample pulse commands reference a pulse in the pulse library with their
name
parameter. The pulse has an initial timet0
and itsduration
is implicitly defined by the length of the pulse it references.fc
The frame change pulse specifies an advance of all future pulses on the pulse channel. The frame change has a time
t0
and itsduration
is implicitly 0.pv
The persistent value pulse holds its input value from the initial time
t0
until the next pulse on the channel, which implicitly defines itsduration
.acquire
The acquire command specifies an acquisition of measurement data. It does not include the measurement stimulus, which may independently managed with pulses applied to the
m
channels. The output data from the acquire command depends on themeas_level
set in the Qobj configuration.Acquire statements also take
kernels
anddiscriminators
data structures that specify the kernel integration of the acquired data to be applied (ifmeas_level= 1 or 2
) and the discriminator function to apply to the kernel integrated data (ifmeas_level=2
) respectively.Acquire may store their results into a
memory_slot
formeas_level
0,1 and 2.Acquire may store their discriminated result into a
register
it and operations may later be done conditionally on the register state. (for simulators/hardware that supports)memory_slot
storage.register
storage.Pulse Library
Sample pulses reference a pulse in the
pulse_library
.n
samples with norm bounded on [-1.,1.].pulse_library
which will be generated by the backend with pulses that are used in the cmd_def.Command Definitions
QASM
gates with their correspondingPULSE
decomposition.Pulse Construction
Each Pulse Qobj will have many experiments, and each experiment will consist of a set of channels that contain time ordered commands. These pulse commands will be constructed to target a specific backend.
QASM to PULSE
Backends provide command definitions which provide a direct mapping between
QASM
gates and sequences ofPULSE
commands.QASM
QObj to aPULSE
QObj through this mapping.Proposed Solution
The proposed solution below is a bottom up API for constructing a Python object oriented representation of a QObj with an easy to use builder interface.
Schedule API (simple v0.8, refined v0.9)
This document will use sample code to describe the pulse API. We will build from commands up to the scheduler and establish the API hierarchy. A separate proposal will detail the internal representation of the Scheduler (ie., DAG).
The specific example will be the case of performing a spin locking experiment. The given pulse sequence involves a single qubit. The pulse sequence consists of
y90p=ry(pi/2)
pulse to initialize qubit in transverse plane of Bloch sphere.A
and phase\phi
for time $\tau$.y90m=ry(-pi/2)
measure
.\phi
and fit data to obtain phase ofx
quadrature with respect to calibrated pulses.Building our pulse sweep.
Initializing Channels (v0.8)
First obtain our backend
We then must initialize the available channels from our backend
note we are very much open to a better name than
ChannelBank
. The channel bank is a wrapper collection over collections of different types of channels.A channel collection is analogous in interface usage to a
Quantum/ClassicalRegister
(in fact this was initially nameChannelRegister
and we are still open to this in order to maintain consistency with the circuit api).Note that by default there is an acquire channel for each qubit. Non-trivial
meas_map
s will be enforced at compile time. It will be up to the backend to determine its behavior for qubits tied in the samemeas_map
entry.Initializing the
Schedule
(v0.8)We now initialize our
Schedule
.The
Schedule
is a timed scheduled collection of commands across channels and is the pulse equivalent of theQuantumCircuit
. Schedules are immutable (useful for tracking schedule reuse among other things). Internally aSchedule
will be described by a control flow graph (CFG) and directed acyclic graph (DAG), these implementations will be covered in a later document and the underlying machinery should be shared betweenQuantumCircuit
andSchedule
(require collaboration withTranspiler
team).A schedule maintains information about the
Command
s it contains such asBuilding Commands(v0.8)
The
PulseCommand
is the base object within the pulse API. To build our pulse schedule for the phase identification experiment we will use a combination of commands supplied by the backend and commands that we create ourselves.We could first create a sample pulse by directly creating a complex list of samples
Alternatively we can create a function that generates the sample pulse (outputting a numpy array) and make it a
DiscreteFunctionalPulse
The above
DiscreteFunction
decorator creates aDiscreteFunction
and is not yet a command. We must callsquare
with aduration
andval
to create aDiscreteFunctionCommand
We will also create a library of standard pulses so alternatively we could use the builtin version
We can verify the equality of the pulses
Which will check that the pulses are of the same type and if so their samples are equal.
Finally we will also support continuous functions in conjunction with a decimation strategy (v0.9).
Dunder methods (v0.9)
Both
SamplePulse
,DiscretePulse
,AnalyticPulse
will support dunder methods to manipulate and create new pulse objects such as__mul__
,__exp__
etc. Optionally we could expose them as one dimensional numpy arrays and treat them as such.Pulse Filters (v0.9)
to be completed Scipy filtering backbone
Command Definitions (v0.8)
The command definitions from the backend will be converted into a
CommandDefinition
and will be available inbackend.defaults
. A given command for a set of qubits will be accesed byThe schedules returned are parameterized (except for
cx
). This will be expanded on later with types and parameters. This will also touch on hardware support analytic pulses. Parameters may be shared across pulses and schedules.duration
s cannot be parameterized as this would allow scheduling constraints to be violated. A parameterized schedule may be evaluated to generate an absolute schedule.The
CommandDefinition
will have available all gates available inqiskit.extensions
that may be constructed from the supplied entries incmd_def
and will be dynamically created and then cached. We can therefore obtain our desired initialization pulse withWe also require an
acquire
command for our qubitWe could optionally supply a
Kernel
orDiscriminator
to theAcquire
command which will be used depending on themeas_level
of the compiled schedule.Building Schedules (#1919, v0.8)
A
schedule has a handle on
Now that we have all of our pulses we may build our experiment schedule. The schedule supports two ways to add commands.def _insert_command(self, block: Union[PulseCommand, Schedule], t0: int, *args, **kwargs)
def _append_command(self, block: Union[PulseCommand, Schedule], *args, **kwargs)
and appends afterSchedule.tf
. Typically the first args will beCommand
a channel(s)/registers(s) to operate on.Schedule
no options required. Which are more pythonically called using the schedule channel handles. Below our simple experiment is created.On insertion appending of a schedule all commands will be added to the new schedule and command overlaps will be enforced.
Note we do not currently allow broadcasting as a given command is almost always unique to a specific channel.
To create more complicated pulses we may compose
Schedule
s by inserting/appending aSchedule
to anotherSchedule
just as we would with a command to a channel.Drive/Measure channel local oscillator (#1901, v0.8)
The local oscillator of a drive/measure channel may be set with
If not set, will default to
qubit(meas)_freq_est
of qubit corresponding to channel.Scheduling (simple v0.8, passmanger v0.9)
Now that we have our experiment we must compile to obtain a
PulseQobj
The pulse library will be built on schedule time from all classes that inherit
SamplePulse
. We can optionally provide a list of schedulesWe may optionally provide a
PassManager
which will containReSchedulePass
es (among other pulses for other compilation steps). AReschedulePass
takesSchedule
s toSchedule
s. For instance these may be decoupling passes. ThePassManager
is input topulse.reschedule
. The actual implementation of this functionality will be covered in more detail at a later point in time.The
schedule
pass will be added as an optional path in thecompile
method alongside the internal call totranspile
.Running Job
Given scheduled qobj, running is as simple as
The experiment memory for a given experiment may be accessed as a numpy array for measurement levels 0 and 1
and counts for measurement level 2.
In the above example we have shown how to build a phase estimation experiment, schedule/create a pulse Qobj and then run that Qobj on a backend to obtain results.
Scheduling (QuantumCircuit to Schedule) (simple v0.8, passmanager v0.9)
The most opportunity for rearranging the order of pulses exists when mapping logically ordered gates to absolutely time-ordered commands as the gate logic maps may be used to optimize pulse scheduling (care must also be taken when scheduling classical logic).
This will simply be made available with
pulse.schedule
will use passes of typeSchedulePass
and these will be designed to exploit the underlying logic of the gates to intelligently place pulses.Transpilation, scheduling and rescheduling may be accomplished in a single step with default passes
This similarly extends to execute
Visualization (v0.8)
Can plot:
SamplePulse
Channel
Schedule
Conditional Command (v0.9)
Commands may be made conditional with
Note that conditionals themselves have a duration and may be represented as a basic block within a control graph that is then split after (covered in later document). The backend must supply the duration of conditionals (as well as other operations)
Jumps (V0.10)
Unconditional jumps are implemented simply by appending schedules. In the future when named blocks are supported in Qobj if a given schedule is appended twice to another schedule an automatic block will be created. This cuts down on command duplication and makes optimization more efficient.
Conditional jumps can be implemented just like a conditional
Command
but on aSchedule
Analysis (V0.9)
Signal
Variety of signal analysis methods to be completed
Kerneling
Provide framework and basic implementation to be completed
Discrimination
Provide framework and basic implementation to be completed
Pulse API Layout (to be updated)