Writing time-dependent functions
An essential part of writing programs in the Rydberg analog model is to write the time-dependent functions representing the amplitude and detuning terms in the drive Hamiltonian. For that, QoolQit implements a set of waveforms that can be used directly and/or composed together.
Base waveforms
Section titled “Base waveforms”A full list of the available waveforms can be found in the API reference.
from qoolqit import Constant, Ramp, Delay
# An empty waveformwf1 = Delay(1.0)
# A waveform with a constant valuewf2 = Constant(1.0, 2.0)
# A waveform that ramps linearly between two valueswf3 = Ramp(1.0, -1.0, 1.0)0.00 ≤ t ≤ 1.00: Delay(t)0.00 ≤ t ≤ 1.00: Constant(t, 2.00)0.00 ≤ t ≤ 1.00: Ramp(t, -1.00, 1.00)As shown above, printing a waveform shows the duration interval over which it applies followed by the description of the waveform.
The first argument is always the duration of the waveform, and the remaining arguments depend on the information required by each waveform. The resulting object is a callable that can be evaluated at any time .
wf1(t = 0.0)wf2(t = 0.5)wf3(t = 1.0)wf1(t = 0.0) = 0.0wf2(t = 0.5) = 2.0wf3(t = 1.0) = 1.0Each waveform also supports evaluation at multiple time steps by calling it on an array of times.
import numpy as np
t_array = np.linspace(0.0, 2.0, 9)
wf3(t_array)t = [0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]wf(t) = [-1. -0.5 0. 0.5 1. 0. 0. 0. 0. ]In the waveform above, we defined it with a duration of , and then evaluated it over nine points from to . As you can see, all points after evaluated to . By default, any waveform evaluated at a time that falls outside the specified duration gives .
Waveforms can be quickly drawn with the draw() method.
wf3.draw()Composite waveforms
Section titled “Composite waveforms”The most straightforward way to arbitrarily compose waveforms is to use the >> operator. This will create a CompositeWaveform representing the waveforms in the order provided.
wf_comp = wf1 >> wf2 >> wf3Composite waveform:| 0.00 ≤ t < 1.00: Delay(t)| 1.00 ≤ t < 2.00: Constant(t, 2.00)| 2.00 ≤ t ≤ 3.00: Ramp(t, -1.00, 1.00)The code above is equivalent to calling CompositeWaveform(wf1, wf2, wf3). As shown, printing the composite waveform will automatically show the individual waveforms in the composition and the times at which they are active. These are automatically calculated from the individual waveforms. A
CompositeWaveform is by itself a subclass of Waveform, and thus the previous logic on calling it at arbitrary time values also applies.
A few convenient properties are directly available in a composite waveform:
# Total durationwf_comp.duration
# List of durations of the individual waveformswf_comp.durations
# List of times where each individual waveform starts / endswf_comp.timesTotal duration : 3.0List of durations : [1.0, 1.0, 1.0]List of times : [0.0, 1.0, 2.0, 3.0]A custom waveform can directly be a CompositeWaveform. That is the case with the PiecewiseLinear waveform, which takes a list of durations (of size ) and a list of values (of size ) and creates a linear interpolation between all values using individual waveforms of type Ramp.
from qoolqit import PiecewiseLinear
durations = [1.0, 1.0, 2.0]values = [0.0, 1.0, 0.5, 0.5]
wf_pwl = PiecewiseLinear(durations, values)
wf_pwl.draw()Defining custom waveforms
Section titled “Defining custom waveforms”The waveform system of QoolQit can be easily extended by subclassing the Waveform class and defining some key properties and methods. To exemplify this we will create a waveform representing a simple shifted sine function,
from qoolqit.waveforms import Waveform
import math
class Sin(Waveform): """A simple sine over a given duration.
Arguments: duration: the total duration. omega: the frequency of the sine wave. shift: the vertical shift of the sine wave. """
def __init__( self, duration: float, omega: float = 2.0 * math.pi, shift: float = 0.0, ) -> None: super().__init__(duration, omega = omega, shift = shift)
def function(self, t: float) -> float: return math.sin(self.omega * t) + self.shiftA few things are crucial in the snippet above:
- Keeping the
durationargument as the first one in the__init__, and initializing the parent class with that value, to be consistent with other waveforms. - Passing every other parameter needed for the waveform in the
__init__and passing it as a keyword argument to the parent class. This will automatically create aparamsdictionary of extra parameters, and set them as attributes to be used later. - Overriding the
functionabstract method, which represents the evaluation of the waveform at some timet. - Optional: overriding the
maxandminmethods. The intended result ofwf.max()andwf.min()is to get the maximum/minimum value the waveform takes over its duration. By default, the baseWaveformclass implements a brute-force sampling method that approximates the maximum and minimum values. However, if this value is easy to know from the waveform parameters, the method should be overriden.
To showcase the usage of the newly defined waveform, let's define a new sine waveform and compose it with a piecewise linear waveform.
from qoolqit import PiecewiseLinearimport math
wf1 = Sin( duration = 1.0, omega = 2.0 * math.pi, shift = 1.0)
wf2 = PiecewiseLinear( durations = [0.5, 0.5], values = [1.0, 1.0, 0.0],)
wf_comp = wf1 >> wf2Composite waveform:| 0.00 ≤ t < 1.00: Sin(t, 6.28, 1.00)| 1.00 ≤ t < 1.50: Ramp(t, 1.00, 1.00)| 1.50 ≤ t ≤ 2.00: Ramp(t, 1.00, 0.00)wf_comp.draw()Following this example, more complete Sin waveform is directly available in QoolQit implementing
from qoolqit import Sin
wf = Sin( duration = 1.0, amplitude = 2.0, omega = 6.0, phi = -5.0, shift = 1.0,)
wf.max()0.00 ≤ t ≤ 1.00: Sin(t, 2.00, 6.00, -5.00, 1.00)Maximum value: 2.999999066583827wf.draw()