{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to create leakage-robust single-qubit gates\n",
"**Design pulses that minimize leakage to unwanted states**\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 allows one to define the physical problem in a higher dimensional quantum system while retaining the ability to optimize for a target operation in a particular subspace. In this notebook we demonstrate the optimization of a single-qubit gate in a superconducting transmon system with three levels.\n",
"\n",
"## Summary workflow\n",
"### 1. Define optimization subspace in the graph\n",
"The flexible Boulder Opal optimization engine expresses all optimization problems as [data flow graphs](https://docs.q-ctrl.com/boulder-opal/user-guides/how-to-represent-quantum-systems-using-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). For an optimal control problem, the cost is typically given by the gate infidelity with respect to a user-defined `target` operation. You can restrict this target operation to a subspace of your full quantum system by multiplying it by the appropriate subspace projector. This ensures that the projected target treats population outside the desired subspace as leakage error. For example, the qubit projector in a three-dimensional space is \n",
"$$ P_{\\rm{qubit}} = \\begin{pmatrix}\n",
"1 & 0 & 0\\\\\n",
"0 & 1 & 0 \\\\\n",
"0 & 0 & 0 \n",
"\\end{pmatrix} .$$\n",
"\n",
"The target operator is defined using the `graph.target` graph operation:\n",
"```python\n",
"target_operator = graph.target(\n",
" full_target.dot(qubit_projector), filter_function_projector=qubit_projector\n",
")\n",
"```\n",
"where `full_target` is your original target operator in the full space. The `filter_function_projector` parameter is optional and should be used when you also want to add [robustness to noise](https://docs.q-ctrl.com/boulder-opal/user-guides/how-to-create-dephasing-and-amplitude-robust-single-qubit-gates) in that subspace. \n",
"\n",
"Once this is defined, we calculate the infidelity by passing this operator to the `graph.infidelity_pwc` graph operation\n",
"```python\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=hamiltonian, target=target_operator, name=\"infidelity\"\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. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Optimize single-qubit Hadamard in a qutrit system\n",
"\n",
"In this example, we consider a qutrit system in which we effect a single-qubit gate robust against control noise while simultaneously minimizing leakage out of the computational subspace. The system is described by the following Hamiltonian:\n",
"$$\n",
"H(t) = \\frac{\\chi}{2} (a^\\dagger)^2 a^2 + (1+\\beta(t))\\left(\\gamma(t) a + \\gamma^*(t) a^\\dagger \\right) + \\frac{\\alpha(t)}{2} a^\\dagger a , \n",
"$$\n",
"where $\\chi$ is the anharmonicity, $\\gamma(t)$ and $\\alpha(t)$ are, respectively, complex and real time-dependent pulses, $\\beta(t)$ is a small, slowly-varying stochastic amplitude noise process, and $a = |0 \\rangle \\langle 1 | + \\sqrt{2} |1 \\rangle \\langle 2 |$."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"is_executing": true
}
},
"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": "markdown",
"metadata": {},
"source": [
"We start by defining the operators and physical parameters:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"pycharm": {
"is_executing": true
}
},
"outputs": [],
"source": [
"# Define target and projector matrices\n",
"hadamard = np.array(\n",
" [[1.0, 1.0, 0], [1.0, -1.0, 0], [0, 0, np.sqrt(2)]], dtype=complex\n",
") / np.sqrt(2)\n",
"qubit_projector = np.pad(np.eye(2), ((0, 1), (0, 1)), mode=\"constant\")\n",
"\n",
"# Define physical constants and parameters\n",
"transmon_levels = 3\n",
"chi = 2 * np.pi * -300.0 * 1e6 # Hz\n",
"gamma_max = 2 * np.pi * 30e6 # Hz\n",
"alpha_max = 2 * np.pi * 30e6 # Hz\n",
"segment_count = 50\n",
"duration = 100e-9 # s\n",
"sinc_cutoff_frequency = 300e6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Below we show how to create a data flow graph for optimizing the system described above. Note that we are using a filter to produce smooth pulses as explained in our [How to add smoothing and band-limits to optimized controls](https://docs.q-ctrl.com/boulder-opal/user-guides/how-to-add-smoothing-and-band-limits-to-optimized-controls) user guide. "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"pycharm": {
"is_executing": true
}
},
"outputs": [],
"source": [
"# Create graph object\n",
"graph = qctrl.create_graph()\n",
"\n",
"# Define standard matrices\n",
"a = graph.annihilation_operator(transmon_levels)\n",
"ad = graph.creation_operator(transmon_levels)\n",
"ada = graph.number_operator(transmon_levels)\n",
"ad2a2 = ada @ ada - ada\n",
"\n",
"# Create the complex optimizable gamma(t) signal\n",
"gamma = graph.utils.complex_optimizable_pwc_signal(\n",
" segment_count=segment_count, maximum=gamma_max, duration=duration\n",
")\n",
"\n",
"# Create the optimizable alpha(t) signal\n",
"alpha = graph.utils.real_optimizable_pwc_signal(\n",
" segment_count=segment_count,\n",
" minimum=-alpha_max,\n",
" maximum=alpha_max,\n",
" duration=duration,\n",
")\n",
"\n",
"# Create filtered signals\n",
"rediscretized_gamma = graph.utils.filter_and_resample_pwc(\n",
" pwc=gamma, cutoff_frequency=sinc_cutoff_frequency, segment_count=256, name=\"gamma\"\n",
")\n",
"rediscretized_alpha = graph.utils.filter_and_resample_pwc(\n",
" pwc=alpha, cutoff_frequency=sinc_cutoff_frequency, segment_count=256, name=\"alpha\"\n",
")\n",
"\n",
"# Create Hamiltonian terms\n",
"anharmonicity = ad2a2 * chi / 2\n",
"drive = graph.hermitian_part(2 * rediscretized_gamma * a)\n",
"shift = rediscretized_alpha * ada / 2\n",
"hamiltonian = anharmonicity + drive + shift\n",
"\n",
"# Create the target operator in the qubit subspace\n",
"target_operator = graph.target(\n",
" hadamard.dot(qubit_projector), filter_function_projector=qubit_projector\n",
")\n",
"\n",
"infidelity = graph.infidelity_pwc(\n",
" hamiltonian=hamiltonian,\n",
" target=target_operator,\n",
" noise_operators=[drive],\n",
" name=\"infidelity\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now run the optimization and plot the resulting control pulses:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"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": [
"# Run the optimization\n",
"optimization_result = qctrl.functions.calculate_optimization(\n",
" cost_node_name=\"infidelity\",\n",
" output_node_names=[\"alpha\", \"gamma\"],\n",
" graph=graph,\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",
" \"$\\\\alpha$\": optimization_result.output[\"alpha\"],\n",
" \"$\\\\gamma$\": optimization_result.output[\"gamma\"],\n",
" },\n",
" polar=False,\n",
")\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"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
}