Source code for qibocal.protocols.rabi.utils

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 extract_rabi(data): """ Extract Rabi fit info. """ if "RabiAmplitude" in data.__class__.__name__: return "amp", "Amplitude [dimensionless]", rabi_amplitude_function if "RabiLength" in data.__class__.__name__: return "length", "Time [ns]", rabi_length_function raise RuntimeError("Data has to be a data structure of the Rabi routines.")
[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