Cold ions: improving the performance of single-qubit gates

Increasing robustness against dephasing and control noise using Q-CTRL pulses

BOULDER OPAL enables you to optimize controls to achieve a target operation on your cold ion hardware. In this guide, we present control solutions for trapped ions. We obtain and verify user-specified controls that are optimized for robustness to control and frequency detuning errors. Additionally, we present experimental validation of our control methods.

Table of Contents

  1. Creating Q-CTRL robust single-ion gates
    1. Generating a primitive benchmark pulse
    2. Generating an optimized robust pulse
    3. Robustness verification with quasi-static scans
  2. Experimental validation of single-ion gates

Imports and initialization

import numpy as np
import matplotlib.pyplot as plt
from qctrlvisualizer import plot_controls

# Predefined pulse imports
from qctrlopencontrols import new_predefined_driven_control

# BOULDER OPAL features
from qctrl import Qctrl
qctrl = Qctrl()

1. Creating Q-CTRL robust single-ion gates

We obtain an optimized control $\pi$ pulse for a single qubit which is robust to both dephasing and control noise.

The total Hamiltonian of the driven single-ion quantum system is:

$$ H(t) = \frac{1 + \beta_\gamma (t)}{2}\left( \gamma(t)\sigma_- + \gamma^*(t)\sigma_+ \right) + \eta(t) \sigma_z, $$

where $\beta_\gamma(t)$ is a fractional time-dependent amplitude fluctuation process, $\eta(t)$ is a small stochastic slowly-varying dephasing noise process and $\sigma_k$ are the Pauli matrices. The time-dependent microwave drive $\gamma(t) = \Omega e^{i \phi}$ has Rabi rate $\Omega$ and phase $\phi$.

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:

  • A microwave drive control with operator $\sigma_-/2$ and complex pulse $\gamma(t)$.

The system includes two noises:

  • control noise associated with the microwave drive control,
  • additive noise with operator $\sigma_z$.

We begin setting up the controls by defining operators and constants.

# 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_x = np.array([[0., 1.],[1., 0.]], dtype=np.complex)
sigma_m = np.array([[0., 0.],[1., 0.]], dtype=np.complex)

# Define physical constants

omega_max = 2*np.pi * 6.0e3 #Hz

Generating a primitive benchmark pulse

We obtain the 'primitive' control scheme available from Q-CTRL Open Controls and described in the Q-CTRL technical documentation.

scheme ='primitive'
total_rotation = np.pi
azimuthal_angle = 0
    
# load OpenControl pulse
pulse = new_predefined_driven_control(
        rabi_rotation=total_rotation,
        azimuthal_angle=azimuthal_angle,
        maximum_rabi_rate=omega_max,
        scheme=scheme,
        name=scheme)

# Export pulse segments
drive_segments = [{'duration': d, "value": v} 
                  for d, v in zip(pulse.durations, pulse.rabi_rates * np.exp(+1j*pulse.azimuthal_angles))]
plot_controls(plt.figure(), {'$\gamma$': drive_segments})
plt.show()

The figure is a visualization of the modulus $\Omega$ and phase (or angle) $\phi$ of the primitive drive $\gamma(t)$, which is a square pulse.

Generating an optimized robust pulse

In order to optimize 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: we include this constraint in this example. All pulses have the same total duration $T$ and the same segment_count.

We set up these options below before we generate and visualize the optimized controls. A cost value close to zero indicates that the optimizer was able to find effective controls to perform the desired gate. All time-scales are arbitrary and may be modified by the user.

# Optimization inputs
segments = 18
duration = 425.0e-6 #s

# Define system and controls
system = qctrl.factories.systems.create(name="Optimized", hilbert_space_dimension=2)

drive = qctrl.factories.drive_controls.create(
    name='Microwave',
    operator=sigma_m/2,
    system=system,
)

qctrl.factories.optimum_pulses.create(
    control=drive,
    upper_bound=omega_max,
    fixed_modulus=True,
    segment_count=segments,
    duration=duration,
)

dephasing_noise = qctrl.factories.additive_noises.create(
    name='Dephasing',
    operator=sigma_z/2,
    system=system
)

amplitude_noise = qctrl.factories.control_noises.create(
    name='Amplitude',
    control=drive,
)

target = qctrl.factories.targets.create(
    unitary_operator=sigma_x,
    projection_operator=identity,
    system=system
)
optimized_system = qctrl.services.robust_optimization.run(system)
print(optimized_system.cost)
2.3548660723991e-11
optimized_controls = {'$\gamma$': control.pulse.segments for control in optimized_system.controls if control.pulse}
plot_controls(plt.figure(), optimized_controls)
plt.show()

The figure displays the modulus and angle of the optimized control.

Robustness verification with quasi-static scans

We next perform 1D quasi-static scans of control and dephasing noise, characterized by the coefficients $\beta_\gamma(t)\equiv \beta_\gamma$ and $\eta(t)\equiv\eta$. We calculate the infidelity of the operation with the given noise value. The operation is defined differently for each scan: for the control amplitude scan we repeat the $\pi$ rotation ten times, whereas for the dephasing scan the ten $\pi$ pulses are repeated with alternating direction of rotation about the Bloch sphere. These concatenated operations are chosen to amplify the effects of the respective errors from the primitive and optimized pulses.

# Quasi-static function parameters

amplitude_coefficients= np.linspace(-0.1, 0.1, 41)
dephasing_coefficients= np.linspace(-0.1, 0.1, 41)* omega_max
quasi_static_functions = {}
# Amplitude calculation: primitive pulse

repeated_drive = drive_segments*10

# Define system and controls
scheme ='primitive'
system =  qctrl.factories.systems.create(name=scheme, hilbert_space_dimension=2)

drive = qctrl.factories.drive_controls.create(
    name='Microwave',
    operator=sigma_m/2.,
    system=system
)

qctrl.factories.custom_pulses.create(
    control=drive,
    segments=repeated_drive
)

amplitude_noise = qctrl.factories.control_noises.create(
    name='Amplitude',
    control=drive
)    

dephasing_noise = qctrl.factories.additive_noises.create(
    name='Dephasing',
    operator=sigma_z/2.,
    system=system
)

target = qctrl.factories.targets.create(
    unitary_operator=identity,
    projection_operator=identity,
    system=system
)

# set up quasi-static function
quasi_static_function_amplitude = qctrl.factories.quasi_static_functions.create(
        x_noise=amplitude_noise,
        x_coefficients=amplitude_coefficients    
)

# calculate quasi-static function
quasi_static_function_a =  qctrl.services.quasi_static_functions.calculate(quasi_static_function_amplitude)
quasi_static_functions[scheme] = [quasi_static_function_a]

# Dephasing calculation: primitive pulse

drive_segments_r = [{'duration': d, "value": -v} 
                    for d, v in zip(pulse.durations, pulse.rabi_rates * np.exp(+1j*pulse.azimuthal_angles))]
alternating_drive = list(np.array([drive_segments, drive_segments_r]*5).flatten())

# Define system and controls
system =  qctrl.factories.systems.create(name=scheme, hilbert_space_dimension=2)

drive = qctrl.factories.drive_controls.create(
    name='Microwave',
    operator=sigma_m/2.,
    system=system
)

qctrl.factories.custom_pulses.create(
    control=drive,
    segments=alternating_drive
)

amplitude_noise = qctrl.factories.control_noises.create(
    name='Amplitude',
    control=drive
)    

dephasing_noise = qctrl.factories.additive_noises.create(
    name='Dephasing',
    operator=sigma_z/2.,
    system=system
)

target = qctrl.factories.targets.create(
    unitary_operator=identity,
    projection_operator=identity,
    system=system
)

# set up quasi-static functions
quasi_static_function_dephasing = qctrl.factories.quasi_static_functions.create(
        x_noise=dephasing_noise,
        x_coefficients=dephasing_coefficients)

# calculate quasi-static functions
quasi_static_function_d =  qctrl.services.quasi_static_functions.calculate(quasi_static_function_dephasing)
quasi_static_functions[scheme].append(quasi_static_function_d)

Next, we evaluate the quasi-static functions for the optimized pulse.

# Amplitude calculation: optimized pulse

opt_control = [control.pulse.segments for control in optimized_system.controls][0]
repeated_opt_control = opt_control * 10

# Define system and controls
scheme = 'optimized'
system =  qctrl.factories.systems.create(name=scheme, hilbert_space_dimension=2)

drive = qctrl.factories.drive_controls.create(
    name='Microwave',
    operator=sigma_m/2.,
    system=system
)

qctrl.factories.custom_pulses.create(
    control=drive,
    segments=repeated_opt_control
)

amplitude_noise = qctrl.factories.control_noises.create(
    name='Amplitude',
    control=drive
)    

dephasing_noise = qctrl.factories.additive_noises.create(
    name='Dephasing',
    operator=sigma_z/2.,
    system=system
)

target = qctrl.factories.targets.create(
    unitary_operator=identity,
    projection_operator=identity,
    system=system
)

# set up quasi-static function
quasi_static_function_amplitude = qctrl.factories.quasi_static_functions.create(
        x_noise=amplitude_noise,
        x_coefficients=amplitude_coefficients    
)

# calculate quasi-static functions
quasi_static_function_a =  qctrl.services.quasi_static_functions.calculate(quasi_static_function_amplitude)
quasi_static_functions[scheme] = [quasi_static_function_a]

# Dephasing calculation: optimized pulse

opt_control_r = [{'duration': d, "value": -v} 
                  for v, d in [(segment['value'], segment['duration']) 
                               for segment in [control.pulse.segments for control in optimized_system.controls][0]]]
alternating_opt_control = list(np.array([opt_control, opt_control_r]*5).flatten())

# Define system and controls
scheme = 'optimized'
system =  qctrl.factories.systems.create(name=scheme, hilbert_space_dimension=2)

drive = qctrl.factories.drive_controls.create(
    name='Microwave',
    operator=sigma_m/2.,
    system=system
)

qctrl.factories.custom_pulses.create(
    control=drive,
    segments=alternating_opt_control
)

amplitude_noise = qctrl.factories.control_noises.create(
    name='Amplitude',
    control=drive
)    

dephasing_noise = qctrl.factories.additive_noises.create(
    name='Dephasing',
    operator=sigma_z/2.,
    system=system
)

target = qctrl.factories.targets.create(
    unitary_operator=identity,
    projection_operator=identity,
    system=system
)

# set up quasi-static functions
quasi_static_function_dephasing = qctrl.factories.quasi_static_functions.create(
        x_noise=dephasing_noise,
        x_coefficients=dephasing_coefficients)

# calculate quasi-static functions
quasi_static_function_d =  qctrl.services.quasi_static_functions.calculate(quasi_static_function_dephasing)
quasi_static_functions[scheme].append(quasi_static_function_d)

schemes = ['primitive', 'optimized']
colors = ['#3273DC','#680CEA']

fig, ax = plt.subplots()
for idx, scheme in enumerate(schemes):
    quasi_static_function = quasi_static_functions[scheme][1]
    infidelities_d = [s['infidelity'] for s in quasi_static_function.sampled_points]
    plt.plot(dephasing_coefficients/omega_max, infidelities_d, label=schemes[idx], color=colors[idx])
plt.xlabel(r"Relative dephasing coefficient $\eta/\Omega_{max}$")
plt.ylabel("Infidelity")
plt.legend()
plt.show()

fig, ax = plt.subplots()
for idx, scheme in enumerate(schemes): 
    quasi_static_function = quasi_static_functions[scheme][0]
    infidelities_a = [s['infidelity'] for s in quasi_static_function.sampled_points]
    plt.plot(amplitude_coefficients, infidelities_a, label=schemes[idx], color=colors[idx])
plt.xlabel(r"Amplitude coefficient $\beta_\gamma$")
plt.ylabel("Infidelity")
plt.legend()
plt.show()

The plots demonstrate the different noise susceptibilities (in terms of operational infidelity) of the primitive and optimized controls. The optimized scheme has a much broader high-fidelity region.

2. Experimental validation of single-ion gates

Above, we have obtained robust optimized controls and performed a theoretical analysis of the robustness to control and dephasing noise.

Single-ion primitive and robust optimized controls have also been obtained and implemented in experiment. The following figure from (Ball et al., arXiv:2001.04060) displays the controls and their observed robustness in experiment. Bloch sphere representations of the controls are shown in (a) and (c), with the corresponding pulse amplitudes and phases in (b) and (d) respectively. The advantage of the optimized pulse is demonstrated in the plots of measured infidelity in (e) and (f), which is the projection of the final state onto the target state.

from IPython.display import Image
Image(filename='Single_qubit_controls.png')

The $x$-axes correspond to the amplitude coefficient $\beta_\gamma$ and the dephasing $\eta / \Omega_{\texttt{max}}$ for (e) and (f) respectively. The shading in these plots represents the net improvement in error robustness afforded by the optimized solution. As for the theoretical analysis above, the control error plot (e) is for ten iterations of the $\pi$ pulses, rotating about the $+X$ axis. The dephasing error plot is similarly for ten iterations of the $\pi$ pulses, but alternately rotating about the $+X$ and $-X$ axes.

Wiki

Comprehensive knowledge base of quantum control theory

Explore