# How to optimize controls in arbitrary quantum systems using graphs

**Highly-configurable non-linear optimization framework for quantum control**

Boulder Opal provides a highly-flexible optimization engine for general-purpose gradient-based optimization. It can be directly applied to model-based control optimization for arbitrary-dimensional quantum systems.

The optimization engine from Boulder Opal allows the user to express their system Hamiltonians as almost-arbitrary functions of the controllable parameters. The underlying structure of this map is a graph, which defines the cost function and can be efficiently evaluated and differentiated. The resulting optimized controls thus achieve the desired objectives within the constraints imposed by the user-defined Hamiltonian structure.

The example in this user guide illustrates how to optimize multiple controls under different constraints in a single system. For a step-by-step description of how to create a robust optimization with multiple controls, see our tutorial Design robust single-qubit gates using computational graphs.

## Summary optimization workflow

### 1. Define the computational graph

The Boulder Opal optimization engine expresses all optimization problems as data flow graphs, which you can create with the `qctrl.create_graph`

function.
The methods of the graph object allow you to represent the mathematical structure of the problem that you want to solve.

For an optimization, a typical workflow is to:

- Create "signals", or scalar-valued functions of time, which typically represent control pulses.
- Create "operators", or matrix-valued functions of time, by modulating constant operators by signals. These typically represent terms of a Hamiltonian.
- Combine the operators into a single Hamiltonian operator.
- Calculate the optimization cost function (typically an infidelity) from the Hamiltonian.

### 2. Execute graph-based optimization

You can calculate an optimization from an input graph using the `qctrl.functions.calculate_optimization`

function.
Provide the name of the node of the graph that represents the cost, and this function will return the optimized value of the output nodes that you requested.

## Worked example: Optimal control of a single qubit

This example shows how to optimize a Hamiltonian with multiple controls. Specifically, consider a single-qubit system represented by the following Hamiltonian:

\begin{align*} H(t) &= \frac{\nu}{2} \sigma_{z} + \frac{1}{2}\left[\gamma(t)\sigma_{-} + \gamma^*(t)\sigma_{+}\right] + \frac{\alpha(t)}{2} \sigma_{z} \,, \end{align*}where $\nu$ is the qubit detuning, $\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.

The functions of time $\gamma(t)$ and $\alpha(t)$ are not predetermined, and instead are optimized by Boulder Opal in order to achieve the target operation, which in this case is a Y-gate.

```
import matplotlib.pyplot as plt
import numpy as np
from qctrlvisualizer import plot_controls
from qctrl import Qctrl
# Starting a session with the API.
qctrl = Qctrl()
```

```
# Define standard matrices.
sigma_y = np.array([[0, -1j], [1j, 0]])
sigma_z = np.array([[1, 0], [0, -1]])
sigma_m = np.array([[0, 1], [0, 0]])
# Define physical constants.
nu = 2 * np.pi * 5e5 # Hz
gamma_max = 2 * np.pi * 3e5 # Hz
alpha_max = 2 * np.pi * 1e5 # Hz
cut_off_frequency = 5e6 # Hz
segment_count = 50
duration = 10e-6 # s
# Crate the graph describing the system.
graph = qctrl.create_graph()
# Create the time-independent detuning term.
detuning = nu * sigma_z / 2
# Create a optimizable complex-valued piecewise-constant (PWC) signal.
rough_gamma = graph.complex_pwc_signal(
moduli=graph.optimization_variable(segment_count, 0, gamma_max),
phases=graph.optimization_variable(
segment_count, 0, 2 * np.pi, is_upper_unbounded=True, is_lower_unbounded=True
),
duration=duration,
)
# Smooth the signal.
kernel = graph.sinc_convolution_kernel(cut_off_frequency)
gamma = graph.discretize_stf(
graph.convolve_pwc(rough_gamma, kernel), duration, 256, name=r"$\gamma$"
)
# Create a PWC operator representing the drive term.
drive = graph.pwc_operator_hermitian_part(gamma * sigma_m)
# Create an optimizable real-valued PWC signal.
rough_alpha = graph.pwc_signal(
values=graph.optimization_variable(segment_count, -alpha_max, alpha_max),
duration=duration,
)
# Smooth the signal.
alpha = graph.discretize_stf(
graph.convolve_pwc(rough_alpha, kernel), duration, 256, name=r"$\alpha$"
)
# Create a PWC operator representing the clock shift term.
shift = alpha * sigma_z / 2
# Define the total Hamiltonian and the target operation.
hamiltonian = detuning + drive + shift
target = graph.target(sigma_y)
# Create the infidelity.
infidelity = graph.infidelity_pwc(hamiltonian, target, name="infidelity")
# Run the optimization.
result = qctrl.functions.calculate_optimization(
graph=graph,
cost_node_name="infidelity",
output_node_names=[r"$\alpha$", r"$\gamma$"],
)
print(f"Optimized cost:\t{result.cost:.3e}")
# Plot the optimized controls.
plot_controls(plt.figure(), controls=result.output)
plt.show()
```