import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from scipy.optimize import curve_fit
from qibocal.auto.operation import Parameters
from ..utils import COLORBAND, COLORBAND_LINE, table_dict, table_html
[docs]def rabi_amplitude_function(x, offset, amplitude, period, phase):
"""
Fit function of Rabi amplitude signal experiment.
Args:
x: Input data.
"""
return offset + amplitude * np.cos(2 * np.pi * x / period + phase)
[docs]def rabi_length_function(x, offset, amplitude, period, phase, t2_inv):
"""
Fit function of Rabi length signal experiment.
Args:
x: Input data.
"""
return offset + amplitude * np.cos(2 * np.pi * x / period + phase) * np.exp(
-x * t2_inv
)
[docs]def plot(data, qubit, fit):
quantity, title, fitting = extract_rabi(data)
figures = []
fitting_report = ""
fig = make_subplots(
rows=1,
cols=2,
horizontal_spacing=0.1,
vertical_spacing=0.1,
subplot_titles=(
"Signal [a.u.]",
"phase [rad]",
),
)
qubit_data = data[qubit]
rabi_parameters = getattr(qubit_data, quantity)
fig.add_trace(
go.Scatter(
x=rabi_parameters,
y=qubit_data.signal,
opacity=1,
name="Signal",
showlegend=True,
legendgroup="Signal",
),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(
x=rabi_parameters,
y=qubit_data.phase,
opacity=1,
name="Phase",
showlegend=True,
legendgroup="Phase",
),
row=1,
col=2,
)
if fit is not None:
rabi_parameter_range = np.linspace(
min(rabi_parameters),
max(rabi_parameters),
2 * len(rabi_parameters),
)
params = fit.fitted_parameters[qubit]
fig.add_trace(
go.Scatter(
x=rabi_parameter_range,
y=fitting(rabi_parameter_range, *params),
name="Fit",
line=go.scatter.Line(dash="dot"),
marker_color="rgb(255, 130, 67)",
),
row=1,
col=1,
)
fitting_report = table_html(
table_dict(
qubit,
["Pi pulse amplitude [a.u.]", "Pi pulse length [ns]"],
[np.round(fit.amplitude[qubit], 3), np.round(fit.length[qubit], 3)],
)
)
fig.update_layout(
showlegend=True,
xaxis_title=title,
yaxis_title="Signal [a.u.]",
xaxis2_title=title,
yaxis2_title="Phase [rad]",
)
figures.append(fig)
return figures, fitting_report
[docs]def plot_probabilities(data, qubit, fit):
quantity, title, fitting = extract_rabi(data)
figures = []
fitting_report = ""
qubit_data = data[qubit]
probs = qubit_data.prob
error_bars = qubit_data.error
rabi_parameters = getattr(qubit_data, quantity)
fig = go.Figure(
[
go.Scatter(
x=rabi_parameters,
y=qubit_data.prob,
opacity=1,
name="Probability",
showlegend=True,
legendgroup="Probability",
mode="lines",
),
go.Scatter(
x=np.concatenate((rabi_parameters, rabi_parameters[::-1])),
y=np.concatenate((probs + error_bars, (probs - error_bars)[::-1])),
fill="toself",
fillcolor=COLORBAND,
line=dict(color=COLORBAND_LINE),
showlegend=True,
name="Errors",
),
]
)
if fit is not None:
rabi_parameter_range = np.linspace(
min(rabi_parameters),
max(rabi_parameters),
2 * len(rabi_parameters),
)
params = fit.fitted_parameters[qubit]
fig.add_trace(
go.Scatter(
x=rabi_parameter_range,
y=fitting(rabi_parameter_range, *params),
name="Fit",
line=go.scatter.Line(dash="dot"),
marker_color="rgb(255, 130, 67)",
),
)
fitting_report = table_html(
table_dict(
qubit,
["Pi pulse amplitude [a.u.]", "Pi pulse length [ns]", "chi2 reduced"],
[fit.amplitude[qubit], fit.length[qubit], fit.chi2[qubit]],
display_error=True,
)
)
fig.update_layout(
showlegend=True,
xaxis_title=title,
yaxis_title="Excited state probability",
)
figures.append(fig)
return figures, fitting_report
[docs]def period_correction_factor(phase: float):
r"""Correct period by taking phase into account.
https://github.com/qiboteam/qibocal/issues/656
We want to find the first maximum or minimum which will
correspond to an exchange of population between 0 and 1.
To find it we need to solve the following equation
:math:`\cos(2 \pi x / T + \phi) = \pm 1` which will give us
the following solution
.. math::
x = ( k - \phi / \pi) T / 2
for integer :math:`k`, which is chosen such that we get the smallest
multiplicative correction compared to :math:`T/2`.
"""
x = phase / np.pi
return np.round(1 + x) - x
[docs]def sequence_amplitude(
targets: list[QubitId], params: Parameters, platform: Platform
) -> tuple[PulseSequence, dict, dict, dict]:
"""Return sequence for rabi amplitude."""
sequence = PulseSequence()
qd_pulses = {}
ro_pulses = {}
durations = {}
for qubit in targets:
qd_pulses[qubit] = platform.create_RX_pulse(qubit, start=0)
if params.pulse_length is not None:
qd_pulses[qubit].duration = params.pulse_length
durations[qubit] = qd_pulses[qubit].duration
ro_pulses[qubit] = platform.create_qubit_readout_pulse(
qubit, start=qd_pulses[qubit].finish
)
sequence.add(qd_pulses[qubit])
sequence.add(ro_pulses[qubit])
return sequence, qd_pulses, ro_pulses, durations
[docs]def sequence_length(
targets: list[QubitId], params: Parameters, platform: Platform
) -> tuple[PulseSequence, dict, dict, dict]:
"""Return sequence for rabi length."""
sequence = PulseSequence()
qd_pulses = {}
ro_pulses = {}
amplitudes = {}
for qubit in targets:
qd_pulses[qubit] = platform.create_qubit_drive_pulse(
qubit, start=0, duration=params.pulse_duration_start
)
if params.pulse_amplitude is not None:
qd_pulses[qubit].amplitude = params.pulse_amplitude
amplitudes[qubit] = qd_pulses[qubit].amplitude
ro_pulses[qubit] = platform.create_qubit_readout_pulse(
qubit, start=qd_pulses[qubit].finish
)
sequence.add(qd_pulses[qubit])
sequence.add(ro_pulses[qubit])
return sequence, qd_pulses, ro_pulses, amplitudes
[docs]def fit_length_function(
x, y, guess, sigma=None, signal=True, x_limits=(None, None), y_limits=(None, None)
):
inf_bounds = [0, -1, 0, -np.pi, 0] if signal else [0, 0, 0, -np.pi, 0]
popt, perr = curve_fit(
rabi_length_function,
x,
y,
p0=guess,
maxfev=100000,
bounds=(
inf_bounds,
[1, 1, np.inf, np.pi, np.inf],
),
sigma=sigma,
)
x_min = x_limits[0]
x_max = x_limits[1]
y_min = y_limits[0]
y_max = y_limits[1]
if signal is False:
popt = [
popt[0],
popt[1] * np.exp(x_min * popt[4] / (x_max - x_min)),
popt[2] * (x_max - x_min),
popt[3] - 2 * np.pi * x_min / popt[2] / (x_max - x_min),
popt[4] / (x_max - x_min),
]
perr = np.sqrt(np.diag(perr))
else:
popt = [ # change it according to the fit function
(y_max - y_min) * (popt[0] + 1 / 2) + y_min,
(y_max - y_min) * popt[1] * np.exp(x_min * popt[4] / (x_max - x_min)),
popt[2] * (x_max - x_min),
popt[3] - 2 * np.pi * x_min / popt[2] / (x_max - x_min),
popt[4] / (x_max - x_min),
]
pi_pulse_parameter = popt[2] / 2 * period_correction_factor(phase=popt[3])
return popt, perr, pi_pulse_parameter
[docs]def fit_amplitude_function(
x, y, guess, sigma=None, signal=True, x_limits=(None, None), y_limits=(None, None)
):
popt, perr = curve_fit(
rabi_amplitude_function,
x,
y,
p0=guess,
maxfev=100000,
bounds=(
[0, 0, 0, 0],
[1, 1, np.inf, 2 * np.pi],
),
sigma=sigma,
)
if signal is False:
perr = np.sqrt(np.diag(perr))
else:
popt = [ # Change it according to fit function changes
y_limits[0] + (y_limits[1] - y_limits[0]) * popt[0],
(y_limits[1] - y_limits[0]) * popt[1],
popt[2] * (x_limits[1] - x_limits[0]),
popt[3] - 2 * np.pi * x_limits[0] / (x_limits[1] - x_limits[0]) / popt[2],
]
pi_pulse_parameter = popt[2] / 2 * period_correction_factor(phase=popt[3])
return popt, perr, pi_pulse_parameter