Source code for qibocal.protocols.readout_optimization.resonator_amplitude

from dataclasses import dataclass, field

import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibolab import AcquisitionType, Delay, PulseSequence

from qibocal import update
from qibocal.auto.operation import Data, Parameters, QubitId, Results, Routine
from qibocal.calibration import CalibrationPlatform
from qibocal.fitting.classifier.qubit_fit import QubitFit
from qibocal.protocols.utils import table_dict, table_html
from qibocal.update import replace


[docs]@dataclass class ResonatorAmplitudeParameters(Parameters): """ResonatorAmplitude runcard inputs.""" amplitude_step: float """Amplituude step to be probed.""" amplitude_start: float = 0.0 """Amplitude start.""" amplitude_stop: float = 1.0 """Amplitude stop value""" error_threshold: float = 0.003 """Probability error threshold to stop the best amplitude search"""
ResonatorAmplitudeType = np.dtype( [ ("error", np.float64), ("amp", np.float64), ("angle", np.float64), ("threshold", np.float64), ] ) """Custom dtype for Optimization RO amplitude.""" @dataclass class ResonatorAmplitudeData(Data): """Data class for `resoantor_amplitude` protocol.""" data: dict[tuple, npt.NDArray[ResonatorAmplitudeType]] = field(default_factory=dict) @dataclass class ResonatorAmplitudeResults(Results): """Result class for `resonator_amplitude` protocol.""" lowest_errors: dict[QubitId, list] """Lowest probability errors""" best_amp: dict[QubitId, list] """Amplitude with lowest error""" best_angle: dict[QubitId, float] """IQ angle that gives lower error.""" best_threshold: dict[QubitId, float] """Thershold that gives lower error.""" def _acquisition( params: ResonatorAmplitudeParameters, platform: CalibrationPlatform, targets: list[QubitId], ) -> ResonatorAmplitudeData: r""" Data acquisition for resoantor amplitude optmization. This protocol sweeps the readout amplitude performing a classification routine and evaluating the error probability at each step. The sweep will be interrupted if the probability error is less than the `error_threshold`. Args: params (:class:`ResonatorAmplitudeParameters`): input parameters platform (:class:`CalibrationPlatform`): Qibolab's platform targets (list): list of QubitIds to be characterized Returns: data (:class:`ResonatorAmplitudeData`) """ data = ResonatorAmplitudeData() for qubit in targets: error = 1 natives = platform.natives.single_qubit[qubit] ro_channel, ro_pulse = natives.MZ()[0] new_amp = params.amplitude_start while error > params.error_threshold and new_amp <= params.amplitude_stop: new_ro = replace(ro_pulse, amplitude=new_amp) sequence_0 = PulseSequence() sequence_1 = PulseSequence() qd_channel, qd_pulse = natives.RX()[0] sequence_1.append((qd_channel, qd_pulse)) sequence_1.append((ro_channel, Delay(duration=qd_pulse.duration))) sequence_1.append((ro_channel, new_ro)) sequence_0.append((ro_channel, new_ro)) state0_results = platform.execute( [sequence_0], nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, ) state1_results = platform.execute( [sequence_1], nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, ) result0 = state0_results[new_ro.id] result1 = state1_results[new_ro.id] iq_values = np.concatenate((result0, result1)) nshots = params.nshots states = [0] * nshots + [1] * nshots model = QubitFit() model.fit(iq_values, np.array(states)) error = model.probability_error data.register_qubit( ResonatorAmplitudeType, (qubit), dict( amp=np.array([new_amp]), error=np.array([error]), angle=np.array([model.angle]), threshold=np.array([model.threshold]), ), ) new_amp += params.amplitude_step return data def _fit(data: ResonatorAmplitudeData) -> ResonatorAmplitudeResults: qubits = data.qubits best_amps = {} best_angle = {} best_threshold = {} lowest_err = {} for qubit in qubits: data_qubit = data[qubit] index_best_err = np.argmin(data_qubit["error"]) lowest_err[qubit] = data_qubit["error"][index_best_err] best_amps[qubit] = data_qubit["amp"][index_best_err] best_angle[qubit] = data_qubit["angle"][index_best_err] best_threshold[qubit] = data_qubit["threshold"][index_best_err] return ResonatorAmplitudeResults(lowest_err, best_amps, best_angle, best_threshold) def _plot( data: ResonatorAmplitudeData, fit: ResonatorAmplitudeResults, target: QubitId ): """Plotting function for Optimization RO amplitude.""" figures = [] opacity = 1 fitting_report = None fig = make_subplots( rows=1, cols=1, ) if fit is not None: fig.add_trace( go.Scatter( x=data[target]["amp"], y=data[target]["error"], opacity=opacity, showlegend=True, mode="lines+markers", ), row=1, col=1, ) fitting_report = table_html( table_dict( target, "Best Readout Amplitude [a.u.]", np.round(fit.best_amp[target], 4), ) ) fig.update_layout( showlegend=True, xaxis_title="Readout Amplitude [a.u.]", yaxis_title="Probability Error", ) figures.append(fig) return figures, fitting_report def _update( results: ResonatorAmplitudeResults, platform: CalibrationPlatform, target: QubitId ): update.readout_amplitude(results.best_amp[target], platform, target) update.iq_angle(results.best_angle[target], platform, target) update.threshold(results.best_threshold[target], platform, target) resonator_amplitude = Routine(_acquisition, _fit, _plot, _update) """Resonator Amplitude Routine object."""