Skip to content

Commit

Permalink
Merge pull request #1044 from qiboteam/pi_half
Browse files Browse the repository at this point in the history
RX90 calibration implementation
  • Loading branch information
ElStabilini authored Dec 12, 2024
2 parents d7745ae + 837cc08 commit 7b23156
Show file tree
Hide file tree
Showing 18 changed files with 456 additions and 201 deletions.
34 changes: 25 additions & 9 deletions doc/source/protocols/flipping.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
Flipping
========

The flipping experiment corrects the amplitude in the qubit drive pulse. In this experiment,
we applying an :math:`R_x(\pi/2)` rotation followed by :math:`N` flips (two :math:`R_x(\pi)` rotations)
The flipping experiment corrects the amplitude in the qubit drive pulse for :math:`R_x(\pi)` rotations. In this experiment,
we apply an :math:`R_x(\pi/2)` rotation followed by :math:`N` flips (two :math:`R_x(\pi)` rotations)
and we measure the qubit state.
The first :math:`R_x(\pi/2)` is necessary to discriminate the over rotations and under rotations of the :math:`R_x(\pi)` pulse:
without it the difference between the two cases is just a global phase, i.e., the
probabilities are the same. With the :math:`R_x(\pi/2)` pulse, in case of under rotations the state will be closer to :math:`\ket{0}`
after the initial flip, in the over rotations one the final state will be closer to :math:`\ket{1}`.

By fitting the resulting data with a sinusoidal function, we can determine the delta amplitude, which allows us to refine the
:math:`\pi` pulse amplitue.
:math:`\pi` pulse amplitude.

We implemented also a version of the flipping protocol to calibrate the drive pulse amplitude of the :math:`R_x(\pi/2)` rotations,
in this case each :math:`R_x(\pi)` rotation is replaced by two :math:`R_x(\pi/2)` rotations.
The main reasons for implementing protocols to fine tune the `R_x(\pi/2)` rotations are explained in :ref:`rabi`.

Parameters
^^^^^^^^^^
Expand All @@ -24,17 +28,29 @@ It follows a runcard example of this experiment.

.. code-block:: yaml
- id: flipping
operation: flipping
parameters:
delta_amplitude: 0.05
nflips_max: 30
nflips_step: 1
- id: flipping
operation: flipping
parameters:
delta_amplitude: 0.05
nflips_max: 30
nflips_step: 1
The expected output is the following:

.. image:: flipping.png

If the same experiment is run setting the `rx90: True` the flipping is performed to calibrate the amplitude of the :math:`R_x(\pi/2)` rotation

.. code-block:: yaml
- id: flipping
operation: flipping
parameters:
delta_amplitude: 0.05
nflips_max: 30
nflips_step: 1
rx90: True
Requirements
^^^^^^^^^^^^

Expand Down
34 changes: 34 additions & 0 deletions doc/source/protocols/rabi/rabi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ Rabi rate is larger than the decay and the pure dephasing rate,
where :math:`\Omega_R` is the Rabi frequency and :math:`\tau` the decay time.


Since many routines and protocols in quantum computing are based on `R_x(\pi/2)` rotations, in qibocal we implemented
also another version of the Rabi experiment which can be used to tune the amplitude (duration) of the drive pulse in order
to excite the qubit from the ground state up to state :math:`\frac{\ket{0}-i\ket{1}}{\sqrt{2}}`.

The possibility to calibrate an `R_x(\pi/2)` rotation as native gate allows us to remove the errors that could arise from assuming that the `R_x(\pi/2)` amplitude (duration)
is exactly half that of the `R_x(\pi)` amplitude (duration). This assumption presumes a perfectly linear response of the qubit to the drive pulse, which is
often not the case due to nonlinearities in the qubit's response or imperfections in the pulse shaping :cite:p:`Chen2018MetrologyOQ`.

In this case the pulse sequence is the same as before with the only difference that instead of a single `R_x(\pi)` pulse we use two concatenated `R_x(\pi/2)` pulses.

Parameters
^^^^^^^^^^

Expand Down Expand Up @@ -103,6 +114,29 @@ It follows an example runcard and plot for the signal exepriment
.. image:: rabi_signal.png

In all the previous examples we run Rabi experiments for calibrating the amplitude (duration) of the drive pulse
to excite the qubit from the ground state up to state :math:`\ket{1}`.
All these example runcards can be modified to calibrate the amplitude (duration) of the drive pulse
to excite the qubit from the ground state up to state :math:`\frac{\ket{0}-i\ket{1}}{\sqrt{2}}` by simply setting the `rx90` parameter to `True`.

In the following we show an example runcard for the amplitude calibration of the `R_x(\pi/2)`.

.. code-block:: yaml
- id: Rabi signal
operation: rabi_amplitude_signal
parameters:
min_amp: 0.01
max_amp: 0.16
step_amp: 0.002
pulse_length: 40
nshots: 1024
relaxation_time: 50000
rx90: True
.. image:: rabi_amplitude_rx90

Requirements
^^^^^^^^^^^^
- :ref:`qubit-spectroscopy`
Expand Down
Binary file added doc/source/protocols/rabi/rabi_amplitude_rx90.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
223 changes: 112 additions & 111 deletions poetry.lock

Large diffs are not rendered by default.

63 changes: 43 additions & 20 deletions src/qibocal/protocols/flipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,35 @@


def flipping_sequence(
platform: CalibrationPlatform, qubit: QubitId, delta_amplitude: float, flips: int
platform: CalibrationPlatform,
qubit: QubitId,
delta_amplitude: float,
flips: int,
rx90: bool,
):
"""Pulse sequence for flipping experiment."""

sequence = PulseSequence()
natives = platform.natives.single_qubit[qubit]

sequence |= natives.R(theta=np.pi / 2)

for _ in range(flips):

qd_channel, rx_pulse = natives.RX()[0]
if rx90:
qd_channel, qd_pulse = natives.RX90()[0]
else:
qd_channel, qd_pulse = natives.RX()[0]

rx_detuned = update.replace(
rx_pulse, amplitude=rx_pulse.amplitude + delta_amplitude
qd_detuned = update.replace(
qd_pulse, amplitude=qd_pulse.amplitude + delta_amplitude
)
sequence.append((qd_channel, rx_detuned))
sequence.append((qd_channel, rx_detuned))
sequence.append((qd_channel, qd_detuned))
sequence.append((qd_channel, qd_detuned))

if rx90:
sequence.append((qd_channel, qd_detuned))
sequence.append((qd_channel, qd_detuned))

sequence |= natives.MZ()

Expand All @@ -59,6 +71,8 @@ class FlippingParameters(Parameters):
Defaults to ``False``."""
delta_amplitude: float = 0
"""Amplitude detuning."""
rx90: bool = False
"""Calibration of native pi pulse, if true calibrates pi/2 pulse"""


@dataclass
Expand All @@ -73,6 +87,8 @@ class FlippingResults(Results):
"""Difference in amplitude between detuned value and fit."""
fitted_parameters: dict[QubitId, dict[str, float]]
"""Raw fitting output."""
rx90: bool
"""Pi or Pi_half calibration"""
chi2: dict[QubitId, list[float]] = field(default_factory=dict)
"""Chi squared estimate mean value and error. """

Expand All @@ -90,8 +106,10 @@ class FlippingData(Data):
"""Resonator type."""
delta_amplitude: float
"""Amplitude detuning."""
pi_pulse_amplitudes: dict[QubitId, float]
"""Pi pulse amplitudes for each qubit."""
pulse_amplitudes: dict[QubitId, float]
"""Pulse amplitudes for each qubit."""
rx90: bool
"""Pi or Pi_half calibration"""
data: dict[QubitId, npt.NDArray[FlippingType]] = field(default_factory=dict)
"""Raw data acquired."""

Expand Down Expand Up @@ -120,10 +138,13 @@ def _acquisition(
data = FlippingData(
resonator_type=platform.resonator_type,
delta_amplitude=params.delta_amplitude,
pi_pulse_amplitudes={
qubit: platform.natives.single_qubit[qubit].RX[0][1].amplitude
pulse_amplitudes={
qubit: getattr(
platform.natives.single_qubit[qubit], "RX90" if params.rx90 else "RX"
)[0][1].amplitude
for qubit in targets
},
rx90=params.rx90,
)

options = {
Expand All @@ -144,6 +165,7 @@ def _acquisition(
qubit=qubit,
delta_amplitude=params.delta_amplitude,
flips=flips,
rx90=params.rx90,
)

sequences.append(sequence)
Expand Down Expand Up @@ -197,9 +219,7 @@ def _fit(data: FlippingData) -> FlippingResults:
chi2 = {}
for qubit in qubits:
qubit_data = data[qubit]
detuned_pi_pulse_amplitude = (
data.pi_pulse_amplitudes[qubit] + data.delta_amplitude
)
detuned_pulse_amplitude = data.pulse_amplitudes[qubit] + data.delta_amplitude
y = qubit_data.prob
x = qubit_data.flips

Expand All @@ -222,10 +242,14 @@ def _fit(data: FlippingData) -> FlippingResults:
perr = np.sqrt(np.diag(perr)).tolist()
popt = popt.tolist()
correction = popt[2] / 2

if data.rx90:
correction /= 2

corrected_amplitudes[qubit] = [
float(detuned_pi_pulse_amplitude * np.pi / (np.pi + correction)),
float(detuned_pulse_amplitude * np.pi / (np.pi + correction)),
float(
detuned_pi_pulse_amplitude
detuned_pulse_amplitude
* np.pi
* 1
/ (np.pi + correction) ** 2
Expand All @@ -237,11 +261,9 @@ def _fit(data: FlippingData) -> FlippingResults:
fitted_parameters[qubit] = popt

delta_amplitude_detuned[qubit] = [
-correction * detuned_pi_pulse_amplitude / (np.pi + correction),
-correction * detuned_pulse_amplitude / (np.pi + correction),
np.abs(
np.pi
* detuned_pi_pulse_amplitude
* np.power(np.pi + correction, -2)
np.pi * detuned_pulse_amplitude * np.power(np.pi + correction, -2)
)
* perr[2]
/ 2,
Expand All @@ -267,6 +289,7 @@ def _fit(data: FlippingData) -> FlippingResults:
delta_amplitude,
delta_amplitude_detuned,
fitted_parameters,
data.rx90,
chi2,
)

Expand Down Expand Up @@ -358,7 +381,7 @@ def _plot(data: FlippingData, target: QubitId, fit: FlippingResults = None):


def _update(results: FlippingResults, platform: CalibrationPlatform, qubit: QubitId):
update.drive_amplitude(results.amplitude[qubit], platform, qubit)
update.drive_amplitude(results.amplitude[qubit], results.rx90, platform, qubit)


flipping = Routine(_acquisition, _fit, _plot, _update)
Expand Down
16 changes: 10 additions & 6 deletions src/qibocal/protocols/rabi/amplitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class RabiAmplitudeResults(RabiAmplitudeSignalResults):
class RabiAmplitudeData(Data):
"""RabiAmplitude data acquisition."""

rx90: bool
"""Pi or Pi_half calibration"""
durations: dict[QubitId, float] = field(default_factory=dict)
"""Pulse durations provided by the user."""
data: dict[QubitId, npt.NDArray[RabiAmpType]] = field(default_factory=dict)
Expand All @@ -55,7 +57,7 @@ def _acquisition(
"""

sequence, qd_pulses, ro_pulses, durations = utils.sequence_amplitude(
targets, params, platform
targets, params, platform, params.rx90
)

sweeper = Sweeper(
Expand All @@ -64,7 +66,7 @@ def _acquisition(
pulses=[qd_pulses[qubit] for qubit in targets],
)

data = RabiAmplitudeData(durations=durations)
data = RabiAmplitudeData(durations=durations, rx90=params.rx90)

# sweep the parameter
results = platform.execute(
Expand Down Expand Up @@ -128,19 +130,21 @@ def _fit(data: RabiAmplitudeData) -> RabiAmplitudeResults:

except Exception as e:
log.warning(f"Rabi fit failed for qubit {qubit} due to {e}.")
return RabiAmplitudeResults(pi_pulse_amplitudes, durations, fitted_parameters, chi2)
return RabiAmplitudeResults(
pi_pulse_amplitudes, durations, fitted_parameters, data.rx90, chi2
)


def _plot(data: RabiAmplitudeData, target: QubitId, fit: RabiAmplitudeResults = None):
"""Plotting function for RabiAmplitude."""
return utils.plot_probabilities(data, target, fit)
return utils.plot_probabilities(data, target, fit, data.rx90)


def _update(
results: RabiAmplitudeResults, platform: CalibrationPlatform, target: QubitId
):
update.drive_amplitude(results.amplitude[target], platform, target)
update.drive_duration(results.length[target], platform, target)
update.drive_amplitude(results.amplitude[target], results.rx90, platform, target)
update.drive_duration(results.length[target], results.rx90, platform, target)


rabi_amplitude = Routine(_acquisition, _fit, _plot, _update)
Expand Down
9 changes: 6 additions & 3 deletions src/qibocal/protocols/rabi/amplitude_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _acquisition(
"""Data acquisition for Rabi experiment sweeping amplitude."""

sequence, qd_pulses, ro_pulses, durations = sequence_amplitude(
targets, params, platform
targets, params, platform, params.rx90
)
frequency_range = np.arange(
params.min_freq,
Expand All @@ -101,7 +101,7 @@ def _acquisition(
pulses=[qd_pulses[qubit] for qubit in targets],
)

data = RabiAmplitudeFreqData(durations=durations)
data = RabiAmplitudeFreqData(durations=durations, rx90=params.rx90)

results = platform.execute(
[sequence],
Expand Down Expand Up @@ -186,6 +186,7 @@ def _fit(data: RabiAmplitudeFreqData) -> RabiAmplitudeFrequencyResults:
fitted_parameters=fitted_parameters,
frequency=fitted_frequencies,
chi2=chi2,
rx90=data.rx90,
)


Expand Down Expand Up @@ -234,10 +235,12 @@ def _plot(
row=1,
col=1,
)
pulse_name = "Pi-half pulse" if data.rx90 else "Pi pulse"

fitting_report = table_html(
table_dict(
target,
["Optimal rabi frequency", "Pi-pulse amplitude"],
["Optimal rabi frequency", f"{pulse_name} amplitude"],
[
fit.frequency[target],
f"{fit.amplitude[target][0]:.6f} +- {fit.amplitude[target][1]:.6f} [a.u.]",
Expand Down
Loading

0 comments on commit 7b23156

Please sign in to comment.