# Silicon qubit: Robust ESR control of semiconducting spin qubits

### Dephasing-robust single-qubit Hadamard with slew rate limit

The Q-CTRL Python Package enables you to create slew-rate-limited control pulses robust to dephasing noise that achieve a target operation on your quantum hardware. In this notebook, we present an example of a Hadamard gate on a single qubit experiencing noise. This is a relevant scenario for electronic spin qubits in semiconductor devices which experience environmental dephasing noise and have slew rate limitations on their microwave pulses. Our solution produces high fidelity gates robust to dephasing noise, simultaneously taking into account any slew rate limits of your controls.

### Table of Contents

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

### Creating a robust ESR pulse

In this example we make an optimized control pulse for an single qubit which is robust to dephasing noise. The total Hamiltonian of this quantum system is:

\begin{align*} H(t) = & \frac{\nu}{2} \sigma_z + \frac{\Omega(t)}{2} \sigma_- + \frac{\Omega^*(t)}{2} \sigma_+ + \frac{\Delta(t)}{2} \sigma_z + \eta(t) \sigma_z, \end{align*}where $\nu$ is the qubit detuning, $\Omega(t)$ is a time dependent Rabi rate created by a microwave drive, $\Delta(t)$ is a time dependent clock shift, $\eta(t)$ is a small stochastic slowly varying dephasing noise process and $\sigma_k$ are the Pauli matrices.

Our aim is to find robust control pulses that achieve a Hadamard gate, i.e.

\begin{align*} U_H = \mathcal{T}\exp\left(-i\int_0^T H(t) dt \right). \end{align*}To perform this optimization with the Q-CTRL Python Package, we need to translate our description into a 'system' object that contains information about all the controls and noises to be provided to the robust control optimizer. The technical documentation explains the conventions Q-CTRL uses when splitting the total Hamiltonian of a system into a set of controls and noises.

Applying this procedure to the control part of the Hamiltonian results in three controls:

- A fixed frequency
`drift control`

with`operator`

$\nu \sigma_z/2$. - A microwave
`drive control`

with`operator`

$\sigma_-/2$ and`complex pulse`

$\Omega(t)$. - A clock
`shift control`

with`operator`

$\sigma_z/2$ and`real pulse`

$\Delta(t)$.

Followed by one noise:

- An
`additive`

`noise`

with`operator`

$\sigma_z$.

In order to optimize the control `pulses`

we first need to parametrize them, as described in Control Formats. Each pulse is treated as a piecewise-constant function made of a set of `segments`

. Each `segment`

has a `duration`

in time and a `real`

/`complex`

`value`

that is held constant.

The next step is to define a set of constraints for optimization. Each `drive control`

has an `upper_bound`

on its modulus, and it can be optionally constrained to have a `fixed_modulus`

. Each `shift control`

has an `upper_bound`

on its modulus. All pulses have the same total `duration`

$T$ and the same `segment_count`

. We also make use of `maximum_slew_rate`

, an argument passed to `qctrl.factories.optimum_pulses.create`

to specify the maximum variation allowed between two neighboring control segments. This argument will add smoothness to the pulse shape. In the case of a `drive`

, whose controls have complex values, this maximum rate represents the maximum variation of the modulus of the control. For `shifts`

, whose values are real, the maximum rate represents variation in its total value, not just the modulus.

The optimizer finally needs a `target`

operation to achieve. This is described by a `unitary_operator`

and `projection_operator`

. The projector operator is useful if you want only achieve a unitary in some subspace and are not worried about the relative phase of this unitary.

We set up these options below and present a worked example of how to generate, plot and export the optimized controls. All time-scales are arbitrary and may be modified by the user.

```
# Define standard matrices
identity = np.array([[1., 0.], [0., 1.]], dtype=np.complex)
sigma_z = np.array([[1., 0.], [0., -1.]], dtype=np.complex)
sigma_m = np.array([[0., 0.], [1., 0.]], dtype=np.complex)
hadamard = np.array([[1., 1.], [1., -1.]], dtype=np.complex) / np.sqrt(2.)
# Define physical constants
nu = 1 * np.pi # MHz
omega_max = 2*np.pi * 2e6 # Hz
delta_max = 2*np.pi * 2e6 # Hz
segments = 256
duration = 0.6e-6 # s
max_omega_slew_rate = omega_max/15.
max_delta_slew_rate = delta_max/15.
# Define system and controls
system = qctrl.factories.systems.create(
name="Hadamard", hilbert_space_dimension=2)
drive = qctrl.factories.drive_controls.create(
name='Microwave',
operator=sigma_m/2,
system=system
)
drive_pulse = qctrl.factories.optimum_pulses.create(
control=drive,
upper_bound=omega_max,
fixed_modulus=False,
segment_count=segments,
duration=duration,
maximum_slew_rate=max_omega_slew_rate,
)
shift = qctrl.factories.shift_controls.create(
name='Clock',
operator=sigma_z/2,
system=system
)
shift_pulse = qctrl.factories.optimum_pulses.create(
control=shift,
upper_bound=delta_max,
fixed_modulus=False,
segment_count=segments,
duration=duration,
maximum_slew_rate=max_delta_slew_rate,
)
drift = qctrl.factories.drift_controls.create(
name='Fixed Frequency',
operator=nu*sigma_z/2,
system=system
)
noise = qctrl.factories.additive_noises.create(
name='Dephasing',
operator=sigma_z,
system=system
)
target = qctrl.factories.targets.create(
unitary_operator=hadamard,
projection_operator=identity,
system=system
)
```

To produce controls robust to dephasing, we use the `robust_optimization`

service. The optimizer attempts to minimize the cost function, which has a term associated with the gate infidelity and another related to the zero frequency components of the filter function, as described in optimization. The optimization routine runs a number of independent optimization processes and returns the best result. For more difficult control problems, you may need to run the optimization multiple times to ensure a good result.

The optimization returns a `system`

object with the `cost`

and the corresponding optimized `controls`

.

```
optimized_system = qctrl.services.robust_optimization.run(system)
print(optimized_system.cost)
```

The optimized controls are listed in the `controls`

attribute of the optimized `system`

. The optimized controls have a `pulse`

object which contains `segments`

, a list of dictionaries featuring the `value`

and the `duration`

for the original piecewise constant control pulse. Here we will plot the optimized controls using `qctrlvisualizer`

.

In optimal control the solutions rarely exhibit structure or symmetry, meaning that it is not necessarily intuitive to interpret the solutions at intermediate times. Again, we remind you that the optimizer is simply seeking solutions for the net unitary operation performed at the conclusion of the solution, subject to constraints placed on how the state is permitted to traverse the multi-dimensional space. From the resultant graphs, you can see that the drives and shifts are within the user-defined bounds.

```
optimized_controls = {control.name: control.pulse.segments
for control in optimized_system.controls if control.pulse}
```

```
plot_controls(plt.figure(), optimized_controls)
plt.show()
```

```
from qctrlvisualizer import print_bloch_sphere_data
print_bloch_sphere_data(microwave_drive_pulse=optimized_controls['Microwave'],
clock_shift_pulse=optimized_controls['Clock'],
drift_amplitude=nu)
```

## Exporting pulse for deploying on an experiment

For exporting, the individual segment values and durations can also be reorganized in separate lists. Note: we only make one array for the duration as they are the same for all controls (in this case).

We also show how to generate and export two csv files. The microwave drive is exported in X and Y quadratures normalized to the maximum Rabi rate, and rounded to 6 significant figures. The clock shifts are normalized the maximum detuning and exported as rounded integers between -1024 and 1024. A user may simply adjust these parameters in order to comply with formatting requirements on e.g. a hardware signal generator.

```
robust_durations = np.array(
[segment['duration'] for segment in optimized_controls['Microwave']], dtype=np.float)
robust_microwave_drive = np.array(
[segment['value'] for segment in optimized_controls['Microwave']], dtype=np.complex)
robust_clock_shift = np.array(
[segment['value'] for segment in optimized_controls['Clock']], dtype=np.float)
```

```
np.savetxt(
'./resources/microwave_export_ESR.csv',
np.concatenate((np.real(robust_microwave_drive)/omega_max,
np.imag(robust_microwave_drive)/omega_max)),
fmt='%.6f', delimiter=',')
np.savetxt(
'./resources/clock_ESR.csv',
np.around(1024*robust_clock_shift/delta_max).astype(np.int),
fmt='%i')
```