# Libraries of pulses for Boulder Opal

**Create parameterized pulses for simulation and optimization**

The controls used in quantum devices often take the form of pulses that are variations over predefined shapes. For example, you can choose to embed your pulse in a Gaussian envelope, to make sure that your controls evolve gradually from zero to their peak, and then back to zero again. Boulder Opal equips you with a library of such predefined pulse functions, to make it easier for you to create pulses according to certain standardized shapes. With them, you only need to provide the minimum required number of parameters that characterize their shapes to create your pulses.

The libraries of pulses are part of the Boulder Opal Toolkits, which are collections of functions, nodes, and classes that enable you to use Boulder Opal functionality faster and with less code. The pulses provided by the Toolkits come in two kinds. The first kind are pulses that belong in graphs and can therefore be used in optimization and simulation. The second are pulses that are independent of the graph framework and that are suitable to be used with closed-loop optimization and reinforcement learning, because they can easily be exported to external quantum computing backends such as Qiskit, Pyquil, and QUA.

This topic will explain how you can use these two kinds of pulses, and show examples of pulses that can be generated with them.

**The Boulder Opal Toolkits are currently in beta phase of development. Breaking changes may be introduced.**

## Graph pulses

Pulses that are represented as graph nodes can be used as normal controls in a system that you model using the graph representation.
You can use these nodes when you want to perform an optimization using `qctrl.functions.calculate_optimization`

or `qctrl.functions.calculate_stochastic_optimization`

, or a simulation using `qctrl.functions.calculate_graph`

.

You can find these pulses in the `graph.pulses`

namespace of the `Graph`

object in two types of forms: piecewise constant (PWC) pulses, which are discretized into segments of constant value, and sampleable tensor-valued function (STF) pulses, which are smooth in their shape.
These two object types are described and compared in the topic Working with time-dependent functions in Boulder Opal.
Most of the pulses are available in both PWC and STF output formats, except for those pulses that contain discontinuities, which are only available in PWC format.
The functions for the two types of pulses are distinguished by their suffixes, which are either `_pwc`

or `_stf`

.
For most applications, a pulse in PWC format is sufficient.

The pulses in both formats are usually quite similar, with the inevitable difference that PWC pulses are discretized into segments of constant value.
For this reason, PWC pulses will usually require the parameters `segment_count`

and `duration`

, which are not present in STF pulses.
The higher the number of segments that you provide in this parameter, the more finely grained the pulse will be discretized, and the closer it will be to its STF equivalent.

Note that many of the parameters that you can pass to the graph pulse functions can be Tensors, which means that they can be optimized.
For example, if you want to find out what is the width and amplitude of a Gaussian pulse to best perform a certain operation, you can pass these two parameters as optimizable tensors to the `graph.pulses.gaussian_pulse_pwc`

function and then run an optimization, using the infidelity of the gate as the cost function.
The Boulder Opal optimizer will then tell you which values of the parameters minimize the infidelity.

To learn more about graph pulses, please read their reference documentation.

## Graph-independent pulses

Sometimes the kind of optimization that is right for you doesn’t involve graphs, as explained in the topic Choosing a control design strategy in Boulder Opal.
The library of graph-independent pulses contains functions that you can use for this kind of functionality, such as closed-loop optimization and reinforcement learning.
For example, suppose that you are interested in optimizing the width or amplitude of a Gaussian pulse used to achieve a certain gate in a real system.
In this case, you can use pulses from this library together with the Boulder Opal function `qctrl.functions.calculate_closed_loop_optimization_step`

to create different Gaussian-shaped pulses and optimize them in your system.

You can access the library of graph-independent pulses via the namespace `qctrl.pulses`

of the `Qctrl`

object.
The functions of this namespace return a `Pulse`

object, which contains the duration and shape of the pulse.
This `Pulse`

object can then be discretized and sampled to obtain a series of points that would be appropriate to export to a quantum computing backend with a particular sampling rate or time step.

For example, if you’re sending your pulses to a quantum computer that requires values sampled every 1 ns, you can use the method `export_with_time_step(time_step=1e-9)`

to obtain an array of values of the pulse that meets those requirements.
You can then provide these values to the backend to apply this pulse to the target quantum device.
Similarly, if the quantum computing backend has a specific sampling rate, as opposed to a time step, you can obtain an array of numbers that is adequate to represent the pulse for your backend by using the `export_with_sampling_rate`

method of the `Pulse`

object.

As you have the liberty to also define the duration of the pulse, it is possible that the time step that you provide during discretization might not exactly divide the duration of the pulse that you passed. In this case, the total duration of the pulse will be rounded up or down to the nearest value that corresponds to a multiple of the time steps. For the other parts of the discretization, the discretized samples correspond to the value of the pulse at the middle of the segment that corresponds to the sample.

To learn more about graph-independent pulses, please read their reference documentation.

## Pulse types

The libraries of pulses have similar kinds of pulses shared among them. In the rest of this topic, we present an overview of the main types:

- General pulses: controls that go from zero to a peak, and then back to zero.
- Oscillations: pulses that consist of oscillating functions that might have multiple peaks.
- Ramps: controls that monotonically vary from an initial value to a final value.

### General pulses

These are pulses that go (either asymptotically or strictly) from zero to a peak, and then back to zero. In the Boulder Opal Toolkits these general pulses available are:

- Gaussian pulses (
`graph.pulses.gaussian_pulse_pwc`

,`graph.pulses.gaussian_pulse_stf`

, and`qctrl.pulses.gaussian_pulse`

). - Hyperbolic secant pulses (
`graph.pulses.sech_pulse_pwc`

,`graph.pulses.sech_pulse_stf`

,`qctrl.pulses.sech_pulse`

). - Cosine pulses (
`graph.pulses.cosine_pulse_pwc`

and`qctrl.pulses.cosine_pulse`

).

The next figure illustrates what a hyperbolic secant pulse created with the Boulder Opal Toolkits looks like.

A particular case of these pulses is the square pulse, which has a finite value equal to its amplitude during a specified length of time, and is zero for all the rest of the time.
Note that the square pulse is discontinuous, and therefore can’t be created as an STF.
When created as a PWC, this pulse will have as few segments as possible, in order to simplify the graph in which it is being applied.
For the graph-independent pulses, the pulse will have as many segments as required by the sampling rate of your backend.
To learn more about square pulses for Boulder Opal, see the reference documentation for `graph.pulses.square_pulse_pwc`

and `qctrl.pulses.square_pulse`

.

### Oscillations

It is possible to create pulses of many different shapes using superpositions of sines and cosines with different frequencies. In the Boulder Opal Toolkits, these pulses can be found in the form of generic sinusoids or as a Hann series of squared sines with increasingly higher frequencies. To learn more about these oscillatory pulses, see the reference documentation for:

- Sinusoids (
`graph.pulses.sinusoid_pwc`

,`graph.pulses.sinusoid_stf`

, and`qctrl.pulses.sinusoid`

). - Hann series (
`graph.pulses.hann_series_pwc`

,`graph.pulses.hann_series_stf`

, and`qctrl.pulses.hann_series`

).

The Hann series in particular can be used as a basis for pulses that are zero at the extremities. The following graph shows an example of a pulse created with a Hann superposition.

### Ramps

Some pulses of the pulse library are continuously increasing or decreasing, instead of having a peak-like profile. These are called ramps, and you can learn more about them in the reference documentation for:

- Linear ramps (
`graph.pulses.linear_ramp_pwc`

,`graph.pulses.linear_ramp_stf`

, and`qctrl.pulses.linear_ramp`

). - Hyperbolic tangent ramps (
`graph.pulses.tanh_ramp_pwc`

,`graph.pulses.tanh_ramp_stf`

, and`qctrl.pulses.tanh_ramp`

).

You can also find usage examples of the library of pulses for Boulder Opal in the user guide How to create analytical pulses for simulation and optimization. For further information about the library of pulses, take a look at their reference documentation.