{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to optimize controls with nonlinear dependences\n",
"**Incorporate nonlinear Hamiltonian dependences on control signals**\n",
"\n",
"Boulder Opal exposes a highly-flexible [optimization engine](https://docs.q-ctrl.com/boulder-opal/references/qctrl/Functions/calculate_optimization.html) for general-purpose gradient-based optimization. It can be directly applied to model-based control optimization for arbitrary-dimensional quantum systems. In some cases the relationship between a control parameter and Hamiltonian term is nonlinear, presenting a major challenge for standard optimization routines. \n",
"\n",
"In this notebook we demonstrate how to simply incorporate arbitrary nonlinear relationships between control parameters such that it is possible to directly perform an optimization which returns proper lab-frame control signals to generate and output.\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).\n",
"\n",
"## Summary workflow\n",
"### 1. Define nonlinearity in computational graph\n",
"The flexible Boulder Opal optimization engine expresses all optimization problems as data flow graphs, 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). Typical costs involve minimization of an infidelity metric captured using the `graph.infidelity_pwc` graph operation.\n",
"\n",
"Expressing non-linear dependence between control parameters and Hamiltonians is achieved by performing standard arithmetical operations on the control parameters prior to constructing signals which are employed in the graph. For instance:\n",
"```python\n",
"signal = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=segment_count,\n",
" minimum=signal_min,\n",
" maximum=signal_max,\n",
" duration=duration,\n",
" name=\"signal\"\n",
" )\n",
"signal_squared = signal ** 2 / signal_max\n",
"signal_squared.name = \"signal_squared\"\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. The cost, the outputs, and the graph must be provided. The function returns the results of the optimization. Note that this example code block uses naming that should be replaced with the naming used in your graph."
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## Example: Optimized controls with a nonlinear relationship to a single-qubit Hamiltonian\n",
"\n",
"We present an example showing how a system with non-linear dependence between controls and Hamiltonian may be optimized. We consider an artificial single-qubit system represented by the following Hamiltonian:\n",
"$$\n",
"H(t) = \\alpha_1(t)\\sigma_x + \\frac{\\alpha_1(t)^2}{\\alpha_{\\text{max}}}\\sigma_y + \\alpha_2(t)\\sigma_z ,\n",
"$$\n",
"where $\\alpha_1(t)$ and $\\alpha_2(t)$ are real time-dependent pulses, and we note the non-linear dependence of the Hamiltonian on $\\alpha_1(t)$. \n",
"\n",
"While here we have chosen a simple artificial non-linearity for the sake of convenience and brevity, the approach we demonstrate extends trivially to far more complex, physically-relevant situations.\n",
"\n",
"Expressing non-linear dependence between control parameters and Hamiltonians is achieved by performing standard arithmetical operations on the control parameters prior to constructing signals. In this case the operations are very simple—we simply square and re-scale the parameters describing the $\\alpha_1(t)$ values—but in general any combination of supported primitive operations may be used."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"is_executing": true
}
},
"outputs": [],
"source": [
"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": {
"pycharm": {
"is_executing": true,
"name": "#%%\n"
}
},
"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 * 6e6 # Hz\n",
"segment_count = 40\n",
"duration = 200e-9 # s\n",
"\n",
"# Create graph object\n",
"graph = qctrl.create_graph()\n",
"\n",
"# Create the alpha_1(t) signal\n",
"alpha_1 = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=segment_count,\n",
" minimum=-alpha_max,\n",
" maximum=alpha_max,\n",
" duration=duration,\n",
" name=\"alpha_1\",\n",
")\n",
"\n",
"# Apply a non-linear transformation to create the alpha_1_squared(t) signal\n",
"alpha_1_squared = alpha_1**2 / alpha_max\n",
"alpha_1_squared.name = \"alpha_1_squared\"\n",
"\n",
"# Create the alpha_2(t) signal\n",
"alpha_2 = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=segment_count,\n",
" minimum=-alpha_max,\n",
" maximum=alpha_max,\n",
" duration=duration,\n",
" name=\"alpha_2\",\n",
")\n",
"\n",
"# Create Hamiltonian terms\n",
"x_term = alpha_1 * graph.pauli_matrix(\"X\")\n",
"y_term = alpha_1_squared * graph.pauli_matrix(\"Y\")\n",
"z_term = alpha_2 * graph.pauli_matrix(\"Z\")\n",
"\n",
"target_operator = graph.target(operator=graph.pauli_matrix(\"X\"))\n",
"\n",
"# Create infidelity\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=x_term + y_term + z_term, target=target_operator, name=\"infidelity\"\n",
")\n",
"\n",
"# Run the optimization\n",
"optimization_result = qctrl.functions.calculate_optimization(\n",
" graph=graph,\n",
" cost_node_name=\"infidelity\",\n",
" output_node_names=[\"alpha_1\", \"alpha_1_squared\", \"alpha_2\"],\n",
" optimization_count=4,\n",
")\n",
"\n",
"print(f\"\\nOptimized cost:\\t{optimization_result.cost:.3e}\")\n",
"\n",
"# Plot the optimized controls\n",
"plot_controls(\n",
" {\n",
" r\"$\\alpha_1$\": optimization_result.output[\"alpha_1\"],\n",
" r\"$\\alpha_1^2$\": optimization_result.output[\"alpha_1_squared\"],\n",
" r\"$\\alpha_2$\": optimization_result.output[\"alpha_2\"],\n",
" }\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"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
}