Source code for qibocal.protocols.ramsey.ramsey

from dataclasses import dataclass, field
from typing import Optional

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 QubitId, Routine
from qibocal.calibration import CalibrationPlatform
from qibocal.config import log
from qibocal.result import probability

from ..utils import COLORBAND, COLORBAND_LINE, chi2_reduced, table_dict, table_html
from .ramsey_signal import (
    RamseySignalData,
    RamseySignalParameters,
    RamseySignalResults,
    _update,
)
from .utils import fitting, process_fit, ramsey_fit, ramsey_sequence


[docs]@dataclass class RamseyParameters(RamseySignalParameters): """Ramsey runcard inputs."""
[docs]@dataclass class RamseyResults(RamseySignalResults): """Ramsey outputs.""" chi2: dict[QubitId, tuple[float, Optional[float]]] """Chi squared estimate mean value and error. """
RamseyType = np.dtype( [("wait", np.float64), ("prob", np.float64), ("errors", np.float64)] ) """Custom dtype for coherence routines."""
[docs]@dataclass class RamseyData(RamseySignalData): """Ramsey acquisition outputs.""" data: dict[QubitId, npt.NDArray[RamseyType]] = field(default_factory=dict) """Raw data acquired."""
[docs]def _acquisition( params: RamseyParameters, platform: CalibrationPlatform, targets: list[QubitId], ) -> RamseyData: """Data acquisition for Ramsey Experiment (detuned). The protocol consists in applying the following pulse sequence RX90 - wait - RX90 - MZ for different waiting times `wait`. The range of waiting times is defined through the attributes `delay_between_pulses_*` available in `RamseyParameters`. The final range will be constructed using `np.arange`. It is possible to detune the drive frequency using the parameter `detuning` in RamseyParameters which will increment the drive frequency accordingly. Currently when `detuning==0` it will be performed a sweep over the waiting values if `detuning` is not zero, all sequences with different waiting value will be executed sequentially. By providing the option `unrolling=True` in RamseyParameters the sequences will be unrolled when the frequency is detuned. The following protocol will display on the y-axis the probability of finding the ground state, therefore it is advise to execute it only after having performed the single shot classification. Error bars are provided as binomial distribution error. """ waits = np.arange( params.delay_between_pulses_start, params.delay_between_pulses_end, params.delay_between_pulses_step, ) data = RamseyData( detuning=params.detuning, qubit_freqs={ qubit: platform.config(platform.qubits[qubit].drive).frequency for qubit in targets }, ) updates = [] 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]], nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.SINGLESHOT, updates=updates, ) for qubit in targets: ro_pulse = list(sequence.channel(platform.qubits[qubit].acquisition))[-1] probs = probability(results[ro_pulse.id], state=1) # The probability errors are the standard errors of the binomial distribution errors = [np.sqrt(prob * (1 - prob) / params.nshots) for prob in probs] data.register_qubit( RamseyType, (qubit), dict( wait=waits, prob=probs, errors=errors, ), ) 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.DISCRIMINATION, averaging_mode=AveragingMode.SINGLESHOT, updates=updates, ) for wait, ro_pulses in zip(waits, all_ro_pulses): for qubit in targets: result = results[ro_pulses[qubit].id] prob = probability(result, state=1) error = np.sqrt(prob * (1 - prob) / params.nshots) data.register_qubit( RamseyType, (qubit), dict( wait=np.array([wait]), prob=np.array([prob]), errors=np.array([error]), ), ) return data
[docs]def _fit(data: RamseyData) -> RamseyResults: 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 = {} chi2 = {} for qubit in qubits: qubit_data = data[qubit] qubit_freq = data.qubit_freqs[qubit] probs = qubit_data["prob"] try: popt, perr = fitting(waits, probs, qubit_data.errors) ( freq_measure[qubit], t2_measure[qubit], delta_phys_measure[qubit], delta_fitting_measure[qubit], popts[qubit], ) = process_fit(popt, perr, qubit_freq, data.detuning) chi2[qubit] = ( chi2_reduced( probs, ramsey_fit(waits, *popts[qubit]), qubit_data.errors, ), np.sqrt(2 / len(probs)), ) except Exception as e: log.warning(f"Ramsey fitting failed for qubit {qubit} due to {e}.") return RamseyResults( detuning=data.detuning, frequency=freq_measure, t2=t2_measure, delta_phys=delta_phys_measure, delta_fitting=delta_fitting_measure, fitted_parameters=popts, chi2=chi2, )
[docs]def _plot(data: RamseyData, target: QubitId, fit: RamseyResults = None): """Plotting function for Ramsey Experiment.""" figures = [] fig = go.Figure() fitting_report = "" qubit_data = data.data[target] waits = data.waits probs = qubit_data["prob"] error_bars = qubit_data["errors"] fig = go.Figure( [ go.Scatter( x=waits, y=probs, opacity=1, name="Probability of State 1", showlegend=True, legendgroup="Probability of State 1", mode="lines", ), go.Scatter( x=np.concatenate((waits, waits[::-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: 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]", "chi2 reduced", ], [ fit.delta_phys[target], fit.delta_fitting[target], fit.frequency[target], fit.t2[target], fit.chi2[target], ], display_error=True, ) ) fig.update_layout( showlegend=True, xaxis_title="Time [ns]", yaxis_title="Excited state probability", ) figures.append(fig) return figures, fitting_report
ramsey = Routine(_acquisition, _fit, _plot, _update) """Ramsey Routine object."""