{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to find time-optimal controls\n",
"**Optimizing over the duration of your controls**\n",
"\n",
"Boulder Opal provides a highly-flexible optimization engine for general-purpose gradient-based optimization,\n",
"which can be directly applied to model-based control optimization in arbitrary quantum systems.\n",
"By expressing the problem as a [graph](https://docs.q-ctrl.com/boulder-opal/topics/understanding-graphs-in-boulder-opal) which defines the cost function to be minimized, one can obtain optimized controls which achieve the desired objectives within the constraints imposed by the system.\n",
"\n",
"Boulder Opal is also capable of generating time-optimal controls by aiming to minimize the duration of the generated pulses.\n",
"This allows you to obtain not only pulses which minimize a target infidelity, but also do so in a short timescale.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary workflow\n",
"\n",
"### 1. Create an optimization variable for the optimized duration\n",
"\n",
"```python\n",
"optimizable_duration = graph.optimization_variable(1, 0, max_duration)[0]\n",
"optimizable_duration.name = \"optimizable_duration\"\n",
"```\n",
"\n",
"### 2. Scale the values in your calculation\n",
"\n",
"Set up your optimization graph similarly to [how you would otherwise](https://docs.q-ctrl.com/boulder-opal/user-guides/how-to-optimize-controls-in-arbitrary-quantum-systems-using-graphs), but multiply the energies and frequencies by the duration, and divide the times by the duration.\n",
"For example, multiply the Hamiltonian by `optimizable_duration`, and set the duration to 1.\n",
"\n",
"This will lead to the same evolution, as the dynamics driven by a Hamiltonian $H$ for a time $T$ are equivalent to those driven by a (unitless) Hamiltonian $\\tilde{H} = H T$ for a (unitless) time $\\tilde{T} = 1$.\n",
"\n",
"### 3. Define the optimization cost\n",
"\n",
"As now your optimization has two targets (minimizing infidelity and duration), you need to combine them in a single node. One way of doing this is with a weighted sum of both items, for instance,\n",
"\n",
"```python\n",
"cost = infidelity + 0.1 * optimizable_duration / max_duration\n",
"cost.name = \"cost\"\n",
"```\n",
"\n",
"You can alter the incentives of the optimizer to minimize the duration or the infidelity by changing the weight of each term in the sum (or using a different form for the cost function).\n",
"\n",
"### 4. Execute graph-based optimization\n",
"\n",
"You can now run the optimization as usual using, for instance `qctrl.functions.calculate_optimization`.\n",
"Note that some of the output graph values might need to be rescaled back by a factor of `optimizable_duration` (or its inverse)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Worked example: Optimal control of a single qubit\n",
"\n",
"This example shows how to optimize a Hamiltonian with multiple controls.\n",
"Specifically, consider a single-qubit system represented by the following Hamiltonian:\n",
"$$\n",
"H(t) = \\frac{1 + \\beta(t)}{2} \\left[\\gamma(t)\\sigma_{-} + \\gamma^*(t)\\sigma_{+}\\right] + \\frac{\\alpha(t)}{2} \\sigma_{z} \\, , \n",
"$$\n",
"where $\\gamma(t)$ and $\\alpha(t)$ are, respectively, complex and real time-dependent pulses, $\\sigma_{\\pm}$ are the qubit ladder operators, and $\\sigma_{z}$ is the Pauli-Z operator.\n",
"$\\beta(t)$ is a small, slowly-varying amplitude noise acting on $\\gamma(t)$.\n",
"\n",
"We will obtain optimal pulses for $\\gamma(t)$ and $\\alpha(t)$, robust to the amplitude noise $\\beta(t)$, to achieve a target Y-gate operation.\n",
"Moreover, we will aim to obtain a pulse that is as short as possible by adding a penalty to the cost function that increases with the pulse duration."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"is_executing": true,
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from qctrlvisualizer import plot_controls\n",
"\n",
"from qctrl import Qctrl\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": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Define physical constants.\n",
"gamma_max = 2 * np.pi * 3e5 # rad/s\n",
"alpha_max = 2 * np.pi * 1e5 # rad/s\n",
"cutoff_frequency = 5e6 # Hz\n",
"segment_count = 16\n",
"max_duration = 10e-6 # s\n",
"\n",
"# Create the graph describing the system.\n",
"graph = qctrl.create_graph()\n",
"\n",
"# Define optimizable duration.\n",
"duration = graph.optimization_variable(1, 0, max_duration)[0]\n",
"duration.name = \"duration\"\n",
"\n",
"# Create a optimizable complex-valued piecewise-constant (PWC) signal.\n",
"rough_gamma = graph.utils.complex_optimizable_pwc_signal(\n",
" segment_count=segment_count, maximum=gamma_max, duration=1.0\n",
")\n",
"# Smooth the signal.\n",
"gamma = graph.utils.filter_and_resample_pwc(\n",
" pwc=rough_gamma,\n",
" segment_count=256,\n",
" cutoff_frequency=cutoff_frequency * duration,\n",
" name=r\"$\\gamma$\",\n",
")\n",
"\n",
"# Create a PWC operator representing the drive term.\n",
"drive = graph.hermitian_part(gamma * graph.pauli_matrix(\"M\"))\n",
"\n",
"# Create an optimizable real-valued PWC signal.\n",
"rough_alpha = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=segment_count, minimum=-alpha_max, maximum=alpha_max, duration=1.0\n",
")\n",
"# Smooth the signal.\n",
"alpha = graph.utils.filter_and_resample_pwc(\n",
" pwc=rough_alpha,\n",
" segment_count=256,\n",
" cutoff_frequency=cutoff_frequency * duration,\n",
" name=r\"$\\alpha$\",\n",
")\n",
"# Create a PWC operator representing the clock shift term.\n",
"shift = alpha * graph.pauli_matrix(\"Z\") / 2\n",
"\n",
"# Define the total Hamiltonian.\n",
"hamiltonian = drive + shift\n",
"\n",
"# Create the infidelity.\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=hamiltonian * duration,\n",
" target=graph.target(graph.pauli_matrix(\"Y\")),\n",
" noise_operators=[drive * duration],\n",
" name=\"infidelity\",\n",
")\n",
"\n",
"cost = infidelity + 0.1 * duration / max_duration\n",
"cost.name = \"cost\"\n",
"\n",
"# Run the optimization.\n",
"result = qctrl.functions.calculate_optimization(\n",
" graph=graph,\n",
" cost_node_name=\"cost\",\n",
" output_node_names=[r\"$\\alpha$\", r\"$\\gamma$\", \"duration\", \"infidelity\"],\n",
")\n",
"print(f\"Optimized cost:\\t\\t{result.cost:.3e}\")\n",
"print(f\"Optimized infidelity:\\t{result.output['infidelity']['value']:.3e}\")\n",
"optimized_duration = result.output[\"duration\"][\"value\"]\n",
"print(f\"Optimized duration:\\t{optimized_duration:.3e}\")\n",
"\n",
"# Plot the optimized controls, scaling back the durations.\n",
"gamma_durations, gamma_values, _ = qctrl.utils.pwc_pairs_to_arrays(\n",
" result.output[r\"$\\gamma$\"]\n",
")\n",
"gamma_durations = gamma_durations * optimized_duration\n",
"\n",
"alpha_durations, alpha_values, _ = qctrl.utils.pwc_pairs_to_arrays(\n",
" result.output[r\"$\\alpha$\"]\n",
")\n",
"alpha_durations = alpha_durations * optimized_duration\n",
"\n",
"plot_controls(\n",
" {\n",
" r\"$\\gamma$\": {\"durations\": gamma_durations, \"values\": gamma_values},\n",
" r\"$\\alpha$\": {\"durations\": alpha_durations, \"values\": alpha_values},\n",
" },\n",
" polar=False,\n",
")\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"| Package | Version |\n",
"| ---------------- | ------- |\n",
"| Python | 3.9.12 |\n",
"| matplotlib | 3.5.1 |\n",
"| numpy | 1.23.3 |\n",
"| scipy | 1.9.1 |\n",
"| qctrl | 19.5.0 |\n",
"| qctrl-commons | 17.3.0 |\n",
"| qctrl-toolkit | 1.9.0 |\n",
"| qctrl-visualizer | 4.0.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.9.12"
}
},
"nbformat": 4,
"nbformat_minor": 4
}