{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"%config InlineBackend.figure_formats = ['svg']\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pulses and waveforms\n",
"\n",
"\n",
"A waveform is simply a time varying signal. When a pulse operation occurs, a digital to analog converter produces a signal by combinding the following data:\n",
"\n",
"* a time varying complex function $ u(t) $ which describes the baseband waveform\n",
"* a *frequency* $f$, in Hertz,\n",
"* a *phase* $\\theta$, in radians,\n",
"* a unitless *scale* $\\alpha$.\n",
"\n",
"The signal generated from this has the mathematical form\n",
"\\begin{equation}\n",
"x(t) = \\text{Re}[\\alpha u(t)e^{i (\\theta + 2 \\pi f t)}],\n",
"\\end{equation}\n",
"where $\\text{Re}$ denotes the real part of a complex number. \n",
"\n",
"In accordance with the usual conventions, we will refer to the real part of $u(t)$ as the **in-phase** component, and the imaginary part as the **quadrature** component. A general complex number $z = x + iy$ will sometimes be referred to as an **IQ-value**, with $I = x$ and $Q = y$.\n",
"\n",
"\n",
"Quil-T provides the programmer with pulse and capture-level control, both by allowing for custom baseband waveforms, as well as allowing for direct control over the run-time frequency, phase, and scale.\n",
"\n",
"\n",
"## Waveform Templates and References\n",
"\n",
"There are two ways to specify a Quil-T waveform:\n",
"- by using a pre-existing template, which has a shape dictated by certain parameters\n",
"- by referencing a custom waveform definition.\n",
"\n",
"Consider the following Quil-T code:\n",
"```\n",
"PULSE 0 \"rf\" flat(duration: 1e-6, iq: 0.5 + 0.5*i)\n",
"```\n",
"This denotes a pulse operation, on frame `0 \"rf\"`, with a total duration of one microsecond and with baseband waveform given by the `flat` template, (corresponding to $u(t) = 1/2 + i/2$ for the duration of the signal). The resulting signal produced depends also on the phase, frequency, and scaling factor associated with frame `0 \"rf\"`. These may be set explicitly, as in\n",
"```\n",
"SET-SCALE 0 \"rf\" 1.0\n",
"SET-FREQUENCY 0 \"rf\" 5e9\n",
"SET-PHASE 0 \"rf\" 0.0\n",
"```\n",
"\n",
"On the other hand, a custom waveform may be defined and used. This is done using the `DEFWAVEFORM` form,\n",
"```\n",
"DEFWAVEFORM my_waveform:\n",
" 0.01, 0.01+0.01*i, ...\n",
"```\n",
"the body of which consists of a list of complex numbers. Such a custom waveform may be referenced directly by name, as in\n",
"```\n",
"PULSE 0 \"rf\" my_waveform.\n",
"```\n",
"\n",
"The precise meaning of this depends on the *sample rate* of the associated frame: this indicates the number of samples per second consumed by the underlying digital-to-analog converter. For example, suppose that the definition of `0 \"rf\"` looked like this\n",
"```\n",
"DEFFRAME 0 \"rf\":\n",
" SAMPLE-RATE: 1000000000.0\n",
" INITIAL-FREQUENCY: 4807541957.13474\n",
" DIRECTION: \"tx\"\n",
"```\n",
"and `my_waveform` has a definition consisting of complex numbers $z_1, \\ldots, z_N$. Then, letting $r=10^9$ denote the sample rate, the resulting pulse has total duration $\\left \\lceil{\\frac{N}{r}}\\right \\rceil$, corresponding to the baseband waveform $ u(t) = z_{\\left \\lfloor{tr}\\right \\rfloor}. $ Here $\\left \\lceil{x}\\right \\rceil $ and $\\left \\lfloor{x}\\right \\rfloor$ denote the ceiling and floor of $x$, respectively. \n",
"\n",
"As before, this baseband waveform is combined with the frame's scale, frequency, and phase during digital to analog conversion.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A Catalog of Template Waveforms\n",
"\n",
"Rigetti provides a number of templates by default. These include\n",
"\n",
"- `flat`, corresponding to simple rectangular waveforms\n",
"- `gaussian`, for a Gaussian waveform\n",
"- `drag_gaussian`, for a Gaussian waveform modified by the Derivative Removal by Adiabatic Gate (DRAG) technique\n",
"- `hrm_gaussian`, for a DRAG Gaussian waveform with second-order corrections\n",
"- `erf_square`, for a flat waveform with smooth edges derived from the Gaussian error function\n",
"- `boxcar_kernel`, for a flat waveform which is normalized to integrate to 1\n",
"\n",
"Each of these waveforms has a corresponding definition in `pyquil.quiltwaveforms`. In addition to providing documentation on the meaning of each of the waveform parameters, this module also contains routines for generating samples for each template.\n",
"\n",
"Below we look at each individidually, discussing the meaning of various parameters and plotting the real part of the waveform envelope."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from pyquil.quilatom import TemplateWaveform\n",
"\n",
"def plot_waveform(wf: TemplateWaveform, sample_rate: float):\n",
" \"\"\" Plot a template waveform by sampling at the specified sample rate. \"\"\"\n",
" samples = wf.samples(sample_rate)\n",
" times = np.arange(len(samples))/sample_rate\n",
"\n",
" print(wf)\n",
" plt.plot(times, samples.real)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### flat\n",
"\n",
"A flat waveform is simple: it represents a constant signal for a certain duration. There are two required parameters:\n",
"\n",
"* `duration`: the length of the waveform, in seconds\n",
"* `iq`: a complex number"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"flat(duration: 1e-6, iq: 1)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import FlatWaveform\n",
"\n",
"plot_waveform(FlatWaveform(duration=1e-6, iq=1.0), sample_rate=1e9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Scale, Phase, Detuning\n",
"\n",
"In addition to the parameters specific to each template waveform, there are also a few generic parameters. One of these we have met: each template waveform has a required `duration` argument, indicating the length of the waveform in seconds.\n",
"\n",
"The other arguments are *optional*, and are used to modulate the basic shape. These are\n",
"\n",
"* scale $\\alpha$, which has the effect of scaling the baseband $u(t) \\mapsto \\alpha u(t)$\n",
"* phase $\\theta$, in radians, which has the effect of a phase shift on the baseband $u(t) \\mapsto e^{i \\theta} u(t)$\n",
"* detuning $f_d$, in Hertz, which has the effect of $u(t) \\mapsto e^{2 \\pi i f_d} u(t)$\n",
"\n",
"These may be provided as arguments to any template waveform, e.g.\n",
"```\n",
"PULSE 0 \"rf\" flat(duration: 1e-8, iq: 1.0, scale: 0.3, phase: 1.570796, detuning: 1e8) \n",
"```\n",
"\n",
"Below we consider this by way of the pyQuil bindings."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"flat(detuning: 10000000, duration: 1e-6, iq: 1)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plot_waveform(FlatWaveform(duration=1e-6, iq=1.0, detuning=1e7), sample_rate=1e9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### gaussian\n",
"\n",
"Several of the template waveforms provided are derived from a standard (unnormalized) Gaussian. Here we have\n",
"\\begin{equation}\n",
"u(t) = \\text{exp}\\Big(-\\frac{(t-t_0)^2}{2 \\sigma^2}\\Big),\n",
"\\end{equation}\n",
"where $t_0$ denotes the center of the Gaussian, and $\\sigma$ is the usual standard deviation. \n",
"\n",
"By Quil-T convention, this is parameterized by the Gaussian's [full width at half maximum](https://en.wikipedia.org/wiki/Full_width_at_half_maximum) (FWHM), which is defined to be\n",
"\n",
"\\begin{equation}\n",
"\\text{FWHM} = 2 \\sqrt{2 \\ln(2)} \\sigma.\n",
"\\end{equation}\n",
"\n",
"As with all Quil-T waveforms, a Quil-T `gaussian` has a finite duration, and thus corresponds to a truncation of a true Gaussian.\n",
"\n",
"In short, the parameters are:\n",
"\n",
"* `duration`: the duration of the waveform, in seconds. The Gaussian will be truncated to $[0, \\text{duration}]$\n",
"* `t0`: the center of the Gaussian, in seconds\n",
"* `fwhm`: the full width half maximum of the Gaussian, in seconds"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"gaussian(duration: 1e-6, fwhm: 4e-7, t0: 5e-7)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import GaussianWaveform\n",
"\n",
"plot_waveform(GaussianWaveform(duration=1e-6, t0=5e-7, fwhm=4e-7), sample_rate=1e9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### drag_gaussian"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `drag_gaussian` waveform extends the basic Gaussian with an additional correction factor (cf. https://arxiv.org/abs/1809.04919 and references therein). The shape is given by\n",
"\\begin{equation}\n",
"u(t) = \\Big(1 + i\\frac{\\alpha}{2 \\pi \\eta \\sigma^2}\\Big)\\text{exp}\\Big(-\\frac{(t-t_0)^2}{2 \\sigma^2}\\Big),\n",
"\\end{equation}\n",
"where $\\eta$ is the anharmonicity constant, in Hz, and $\\alpha$ is a dimensionless shape parameter.\n",
"\n",
"As before, rather than providing $\\sigma$ explicity, `drag_gaussian` takes a full-width half max parameter. \n",
"\n",
"In summary, the required arguments to `drag_gaussian` are:\n",
"\n",
"* `duration`: the duration of the waveform, in seconds. The Gaussian will be truncated to $[0, \\text{duration}]$\n",
"* `t0`: the center of the Gaussian, in seconds\n",
"* `fwhm`: the full width half maximum of the Gaussian, in seconds\n",
"* `anh`: the anharmonicity constant $\\eta$, in Hertz\n",
"* `alpha`: dimensionless shape parameter $\\alpha$.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"drag_gaussian(alpha: 1, anh: 1.1, duration: 1e-6, fwhm: 4e-7, t0: 5e-7)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import DragGaussianWaveform\n",
"\n",
"plot_waveform(\n",
" DragGaussianWaveform(duration=1e-6, t0=5e-7, fwhm=4e-7, anh=1.1, alpha=1.0),\n",
" sample_rate=1e9\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, we only plotted the *real part* of the waveform above. The imaginary part is relevant when the baseband waveform is converted to a passband waveform, as we can demonstrate below via the optional `detuning` argument."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"drag_gaussian(alpha: 1, anh: 1.1, detuning: 10000000, duration: 1e-6, fwhm: 4e-7, t0: 5e-7)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import DragGaussianWaveform\n",
"\n",
"plot_waveform(\n",
" DragGaussianWaveform(duration=1e-6, t0=5e-7, fwhm=4e-7, anh=1.1, alpha=1.0, detuning=1e7),\n",
" sample_rate=1e9\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### hrm_gaussian\n",
"\n",
"The `hrm_gaussian` waveform is a variant of `drag_gaussian` which incorporates a higher order term. The shape is given by\n",
"\\begin{equation}\n",
"u(t) = \\Big(1 - H_2 \\frac{(t-t_0)^2}{2 \\sigma^2} + i\\frac{\\alpha (t-t_0)}{2 \\pi \\eta \\sigma^2} \\big(1 - H_2 \\big(\\frac{(t-t_0)^2}{2\\sigma^2} - 1\\big)\\Big)\\text{exp}\\Big(-\\frac{(t-t_0)^2}{2 \\sigma^2}\\Big),\n",
"\\end{equation}\n",
"where $\\alpha$ is a dimensionless DRAG parameter, $\\eta$ is the anharmonicity constant, and $H_2$ is a second order correction coefficient (cf. [1]). Note that when $H_2$ equals 0 this reduces to an ordinary `drag_gaussian`.\n",
"\n",
"The required arguments to `hrm_gaussian` are:\n",
"\n",
"* `duration`: the duration of the waveform, in seconds. The Gaussian will be truncated to $[0, \\text{duration}]$\n",
"* `t0`: the center of the Gaussian, in seconds\n",
"* `fwhm`: the full width half maximum of the Gaussian, in seconds\n",
"* `anh`: the anharmonicity constant $\\eta$, in Hertz\n",
"* `alpha`: dimensionless shape parameter $\\alpha$\n",
"* `second_order_hrm_coeff`: the constant $H_2$.\n",
"\n",
"[1] Warren, W. S. (1984). Effects of arbitrary laser or NMR pulse shapes on population inversion and coherence. The Journal of Chemical Physics, 81(12), 5437–5448. doi:10.1063/1.447644 "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hrm_gaussian(alpha: 1, anh: 1.1, duration: 1e-6, fwhm: 4e-7, second_order_hrm_coeff: 0.5, t0: 5e-7)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import HrmGaussianWaveform\n",
"\n",
"plot_waveform(\n",
" HrmGaussianWaveform(duration=1e-6, t0=5e-7, fwhm=4e-7, anh=1.1, alpha=1.0, second_order_hrm_coeff=0.5),\n",
" sample_rate=1e9\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### erf_square\n",
"\n",
"The `erf_square` waveform is a variant of `flat_waveform` with the boundary discontinuities smoothed via the [error function](https://en.wikipedia.org/wiki/Error_function) (erf), and additional zero-padding.\n",
"\n",
"The required arguments are:\n",
"\n",
"* `duration`: the duration of the nonzero part of the waveform, in seconds\n",
"* `risetime`: width of each of the rise and fall sections of the pulse, in seconds\n",
"* `pad_left`: amount of zero-padding to add to the left of the pulse, in seconds\n",
"* `pad_right`: amount of zero-padding to add to the right of the pulse, in seconds\n",
"\n",
"**NOTE**: The total duration of the waveform is `duration + pad_left + pad_right`; the total duration of the *support* (nonzero entries) is `duration`.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"erf_square(duration: 1e-6, pad_left: 1e-7, pad_right: 1e-7, risetime: 1e-7)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import ErfSquareWaveform\n",
"\n",
"plot_waveform(\n",
" ErfSquareWaveform(duration=1e-6, risetime=1e-7, pad_left=1e-7, pad_right=1e-7),\n",
" sample_rate=1e9\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Capture and Kernels\n",
"\n",
"In Quil-T, waveforms are used in two places:\n",
"\n",
"* in a `PULSE` operation, to specify what signal is generated\n",
"* in a `CAPTURE` operation, to specify how to resolve a signal into a number\n",
"\n",
"The `CAPTURE` operation involves reading in a signal on a suitable signal line, and integrating it with respect to a *kernel*. \n",
"\n",
"Mathematically, there is a real-valued signal $ s(t), $ corresponding to a reading on the signal line. This is combined with a complex-valued \"baseband\" kernel $k(t)$ to get a resulting **IQ value** $z$ by\n",
"\\begin{equation}\n",
"z = \\int_{t_\\text{min}}^{t_\\text{max}} k(t) s(t) e^{-2 \\pi i f t} \\, dt, \n",
"\\end{equation}\n",
"where $f$ denotes the frequency of the associated capture frame.\n",
"\n",
"\n",
"In Quil-T, this integrating kernel is specified by a waveform, with the usual convention that is is scaled to satisfy $\\int k(t) \\, dt = 1.$ The most common example is the `boxcar_kernel`, which corresponds to a flat pulse scaled to satisfy this condition.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### boxcar_kernel\n",
"\n",
"Because of the normalization condition, the `boxcar_kernel` requires only a `duration` argument for its construction."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"boxcar_kernel(duration: 1e-6)\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pyquil.quiltwaveforms import BoxcarAveragerKernel\n",
"\n",
"plot_waveform(\n",
" BoxcarAveragerKernel(duration=1e-6),\n",
" sample_rate=1e9\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The samples should sum to (roughly) one."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"assert np.isclose(\n",
" np.sum(BoxcarAveragerKernel(duration=1e-6).samples(1e9)),\n",
" 1.0\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Note**: The reference implementations of these waveforms makes use of Python's double precision floating point arithmetic. The *actual* implementation involves a certain amount of hardware dependence. For example, Rigetti's waveform generation hardware currently makes use of 16 bit fixed point arithmetic."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Compile Time versus Run Time\n",
"\n",
"When thinking about parameters in Quil-T, there are two times to consider:\n",
"* the time of compilation, when a Quil-T program is translated to a binary format executable on Rigetti hardware\n",
"* the time at which the program is run.\n",
"\n",
"**All template parameters must be resolved at compile time.** The Quil-T compiler depends on being able to determine the size and contents of waveforms in advane. In other words, the following is not allowed:\n",
"```\n",
"DECLARE theta REAL\n",
"PULSE 0 \"rf\" flat(duration: 1e-8, iq: 1.0, phase: theta)\n",
"```\n",
"However, the following program is valid, and does something equivalent\n",
"```\n",
"DECLARE theta REAL\n",
"SHIFT-PHASE 0 \"rf\" theta\n",
"PULSE 0 \"rf\" flat(duration: 1e-8, iq: 1.0)\n",
"SHIFT-PHASE 0 \"rf\" -theta\n",
"```\n",
"(Why the second `SHIFT-PHASE`? To leave the frame `0 \"rf\"` as it was before we started!)\n",
"\n",
"\n",
"Explicit control over the *run-time* phase, frequency, or scale requires the use of one of the following instructions.\n",
"\n",
"* `SET-SCALE`\n",
"* `SET-PHASE`\n",
"* `SHIFT-PHASE`\n",
"* `SET-FREQUENCY`\n",
"* `SHIFT-FREQUENCY`\n",
"\n",
"all of which support run-time parameter arguments."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.14"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}