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

  1. Creating a robust ESR pulse
  2. Exporting pulse for deploying on an experiment

Imports and initialization

# 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)
4.49642066483009e-11

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)
print_bloch_sphere_data not available on this version.

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')

Wiki

Comprehensive knowledge base of quantum control theory

Explore