Source code for qibocal.protocols.readout_characterization

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, 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.protocols.utils import (
    effective_qubit_temperature,
    format_error_single_cell,
    round_report,
    table_dict,
    table_html,
)


[docs]@dataclass class ReadoutCharacterizationParameters(Parameters): """ReadoutCharacterization runcard inputs.""" delay: float = 0 """Delay between readouts, could account for resonator deplation or not [ns]."""
[docs]@dataclass class ReadoutCharacterizationResults(Results): """ReadoutCharacterization outputs.""" fidelity: dict[QubitId, float] "Fidelity of the measurement" assignment_fidelity: dict[QubitId, float] """Assignment fidelity.""" qnd: dict[QubitId, float] "QND-ness of the measurement" effective_temperature: dict[QubitId, list[float]] """Effective qubit temperature.""" Lambda_M: dict[QubitId, float] "Mapping between a given initial state to an outcome after the measurement" Lambda_M2: dict[QubitId, float] "Mapping between the outcome after the measurement and it still being that outcame after another measurement"
ReadoutCharacterizationType = np.dtype( [ ("i", np.float64), ("q", np.float64), ] ) """Custom dtype for ReadoutCharacterization."""
[docs]@dataclass class ReadoutCharacterizationData(Data): """ReadoutCharacterization acquisition outputs.""" qubit_frequencies: dict[QubitId, float] = field(default_factory=dict) """Qubit frequencies.""" delay: float = 0 """Delay between readouts [ns].""" data: dict[tuple, npt.NDArray[ReadoutCharacterizationType]] = field( default_factory=dict ) """Raw data acquired.""" samples: dict[tuple, npt.NDArray] = field(default_factory=dict) """Raw data acquired."""
[docs]def _acquisition( params: ReadoutCharacterizationParameters, platform: Platform, targets: list[QubitId], ) -> ReadoutCharacterizationData: """Data acquisition for resonator spectroscopy.""" data = ReadoutCharacterizationData( qubit_frequencies={ qubit: platform.qubits[qubit].drive_frequency for qubit in targets }, delay=float(params.delay), ) # FIXME: ADD 1st measurament and post_selection for accurate state preparation ? for state in [0, 1]: # Define the pulse sequences if state == 1: RX_pulses = {} ro_pulses = {} sequence = PulseSequence() for qubit in targets: start = 0 if state == 1: RX_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) sequence.add(RX_pulses[qubit]) start = RX_pulses[qubit].finish ro_pulses[qubit] = [] for _ in range(2): ro_pulse = platform.create_qubit_readout_pulse(qubit, start=start) start += ro_pulse.duration + int( params.delay ) # device required conversion sequence.add(ro_pulse) ro_pulses[qubit].append(ro_pulse) # execute the pulse sequence results = platform.execute_pulse_sequence( sequence, ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, ), ) results_samples = platform.execute_pulse_sequence( sequence, ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, ), ) # Save the data for qubit in targets: for i, ro_pulse in enumerate(ro_pulses[qubit]): result = results[ro_pulse.serial] data.register_qubit( ReadoutCharacterizationType, (qubit, state, i), dict(i=result.voltage_i, q=result.voltage_q), ) result_samples = results_samples[ro_pulse.serial] data.samples[qubit, state, i] = result_samples.samples.tolist() return data
[docs]def _fit(data: ReadoutCharacterizationData) -> ReadoutCharacterizationResults: """Post-processing function for ReadoutCharacterization.""" qubits = data.qubits assignment_fidelity = {} fidelity = {} effective_temperature = {} qnd = {} Lambda_M = {} Lambda_M2 = {} for qubit in qubits: # 1st measurement (m=1) m1_state_1 = data.samples[qubit, 1, 0] nshots = len(m1_state_1) # state 1 state1_count_1_m1 = np.count_nonzero(m1_state_1) state0_count_1_m1 = nshots - state1_count_1_m1 m1_state_0 = data.samples[qubit, 0, 0] # state 0 state1_count_0_m1 = np.count_nonzero(m1_state_0) state0_count_0_m1 = nshots - state1_count_0_m1 # 2nd measurement (m=2) m2_state_1 = data.samples[qubit, 1, 1] # state 1 state1_count_1_m2 = np.count_nonzero(m2_state_1) state0_count_1_m2 = nshots - state1_count_1_m2 m2_state_0 = data.samples[qubit, 0, 1] # state 0 state1_count_0_m2 = np.count_nonzero(m2_state_0) state0_count_0_m2 = nshots - state1_count_0_m2 # Repeat Lambda and fidelity for each measurement ? Lambda_M[qubit] = [ [state0_count_0_m1 / nshots, state0_count_1_m1 / nshots], [state1_count_0_m1 / nshots, state1_count_1_m1 / nshots], ] # Repeat Lambda and fidelity for each measurement ? Lambda_M2[qubit] = [ [state0_count_0_m2 / nshots, state0_count_1_m2 / nshots], [state1_count_0_m2 / nshots, state1_count_1_m2 / nshots], ] assignment_fidelity[qubit] = ( 1 - (state1_count_0_m1 / nshots + state0_count_1_m1 / nshots) / 2 ) fidelity[qubit] = 2 * assignment_fidelity[qubit] - 1 # QND FIXME: Careful revision P_0o_m0_1i = state0_count_1_m1 * state0_count_0_m2 / nshots**2 P_0o_m1_1i = state1_count_1_m1 * state0_count_1_m2 / nshots**2 P_0o_1i = P_0o_m0_1i + P_0o_m1_1i P_1o_m0_0i = state0_count_0_m1 * state1_count_0_m2 / nshots**2 P_1o_m1_0i = state1_count_0_m1 * state1_count_1_m2 / nshots**2 P_1o_0i = P_1o_m0_0i + P_1o_m1_0i qnd[qubit] = 1 - (P_0o_1i + P_1o_0i) / 2 effective_temperature[qubit] = effective_qubit_temperature( prob_1=state0_count_1_m1 / nshots, prob_0=state0_count_0_m1 / nshots, qubit_frequency=data.qubit_frequencies[qubit], nshots=nshots, ) return ReadoutCharacterizationResults( fidelity, assignment_fidelity, qnd, effective_temperature, Lambda_M, Lambda_M2 )
[docs]def _plot( data: ReadoutCharacterizationData, fit: ReadoutCharacterizationResults, target: QubitId, ): """Plotting function for ReadoutCharacterization.""" # Maybe the plot can just be something like a confusion matrix between 0s and 1s ??? figures = [] fitting_report = "" fig = go.Figure() for state in range(2): for measure in range(2): shots = data.data[target, state, measure] fig.add_trace( go.Scatter( x=shots.i, y=shots.q, name=f"Prepared state {state} measurement {measure}", mode="markers", showlegend=True, opacity=0.7, marker=dict(size=3), ) ) fig.update_layout( title={ "text": "IQ Plane", "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top", }, xaxis_title="I", yaxis_title="Q", ) figures.append(fig) if fit is not None: fig = make_subplots( rows=1, cols=2, subplot_titles=( "1st measurement statistics", "2nd measurement statistics", ), ) fig.add_trace( go.Heatmap( z=fit.Lambda_M[target], x=["0", "1"], y=["0", "1"], coloraxis="coloraxis", ), row=1, col=1, ) fig.add_trace( go.Heatmap( z=fit.Lambda_M2[target], x=["0", "1"], y=["0", "1"], coloraxis="coloraxis", ), row=1, col=2, ) fig.update_xaxes(title_text="Measured state", row=1, col=1) fig.update_xaxes(title_text="Measured state", row=1, col=2) fig.update_yaxes(title_text="Prepared state", row=1, col=1) fig.update_yaxes(title_text="Prepared state", row=1, col=2) figures.append(fig) fitting_report = table_html( table_dict( target, [ "Delay between readouts [ns]", "Assignment Fidelity", "Fidelity", "QND", "Effective Qubit Temperature [K]", ], [ np.round(data.delay), np.round(fit.assignment_fidelity[target], 6), np.round(fit.fidelity[target], 6), np.round(fit.qnd[target], 6), format_error_single_cell( round_report([fit.effective_temperature[target]]) ), ], ) ) return figures, fitting_report
[docs]def _update( results: ReadoutCharacterizationResults, platform: Platform, target: QubitId ): update.readout_fidelity(results.fidelity[target], platform, target) update.assignment_fidelity(results.assignment_fidelity[target], platform, target)
readout_characterization = Routine(_acquisition, _fit, _plot, _update) """ReadoutCharacterization Routine object."""