Source code for qibocal.protocols.readout_optimization.resonator_amplitude

from dataclasses import dataclass, field
from os import error

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, ExecutionParameters
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId

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


[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: Platform, 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:`Platform`): Qibolab's platform targets (list): list of QubitIds to be characterized Returns: data (:class:`ResonatorAmplitudeData`) """ data = ResonatorAmplitudeData() for qubit in targets: error = 1 old_amp = platform.qubits[qubit].native_gates.MZ.amplitude new_amp = params.amplitude_start while error > params.error_threshold and new_amp <= params.amplitude_stop: platform.qubits[qubit].native_gates.MZ.amplitude = new_amp sequence_0 = PulseSequence() sequence_1 = PulseSequence() qd_pulses = platform.create_RX_pulse(qubit, start=0) ro_pulses = platform.create_qubit_readout_pulse( qubit, start=qd_pulses.finish ) sequence_0.add(ro_pulses) sequence_1.add(qd_pulses) sequence_1.add(ro_pulses) state0_results = platform.execute_pulse_sequence( sequence_0, ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, ), ) state1_results = platform.execute_pulse_sequence( sequence_1, ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, ), ) result0 = state0_results[ro_pulses.serial] result1 = state1_results[ro_pulses.serial] i_values = np.concatenate((result0.voltage_i, result1.voltage_i)) q_values = np.concatenate((result0.voltage_q, result1.voltage_q)) iq_values = np.stack((i_values, q_values), axis=-1) nshots = int(len(i_values) / 2) 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]), ), ) platform.qubits[qubit].native_gates.MZ.amplitude = old_amp 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: Platform, 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."""