Source code for qibocal.protocols.ramsey.ramsey_signal

from dataclasses import dataclass, field
from typing import Optional, Union

import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibolab import AcquisitionType, AveragingMode, Parameter, Readout, Sweeper

from qibocal.auto.operation import Data, Parameters, QubitId, Results, Routine
from qibocal.calibration import CalibrationPlatform
from qibocal.config import log
from qibocal.result import magnitude

from ... import update
from ..utils import readout_frequency, table_dict, table_html
from .utils import fitting, process_fit, ramsey_fit, ramsey_sequence


[docs]@dataclass class RamseySignalParameters(Parameters): """Ramsey runcard inputs.""" delay_between_pulses_start: int """Initial delay between RX(pi/2) pulses in ns.""" delay_between_pulses_end: int """Final delay between RX(pi/2) pulses in ns.""" delay_between_pulses_step: int """Step delay between RX(pi/2) pulses in ns.""" detuning: Optional[int] = None """Frequency detuning [Hz] (optional). If 0 standard Ramsey experiment is performed.""" unrolling: bool = False """If ``True`` it uses sequence unrolling to deploy multiple sequences in a single instrument call. Defaults to ``False``."""
[docs]@dataclass class RamseySignalResults(Results): """Ramsey outputs.""" detuning: float """Qubit frequency detuning.""" frequency: dict[QubitId, Union[float, list[float]]] """Drive frequency [GHz] for each qubit.""" t2: dict[QubitId, Union[float, list[float]]] """T2 for each qubit [ns].""" delta_phys: dict[QubitId, Union[float, list[float]]] """Drive frequency [Hz] correction for each qubit.""" delta_fitting: dict[QubitId, Union[float, list[float]]] """Raw drive frequency [Hz] correction for each qubit. including the detuning.""" fitted_parameters: dict[QubitId, list[float]] """Raw fitting output."""
RamseySignalType = np.dtype([("wait", np.float64), ("signal", np.float64)]) """Custom dtype for coherence routines."""
[docs]@dataclass class RamseySignalData(Data): """Ramsey acquisition outputs.""" detuning: Optional[int] = None """Frequency detuning [Hz].""" qubit_freqs: dict[QubitId, float] = field(default_factory=dict) """Qubit freqs for each qubit.""" data: dict[QubitId, npt.NDArray[RamseySignalType]] = field(default_factory=dict) """Raw data acquired.""" @property def waits(self): """ Return a list with the waiting times without repetitions. """ qubit = next(iter(self.data)) return np.unique(self.data[qubit].wait)
[docs]def _acquisition( params: RamseySignalParameters, platform: CalibrationPlatform, targets: list[QubitId], ) -> RamseySignalData: """Data acquisition for Ramsey Experiment (detuned).""" # create a sequence of pulses for the experiment # RX90 - t - RX90 - MZ # define the parameter to sweep and its range: waits = np.arange( # wait time between RX90 pulses params.delay_between_pulses_start, params.delay_between_pulses_end, params.delay_between_pulses_step, ) data = RamseySignalData( detuning=params.detuning, qubit_freqs={ qubit: platform.config(platform.qubits[qubit].drive).frequency for qubit in targets }, ) updates = [] updates += [ {platform.qubits[q].probe: {"frequency": readout_frequency(q, platform)}} for q in targets ] if params.detuning is not None: for qubit in targets: channel = platform.qubits[qubit].drive f0 = platform.config(channel).frequency updates.append({channel: {"frequency": f0 + params.detuning}}) if not params.unrolling: sequence, delays = ramsey_sequence(platform, targets) sweeper = Sweeper( parameter=Parameter.duration, values=waits, pulses=delays, ) # execute the sweep results = platform.execute( [sequence], [[sweeper]], updates=updates, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC, ) for qubit in targets: ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] result = results[ro_pulse.id] # The probability errors are the standard errors of the binomial distribution data.register_qubit( RamseySignalType, (qubit), dict( wait=waits, signal=magnitude(result), ), ) else: sequences, all_ro_pulses = [], [] for wait in waits: sequence, _ = ramsey_sequence(platform, targets, wait) sequences.append(sequence) all_ro_pulses.append( { qubit: [ pulse for pulse in list( sequence.channel(platform.qubits[qubit].acquisition) ) if isinstance(pulse, Readout) ][0] for qubit in targets } ) results = platform.execute( sequences, nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC, updates=updates, ) for wait, ro_pulses in zip(waits, all_ro_pulses): for qubit in targets: result = results[ro_pulses[qubit].id] data.register_qubit( RamseySignalType, (qubit), dict( wait=np.array([wait]), signal=np.array([magnitude(result)]), ), ) return data
[docs]def _fit(data: RamseySignalData) -> RamseySignalResults: r"""Fitting routine for Ramsey experiment. The used model is .. math:: y = p_0 + p_1 sin \Big(p_2 x + p_3 \Big) e^{-x p_4}. """ qubits = data.qubits waits = data.waits popts = {} freq_measure = {} t2_measure = {} delta_phys_measure = {} delta_fitting_measure = {} for qubit in qubits: qubit_data = data[qubit] qubit_freq = data.qubit_freqs[qubit] signal = qubit_data["signal"] try: popt, perr = fitting(waits, signal) ( freq_measure[qubit], t2_measure[qubit], delta_phys_measure[qubit], delta_fitting_measure[qubit], popts[qubit], ) = process_fit(popt, perr, qubit_freq, data.detuning) except Exception as e: log.warning(f"Ramsey fitting failed for qubit {qubit} due to {e}.") return RamseySignalResults( detuning=data.detuning, frequency=freq_measure, t2=t2_measure, delta_phys=delta_phys_measure, delta_fitting=delta_fitting_measure, fitted_parameters=popts, )
[docs]def _plot(data: RamseySignalData, target: QubitId, fit: RamseySignalResults = None): """Plotting function for Ramsey Experiment.""" figures = [] fig = go.Figure() fitting_report = "" qubit_data = data.data[target] waits = data.waits signal = qubit_data["signal"] fig = go.Figure( [ go.Scatter( x=waits, y=signal, opacity=1, name="Signal", showlegend=True, legendgroup="Signal", mode="lines", ), ] ) if fit is not None: fig.add_trace( go.Scatter( x=waits, y=ramsey_fit( waits, float(fit.fitted_parameters[target][0]), float(fit.fitted_parameters[target][1]), float(fit.fitted_parameters[target][2]), float(fit.fitted_parameters[target][3]), float(fit.fitted_parameters[target][4]), ), name="Fit", line=go.scatter.Line(dash="dot"), ) ) fitting_report = table_html( table_dict( target, [ "Delta Frequency [Hz]", "Delta Frequency (with detuning) [Hz]", "Drive Frequency [Hz]", "T2* [ns]", ], [ np.round(fit.delta_phys[target][0], 3), np.round(fit.delta_fitting[target][0], 3), np.round(fit.frequency[target][0], 3), np.round(fit.t2[target][0], 3), ], ) ) fig.update_layout( showlegend=True, xaxis_title="Time [ns]", yaxis_title="Signal [a.u.]", ) figures.append(fig) return figures, fitting_report
[docs]def _update( results: RamseySignalResults, platform: CalibrationPlatform, target: QubitId ): if results.detuning is not None: update.drive_frequency(results.frequency[target][0], platform, target) else: update.t2(results.t2[target], platform, target)
ramsey_signal = Routine(_acquisition, _fit, _plot, _update) """Ramsey Routine object."""