Single spin Part 2: Gradient descent gate optimization

In this notebook, we show how to use ParaQeet to optimize a single-qubit gate, specifically an \(X\)-gate.

import matplotlib.pyplot as plt
import numpy as np

from paraqeet.measurement.unitary_fidelity import UnitaryFidelity
from paraqeet.model.closed_system import ClosedSystem
from paraqeet.model.drive_operator import DriveOperator
from paraqeet.model.qubit import Qubit
from paraqeet.optimization_map import OptimizationMap
from paraqeet.optimizers.scipy_optimizer import ScipyOptimizer
from paraqeet.optimizers.scipy_optimizer_gradient import ScipyOptimizerGradient
from paraqeet.propagation.scipy_expm_goat import ScipyExpmGOAT
from paraqeet.quantity import Quantity
from paraqeet.signal.envelopes import ConstantEnvelope
from paraqeet.signal.iq_mixer import IQMixer

System Setup

We first set up the qubit system we want to control. We set the qubit frequency \(\omega_q / 2 \pi\) to be \(4.327884\) GHz and define the Hamiltonian as

\[H(t)=H_\text{drift}+H_c(t)= \frac{\omega_q}{2} \sigma_z + \Omega(t)\sigma_x,\]

where \(\Omega(t)\) will be supplied by the generator.

freq = 4.327884e9 * 2 * np.pi

# drive = DriveOperator(gen, is_longitudinal=False)
controlled_qubit = Qubit(
    frequency=Quantity(
        freq,
        min_value=freq / 4,
        max_value=freq,
        unit="Hz",
        name="Qubit frequency",
    ),
    drives=[],
)

For signal generation, we define a simple cosine shaped tone generator \(A \cos(\omega t)\)

t_simu = 10e-9
tone = ConstantEnvelope()
# In this notebook we set the parameter t_final equal to the simulation
# time, but it is not strictly necessary as long as t_final is larger
# than t_simu (see the notebook 02A_Single_qubit_state_preparation.ipynb)
tone.t_final.set_value(t_simu)
gen = IQMixer(envelopes=[tone])

We can inspect the parameters with

params_tone = tone.get_parameters()
print(params_tone)
params_gen = gen.get_parameters()
print(params_gen)
[Amplitude: 24.7 MHz x 2pi, t_final: 10 ns]
[Amplitude: 24.7 MHz x 2pi, t_final: 10 ns, lo_freq: 4.8 GHz x 2pi, Phase: 0 rad]

In this notebook, we would like to optimize the amplitude Amplitude and frequency lo_freq if the drive. We add a drive on the qubit.

drive = DriveOperator(gen, is_longitudinal=False)
controlled_qubit.drives = [drive]
model = ClosedSystem(controlled_qubit)

In this notebook, we would like to optimize the amplitude Amplitude and frequency lo_freq if the drive. We add a drive on the qubit.

drive = DriveOperator(gen, is_longitudinal=False)
controlled_qubit.drives = [drive]
model = ClosedSystem(controlled_qubit)

Textbook values for implementing an \(X\) rotation on this system at a time \(T\) would be \(\omega=\omega_q\) and \(A=\pi/T\). We use some offset from these values as initial guess to demonstrate the optimization procedure.

params_gen[0].set_value(0.5 * np.pi / t_simu)
params_gen[2].set_value(1.01 * freq)

We select a propagation method, piecewise constant exponentation, and configure an \(X\)-gate as a target gate. Also we initialize the identity at time \(0\).

prop = ScipyExpmGOAT(model, resolution=500e9)
times = np.array([0.0, t_simu])

pauli_x = np.array([[0.0, 1.0], [1.0, 0.0]])
pauli_y = np.array([[0.0, -1.0j], [1.0j, 0.0]])
pauli_z = np.array([[1.0, 0.0], [0.0, -1.0]])

prop.set_initial_state(np.identity(2))
gate_fid = UnitaryFidelity(
    propagation=prop,
    gate=pauli_x,
)
from plotting import plot_signal_and_dynamics

ts = np.linspace(0.0, t_simu, 501)
plot_signal_and_dynamics(gen, prop, ts, state_labels=[r"$|0\rangle$", r"$|1\rangle$"]);
../_images/02B_Single_qubit_gate_16_0.png

As expected, we get a partial transfer and a low fidelity.

print(f"Gate fidelity: {gate_fid.measure(times)}")
Gate fidelity: 0.09555411208408474

We define an optimizer and link our fidelity measure as a goal function and the parameters of the cosine tone and optimize just amplitude and frequency, as in the state transfer example.

optmap = OptimizationMap()
optmap.add(gen, [params_gen[0], params_gen[2]])
opt = ScipyOptimizerGradient(gate_fid, optimization_map=optmap)
opt.optimize(times)
{'status': 1, 'value': 0.8037201693294322, 'iterations': 17, 'message': 'CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL'}
plot_signal_and_dynamics(gen, prop, ts, state_labels=[r"$|0\rangle$", r"$|1\rangle$"]);
../_images/02B_Single_qubit_gate_22_0.png

Dynamics of Pauli operators

Here, we end up with an unsatisfactory result. For optimizing gate fidelities, looking at state populations does not give enough information to identify the problem.

def expecation_value(op, states):
    """Get the expectation value across states."""
    ex = []
    for state in states:
        ex.append(np.real(state.conj() @ op @ state.T))
    return ex
def plot_pauli():
    """Plot the Pauli operators."""
    ts = np.linspace(0, t_simu, 1001)
    states = prop.propagate(ts)
    sig = gen.get_value(ts) / 1e6 / (2 * np.pi)

    fig, ax = plt.subplots(2, figsize=(4, 4), sharex=True)
    ax[0].plot(ts / 1e-9, sig)
    ax[0].set_ylabel(r"Field [MHz / $2\pi$]")
    ax[0].grid(True, linestyle=(1, (1, 5)), linewidth=1)

    ax[1].plot(ts / 1e-9, expecation_value(pauli_x, states[:, :, 0]))
    ax[1].plot(ts / 1e-9, expecation_value(pauli_y, states[:, :, 0]))
    ax[1].plot(ts / 1e-9, expecation_value(pauli_z, states[:, :, 0]))
    ax[1].set_ylabel(r"Expectation value $\langle\hat\sigma_i\rangle$")
    ax[1].grid(True, linestyle=(1, (1, 5)), linewidth=1)

    ax[-1].set_xlabel("Time [ns]")
    ax[1].legend(["X", "Y", "Z"])
    return fig, ax


plot_pauli()
(<Figure size 500x500 with 2 Axes>,
 array([<Axes: ylabel='Field [MHz / $2\pi$]'>,
        <Axes: xlabel='Time [ns]', ylabel='Expectation value $\langle\hat\sigma_i\rangle$'>],
       dtype=object))
../_images/02B_Single_qubit_gate_25_1.png

Instead, we look at the expecation values of the three Pauli operators and observe that the qubit is rotating at its eigenfrequency along the Z-axis. We can mitigate this problem by allowing the rotation axis of our drive to shift and include the phase parameter in the optimization.

optmap = OptimizationMap()
optmap.add(tone, [params_gen[0], params_gen[2], params_gen[3]])
opt = ScipyOptimizer(gate_fid, optimization_map=optmap)
params_gen[0].set_value(0.5 * np.pi / t_simu)
params_gen[2].set_value(1.01 * freq)
opt.optimize(times)
{'status': 1, 'value': 1.0475176281943277e-11, 'iterations': 108, 'message': 'CONVERGENCE: RELATIVE REDUCTION OF F <= FACTR*EPSMCH'}
plot_pauli()
(<Figure size 500x500 with 2 Axes>,
 array([<Axes: ylabel='Field [MHz / $2\pi$]'>,
        <Axes: xlabel='Time [ns]', ylabel='Expectation value $\langle\hat\sigma_i\rangle$'>],
       dtype=object))
../_images/02B_Single_qubit_gate_29_1.png