{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to add smoothing and band-limits to optimized controls\n",
"**Incorporate smoothing of optimized waveforms**\n",
"\n",
"Boulder Opal exposes a highly-flexible optimization engine for general-purpose gradient-based optimization.\n",
"It can be directly applied to model-based control optimization for arbitrary-dimensional quantum systems.\n",
"In general the optimizer will employ whatever freedom it is given, including infinite-bandwidth transitions between time-slices of a piecewise-constant signal.\n",
"However, such controls will not generally be faithfully reproduced on hardware, causing a challenge in realizing high-fidelity controls.\n",
"However, by incorporating smoothing into the optimization routine we can ensure that the outputs of an optimization are well reproduced in hardware.\n",
"\n",
"In this user guide we introduce two different techniques for creating smooth or band-limited optimized controls: incorporating linear time-invariant filters into an optimization and using bounding slew rates in controls.\n",
"To learn the basics about control optimization, you can follow our [robust optimization tutorial](https://docs.q-ctrl.com/boulder-opal/tutorials/design-robust-single-qubit-gates-using-computational-graphs)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary workflow\n",
"\n",
"### 1. Define smoothing constraint in computational graph\n",
"The flexible [Boulder Opal optimization engine](https://docs.q-ctrl.com/boulder-opal/references/qctrl/Functions/calculate_optimization.html) expresses all optimization problems as data flow [graphs](https://docs.q-ctrl.com/boulder-opal/topics/understanding-graphs-in-boulder-opal), which describe how optimization variables (variables that can be tuned by the optimizer) are transformed into the cost function (the objective that the optimizer attempts to minimize).\n",
"\n",
"In order to add smoothing you can pursue several different methods:\n",
"\n",
"#### Filtering control signals\n",
"One can achieve tight frequency-domain control over the spectral content of a waveform using filtering.\n",
"The technique we use here is to pass the controls through a linear time-invariant filter in order to enforce explicit bandwidth limits.\n",
"To implement this behavior, we can create optimizable signals as usual, and then use the `graph.utils.filter_and_resample_pwc` node from the [utils](https://docs.q-ctrl.com/boulder-opal/references/qctrl/Toolkits/utils.html) toolkit in Boulder Opal to generate the filtered piecewise-constant (PWC) signal.\n",
"\n",
"***\n",
"_**`graph.utils` is part of the [Boulder Opal Toolkits](https://docs.q-ctrl.com/boulder-opal/references/qctrl/Toolkits.html) which are currently in beta phase of development. Breaking changes may be introduced.**_\n",
"\n",
"***\n",
"\n",
"The effect of these filters is to transform the control signals before they reach the quantum system, via convolution with the filter impulse response.\n",
"Failing to take the filters into account during the optimization can lead to poor results, since in that case the system used for the optimization does not accurately model reality.\n",
"\n",
"You can also additionally multiply the values of the signal by an envelope function that goes to zero at its extremities, which will effectively anchor the beginning and the end of the optimized pulse at zero.\n",
"Note, however, that depending on the functional form of the envelope, the banwidth of the optimized signal can go beyond the bandwidth limit set by the filter.\n",
"\n",
"#### Bounding slew rates\n",
"A simple alternative is to constrain the rate of change of a signal between timesteps.\n",
"To implement a bounded slew rate, you create signal values using a Boulder Opal graph operation,\n",
"`graph.anchored_difference_bounded_variables`.\n",
"This function produces values that are constrained to satisfy the slew rate requirement, and in addition are anchored to zero at the start and end of the gate.\n",
"\n",
"\n",
"### 2. Execute graph-based optimization\n",
"\n",
"With the graph object created, an optimization can be run using the `qctrl.functions.calculate_optimization` function.\n",
"The cost, the outputs, and the graph must be provided.\n",
"The function returns the results of the optimization.\n",
"Note that this example code block uses naming that should be replaced with the naming used in your graph."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Smoothing control pulses using linear filters\n",
" \n",
"In this section we present examples showing how linear time-invariant filters may be incorporated into an optimization.\n",
"To exemplify the use of filters, we consider a basic single-qubit system described by the Hamiltonian:\n",
"\n",
"\\begin{align*}\n",
"H(t) &= \\frac{1}{2} \\Omega(t) L(\\alpha)(t) \\sigma_{x} + \\beta(t) \\sigma_{z} , \n",
"\\end{align*}\n",
"\n",
"where $\\alpha(t)$ is a real time-dependent pulse,\n",
"$L$ is the filter applied to the pulse,\n",
"$\\Omega(t)$ is an envelope fixing the pulse edges to zero,\n",
"and $\\beta(t)$ is a small, slowly-varying stochastic dephasing noise process.\n",
"\n",
"The effect of the filter is to transform the control signal before it reaches the quantum system, via convolution with the filter impulse response.\n",
"\n",
"\n",
"In this example, we use an in-built Boulder Opal function to produce the sinc filter to smooth the control pulse. Furthermore, we modulate the smoothed pulse with an envelope that enforces the final solution to start and end at zero. \n",
"From the optimizations we output three sets of signals: the raw piecewise-constant signal, its smoothed version, and the final modulated signal.\n",
"The latter is the filtered signal that actually reaches the quantum system and performs the optimized gate. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from qctrl import Qctrl\n",
"from qctrlvisualizer import plot_controls\n",
"\n",
"# Start a Boulder Opal session.\n",
"qctrl = Qctrl()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/100 [00:00"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Define physical constraints.\n",
"alpha_max = 2 * np.pi * 8.5e6 # rad/s\n",
"sinc_cutoff_frequency = 2 * np.pi * 48e6 # rad/s\n",
"optimizable_variable_count = 32\n",
"segment_count = 128\n",
"duration = 250e-9 # s\n",
"\n",
"# Create graph object.\n",
"graph = qctrl.create_graph()\n",
"\n",
"# Create unfiltered signal.\n",
"unfiltered_alpha = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=optimizable_variable_count,\n",
" duration=duration,\n",
" maximum=alpha_max,\n",
" minimum=-alpha_max,\n",
" name=\"$\\\\alpha$\",\n",
")\n",
"\n",
"# Filter signal.\n",
"rediscretized_alpha = graph.utils.filter_and_resample_pwc(\n",
" pwc=unfiltered_alpha,\n",
" cutoff_frequency=sinc_cutoff_frequency,\n",
" segment_count=segment_count,\n",
" name=\"$L(\\\\alpha)$\",\n",
")\n",
"\n",
"# Modulate signal with envelope.\n",
"envelope_signal = graph.signals.cosine_pulse_pwc(\n",
" duration=duration, segment_count=segment_count, amplitude=1.0\n",
")\n",
"alpha = envelope_signal * rediscretized_alpha\n",
"alpha.name = \"$\\\\Omega L(\\\\alpha)$\"\n",
"\n",
"# Create control term.\n",
"control_term = alpha * graph.pauli_matrix(\"X\") / 2\n",
"\n",
"# Create dephasing noise term.\n",
"dephasing_term = graph.pauli_matrix(\"Z\") / duration\n",
"\n",
"# Create infidelity.\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=control_term,\n",
" target=graph.target(graph.pauli_matrix(\"X\")),\n",
" noise_operators=[dephasing_term],\n",
" name=\"infidelity\",\n",
")\n",
"\n",
"# Run the optimization.\n",
"result = qctrl.functions.calculate_optimization(\n",
" graph=graph,\n",
" cost_node_name=\"infidelity\",\n",
" output_node_names=[\"$\\\\alpha$\", \"$L(\\\\alpha)$\", \"$\\\\Omega L(\\\\alpha)$\"],\n",
" optimization_count=8,\n",
")\n",
"print(f\"Optimized cost: {result.cost:.3e}\")\n",
"\n",
"# Visualize controls.\n",
"plot_controls(result.output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Unfiltered (top), filtered (center), and pinned (bottom) control amplitudes as a function of time."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Band-limited pulses with bounded slew rates\n",
"\n",
"Next we show how to optimize a system in which the rates of change of the controls are limited.\n",
"Using this constraint can help to ensure that optimized controls can be reliably implemented on physical hardware (which may be subject to bandwidth limits).\n",
"We consider a standard single-qubit system subject to dephasing noise:\n",
"\n",
"\\begin{align*}\n",
"H(t) &= \\frac{1}{2} \\alpha_1(t)\\sigma_{x} + \\frac{1}{2} \\alpha_2(t) \\sigma_{z} + \\beta(t) \\sigma_{z} , \n",
"\\end{align*}\n",
"\n",
"where $\\alpha_1(t)$ and $\\alpha_2(t)$ are real time-dependent pulse and $\\beta(t)$ is a small, slowly-varying stochastic dephasing noise process.\n",
"In this case, we enforce a maximum slew rate constraint on $\\alpha_1(t)$ and $\\alpha_2(t)$, to cap the variation between adjacent segment values."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"pycharm": {
"is_executing": true
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/100 [00:00"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Define physical constraints.\n",
"alpha_max = 2 * np.pi * 8.5e6 # rad/s\n",
"max_slew_rate = alpha_max / 10\n",
"segment_count = 128\n",
"duration = 400e-9 # s\n",
"\n",
"# Create graph object.\n",
"graph = qctrl.create_graph()\n",
"\n",
"# Create signals.\n",
"alpha_1_values = graph.anchored_difference_bounded_variables(\n",
" count=segment_count,\n",
" lower_bound=-alpha_max,\n",
" upper_bound=alpha_max,\n",
" difference_bound=max_slew_rate,\n",
")\n",
"alpha_1 = graph.pwc_signal(values=alpha_1_values, duration=duration, name=\"$\\\\alpha_1$\")\n",
"alpha_2_values = graph.anchored_difference_bounded_variables(\n",
" count=segment_count,\n",
" lower_bound=-alpha_max,\n",
" upper_bound=alpha_max,\n",
" difference_bound=max_slew_rate,\n",
")\n",
"alpha_2 = graph.pwc_signal(values=alpha_2_values, duration=duration, name=\"$\\\\alpha_2$\")\n",
"\n",
"# Create control Hamiltonian.\n",
"hamiltonian = (\n",
" alpha_1 * graph.pauli_matrix(\"X\") + alpha_2 * graph.pauli_matrix(\"Z\")\n",
") / 2\n",
"\n",
"# Create dephasing noise term.\n",
"dephasing_term = graph.pauli_matrix(\"Z\") / duration\n",
"\n",
"# Create infidelity.\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=hamiltonian,\n",
" target=graph.target(operator=graph.pauli_matrix(\"Y\")),\n",
" noise_operators=[dephasing_term],\n",
" name=\"infidelity\",\n",
")\n",
"\n",
"# Run the optimization.\n",
"result = qctrl.functions.calculate_optimization(\n",
" graph=graph,\n",
" cost_node_name=\"infidelity\",\n",
" output_node_names=[\"$\\\\alpha_1$\", \"$\\\\alpha_2$\"],\n",
" optimization_count=20,\n",
")\n",
"print(f\"Optimized cost: {result.cost:.3e}\")\n",
"\n",
"# Plot the optimized controls.\n",
"plot_controls(result.output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pulse amplitudes obtained from a band-limited optimization with bounded-slew rates for $\\alpha_1(t)$ (top) and $\\alpha_2(t)$ (bottom)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"| Package | Version |\n",
"| --------------------- | ------------ |\n",
"| Python | 3.10.8 |\n",
"| matplotlib | 3.6.3 |\n",
"| numpy | 1.24.1 |\n",
"| scipy | 1.10.0 |\n",
"| qctrl | 20.1.1 |\n",
"| qctrl-commons | 17.7.0 |\n",
"| boulder-opal-toolkits | 2.0.0-beta.3 |\n",
"| qctrl-visualizer | 4.4.0 |\n"
]
}
],
"source": [
"from qctrl.utils import print_environment_related_packages\n",
"\n",
"print_environment_related_packages()"
]
}
],
"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.10.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}