DRAG Correction of a Gaussian Pulse#

First, we make the necessary imports.

import numpy as np

from paraqeet.eom.schroedinger_equation import SchroedingerEquation
from paraqeet.hamiltonian.drive import Drive
from paraqeet.hamiltonian.transmon import TransmonHamiltonian
from paraqeet.measurement.unitary_fidelity import UnitaryFidelity
from paraqeet.optimization_map import OptimizationMap
from paraqeet.optimizers.scipy_optimizer import ScipyOptimizer
from paraqeet.propagation import GOAT, Expm
from paraqeet.quantity import Quantity
from paraqeet.signal.envelopes import GaussEnvelope
from paraqeet.signal.iq_mixer import IQMixer
from paraqeet.signal.signal import DRAGMixer

1. Define the Gaussian Tone and put into the DRAGMixer#

The GaussTone explicitly allows for the evaluation of an envelope signal and its time derivative which is then used to calculate the DRAG (Motzoi et al., 2009) [12] corrected signal in the DRAGMixer

t_final = 20e-9
env_tone = GaussEnvelope()
env_tone.t_final.set_value(t_final)
drag_tone = DRAGMixer(envelopes=env_tone)
drag_tone._envs[0]._delta
Delta: -1.26 GHz
gen = IQMixer(envelopes=[drag_tone])
freq = 4.8e9 * 2 * np.pi
anhar = -200e6 * 2 * np.pi

num_levels = 3
transmon_hamiltonian = TransmonHamiltonian(
    frequency=Quantity(
        freq,
        min_value=np.array(freq / 4),
        max_value=np.array(freq * 1.2),
        unit="Hz",
        name="Qubit frequency",
    ),
    anharmonicity=Quantity(
        anhar,
        min_value=np.array(anhar * 1.2),
        max_value=np.array(anhar * 0.8),
        unit="Hz",
        name="Qubit anharmonicity",
    ),
    num_levels=num_levels,
    drives=[],
)

drive_op = transmon_hamiltonian.annihilation_op + (transmon_hamiltonian.annihilation_op).conj().T
drive = Drive(drive_op, gen)
transmon_hamiltonian.drives = [drive]

model = SchroedingerEquation(
    hamiltonian_func=transmon_hamiltonian.get_value,
    hamiltonian_gradient_func=transmon_hamiltonian.get_gradient,
)

params = gen.get_parameters()

prop = GOAT(
    Expm(eom_func=model.get_value, resolution=100e9, initial_state=np.eye(num_levels)),
    eom_gradient_func=model.get_gradient,
)
params
[Amplitude: 24.7 MHz x 2pi,
 t_final: 20 ns,
 Delta: -1.26 GHz,
 lo_freq: 4.8 GHz x 2pi,
 Phase: 0 rad]
params[0].set_value(3e8)
params[2].set_value(2 * anhar)
gen.get_parameters()
[Amplitude: 47.7 MHz x 2pi,
 t_final: 20 ns,
 Delta: -2.51 GHz,
 lo_freq: 4.8 GHz x 2pi,
 Phase: 0 rad]

2. Set up the ideal reference matrix to compare the pulse’s result to.#

def rx(theta) -> np.ndarray:
    """Get the ideal representation of a rx rotation of angle theta."""
    return np.array(
        [
            [np.cos(theta / 2), -1j * np.sin(theta / 2), 0],
            [-1j * np.sin(theta / 2), np.cos(theta / 2), 0],
            [0, 0, 1],
        ],
        dtype=np.complex128,
    )

Set up the measure that is optimized. In this case, the gate fidelity between the propagator resulting from the pulse simulation and the ideal reference defined above is used.

gate_fid = UnitaryFidelity(
    propagation_func=prop.get_value,
    propagation_gradient_func=prop.get_gradient,
    gate=rx(np.pi / 2),
)

Plot initial pulse shape and population transfer. Target is the full population transfer, i.e., an X-gate.

from plotting import plot_signal_and_dynamics

ts = np.linspace(0.0, t_final, 1001)
plot_signal_and_dynamics(gen, prop, ts, state_labels=[r"$|0\rangle$", r"$|1\rangle$"])
array([<Axes: ylabel='Amplitude n[MHz / $2\pi$]'>,
       <Axes: xlabel='Time [ns]', ylabel='Population'>], dtype=object)
../_images/03_DRAG_pulses_15_1.png

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

times = np.array([0.0, t_final])
gate_fid.get_value(times)
Array(0.97379736, dtype=float64)

3. Optimization#

We define an optimizer and link our fidelity measure as a goal function and the parameters of the cosine tone.

optmap = OptimizationMap()
selected_params = []
for i in [0, 2, 3, 4]:
    selected_params.append(params[i])
optmap.add(gen, selected_params)
opt = ScipyOptimizer(measure_func=gate_fid.get_value, optimization_map=optmap)
opt.optimize(times)
{'status': 1, 'value': 0.005760910023328569, 'iterations': 90, 'message': 'CONVERGENCE: RELATIVE REDUCTION OF F <= FACTR*EPSMCH'}

Print all parameters that were optimized.

print("{: >15}: {: >20} {: >20} {: >20}".format("Name", "Value", "Min", "Max"))
print("-" * 80)
for par in selected_params:
    print(
        f"{par.get_name(): >15}: {par.get_value()[0]: >20.6e} "
        f"{par.get_min_value()[0]: >20.6e} {par.get_max_value()[0]: >20.6e}"
    )
           Name:                Value                  Min                  Max
--------------------------------------------------------------------------------
      Amplitude:         2.455094e+08         0.000000e+00         1.000000e+09
          Delta:        -2.493769e+09        -3.769911e+09        -1.256637e+08
        lo_freq:         3.015935e+10         2.412743e+10         3.619115e+10
          Phase:         3.539781e-04        -3.141593e+00         3.141593e+00

Plot final pulse shape and population transfer. Target is the full population transfer, i.e., an X-gate.

plot_signal_and_dynamics(gen, prop, ts, state_labels=[r"$|0\rangle$", r"$|1\rangle$"])
array([<Axes: ylabel='Amplitude n[MHz / $2\pi$]'>,
       <Axes: xlabel='Time [ns]', ylabel='Population'>], dtype=object)
../_images/03_DRAG_pulses_24_1.png

We can see from the plot and optimizer output that we have found better controls, for which the excitation to the second excited state is much smaller than initially.

gate_fid.get_value(times)
Array(0.99423909, dtype=float64)

References#

  • (Motzoi et al., 2009) F. Motzoi et al., “Simple pulses for elimination of leakage in weakly nonlinear qubits,” Physical Review Letters 103, 110501 (2009).