{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pulses and Waveforms\n", "\n", "*What you will learn:*\n", "\n", "- how `Pulse`s are used to define the [driving Hamiltonian](programming.md#driving-hamiltonian);\n", "- what are `Waveform`s and which options you can choose from;\n", "- how to create a `Pulse`;\n", "- some helpful tips to keep in mind when creating a `Pulse`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Programming the driving Hamiltonian\n", "\n", "To program the [driving Hamiltonian](programming.md#driving-hamiltonian) of a system with $N$ atoms in Pulser, one needs to combine two objects:\n", "\n", "- The `Channel`, which defines \n", " - the [addressed basis](conventions.md#bases), i.e. the states $|a\\rangle$ and $|b\\rangle$) and\n", " - which atoms are targeted. i.e. for which atom(s) in $i \\in \\{1,...,N\\}$ is $\\Omega_i$, $\\delta_i$ and $\\phi_i$ defined.\n", "- The `Pulse`, which defines, over a given duration $\\Delta t$, \n", " - the Rabi frequency, $\\Omega(t \\rightarrow t+\\Delta t)$ (given as a `Waveform`);\n", " - the detuning, $\\delta(t \\rightarrow t+\\Delta t)$ (also given as a `Waveform`);\n", " - the phase, $\\phi$, which is constant from $t$ to $t+\\Delta t$.\n", "\n", "
\n", "\n", "By adding **pulses** to **channels**, the full driving Hamiltonian is defined over the entire duration of the `Sequence`.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "plaintext" } }, "source": [ "## Waveforms\n", "\n", "### The `Waveform` base class\n", "\n", "In Pulser, a [Waveform](apidoc/_autosummary/pulser.waveforms.Waveform.rst) defines some time-dependent parameter over a certain duration. Every `Waveform` has two key properties:\n", "- its `duration`, which is an integer value in $ns$;\n", "- its `samples`, which *define the Waveform's value at each* $ns$. \n", "\n", "
\n", "\n", "In Pulser, samples are *always* defined with a time step of **1 ns**. This means that, to access a value at `t=x #ns`, one can simply get `samples[x]`.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "plaintext" } }, "source": [ "### Available Waveforms" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "raw" } }, "source": [ "To create a `Waveform`, one must use one of its subclasses:" ] }, { "cell_type": "raw", "metadata": { "editable": true, "raw_mimetype": "text/restructuredtext", "slideshow": { "slide_type": "" }, "tags": [], "vscode": { "languageId": "raw" } }, "source": [ ".. currentmodule:: pulser.waveforms\n", "\n", ".. autosummary::\n", "\n", " ~pulser.waveforms.ConstantWaveform\n", " ~pulser.waveforms.RampWaveform\n", " ~pulser.waveforms.BlackmanWaveform\n", " ~pulser.waveforms.InterpolatedWaveform\n", " ~pulser.waveforms.CustomWaveform\n", " ~pulser.waveforms.KaiserWaveform\n", " ~pulser.waveforms.CompositeWaveform" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "plaintext" } }, "source": [ "## Pulses\n", "\n", "### Standard definition\n", "\n", "To create a `Pulse`, one must define $\\Omega(t)$, $\\delta(t)$ and $\\phi$ over a duration $\\Delta t$. While the phase $\\phi$ must be constant in each `Pulse`, $\\Omega$ and $\\delta$ are time-dependent; as such, they are defined as waveforms.\n", "\n", "
\n", "\n", "In a `Pulse`, $\\Omega(t)$ and $\\delta(t)$ are always in units of $rad/\\mu s$, while $\\phi$ is in $rad$.\n", "\n", "
\n", "\n", "\n", "As an example, below is a 500 $ns$ `Pulse`, with amplitude given by a `BlackmanWaveform` of area $\\pi$, detuning given by a `RampWaveform` from -10 to 10 $rad/\\mu s$ and a phase of $\\pi/2$.\n", "\n", "
\n", "\n", "In Pulser, **Rabi frequency** and **amplitude** are equivalent terms for $\\Omega$ and are used interchangeably.\n", "\n", "
\n", "\n", "
\n", "\n", "In the same Pulse, the amplitude and detuning waveforms **must have the same duration**.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [] }, "outputs": [], "source": [ "import numpy as np\n", "import pulser\n", "\n", "pulse = pulser.Pulse(\n", " amplitude=pulser.BlackmanWaveform(500, np.pi),\n", " detuning=pulser.RampWaveform(500, -10, 10),\n", " phase=np.pi / 2,\n", ")\n", "pulse.draw() # Draws the Pulse" ] }, { "cell_type": "markdown", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, "tags": [], "vscode": { "languageId": "raw" } }, "source": [ "### Shortcuts for constant parameters\n", "\n", "When the `amplitude` or `detuning` are constant, these class methods avoid having to use `ConstantWaveform`:" ] }, { "cell_type": "raw", "metadata": { "editable": true, "raw_mimetype": "text/restructuredtext", "slideshow": { "slide_type": "" }, "tags": [], "vscode": { "languageId": "raw" } }, "source": [ ".. currentmodule:: pulser.pulse\n", "\n", ".. autosummary::\n", " :nosignatures:\n", "\n", " Pulse.ConstantAmplitude\n", " Pulse.ConstantDetuning\n", " Pulse.ConstantPulse" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "- In `Pulse.ConstantAmplitude()` and `Pulse.ConstantDetuning()`, the pulse `duration` is taken from the `Waveform` parameter.\n", "- In `Pulse.ConstantPulse()`, `duration` must be explicitly given.\n", "\n", "
\n", "\n", "Below is an example of these methods in action, all of them creating the same 1000 $ns$ pulse with $\\Omega=1~rad/\\mu s$, $\\delta=-1~rad/\\mu s$ and $\\phi=0$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pulser\n", "\n", "const1 = pulser.ConstantWaveform(1000, 1) # A constant waveform of 1000 ns\n", "pulse1 = pulser.Pulse.ConstantAmplitude(\n", " amplitude=1, # float\n", " detuning=-const1, # Waveform\n", " phase=0, # float\n", ")\n", "pulse2 = pulser.Pulse.ConstantDetuning(\n", " amplitude=const1, # Waveform\n", " detuning=-1, # float\n", " phase=0, # float\n", ")\n", "pulse3 = pulser.Pulse.ConstantPulse(\n", " duration=1000, # int\n", " amplitude=1, # float\n", " detuning=-1, # float\n", " phase=0, # float\n", ")\n", "assert pulse1 == pulse2 == pulse3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tips for Pulse creation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Keep the `Channel` constraints in mind\n", "\n", "Some `Channel` parameters dictate what is allowed in a `Pulse` once it's added to the `Sequence`, so it is often useful to have these limitations in mind when first designing the `Pulse`. In particular, you should keep in mind:\n", "\n", "- `Channel.max_abs_detuning`, the maximum absolute value of detuning allowed;\n", "- `Channel.max_amp`, the maximum amplitude allowed;\n", "- `Channel.min_avg_amp`, the minimum average amplitude allowed (when not zero);\n", "- `Channel.min_duration`, the minimum pulse duration allowed;\n", "- `Channel.clock_period`, which dictates that every pulse's duration must be a multiple of this value.\n", "\n", "### Remember that waveforms can be concatenated\n", "\n", "When programming $\\Omega(t)$ and $\\delta(t)$ with Pulser, it's usually preferable to divide these quantities into a stream of simple pulses. However, this is not always convenient, as the natural breaking point in the `amplitude` and `detuning` waveforms may not always match. In these cases, the `CompositeWaveform` allows for the creation of a more complex waveform by concatenation of multiple, smaller waveforms. Take a look a [this page](tutorials/composite_wfs.nblink) to see how `CompositeWaveform` might help you." ] } ], "metadata": { "kernelspec": { "display_name": "__venv__", "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.10.12" } }, "nbformat": 4, "nbformat_minor": 4 }