Source code for qibocal.protocols.flux_dependence.resonator_crosstalk

from dataclasses import dataclass, field
from typing import Optional

import numpy as np
import numpy.typing as npt
from qibolab import AcquisitionType, AveragingMode, ExecutionParameters
from qibolab.platform import Platform
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId
from qibolab.sweeper import Parameter, Sweeper, SweeperType
from scipy.optimize import curve_fit

from qibocal import update
from qibocal.auto.operation import Routine
from qibocal.config import log

from ..utils import HZ_TO_GHZ, extract_feature, table_dict, table_html
from . import utils
from .resonator_flux_dependence import (
    ResFluxType,
    ResonatorFluxData,
    ResonatorFluxParameters,
    ResonatorFluxResults,
)
from .resonator_flux_dependence import _fit as diagonal_fit


[docs]@dataclass class ResCrosstalkParameters(ResonatorFluxParameters): """ResonatorFlux runcard inputs.""" bias_point: Optional[dict[QubitId, float]] = field(default_factory=dict) """Dictionary with {qubit_id: bias_point_qubit_id}.""" flux_qubits: Optional[list[QubitId]] = None """IDs of the qubits that we will sweep the flux on. If ``None`` flux will be swept on all qubits that we are running the routine on in a multiplex fashion. If given flux will be swept on the given qubits in a sequential fashion (n qubits will result to n different executions). Multiple qubits may be measured in each execution as specified by the ``qubits`` option in the runcard. """
[docs]@dataclass class ResCrosstalkResults(ResonatorFluxResults): """ResCrosstalk outputs.""" resonator_frequency_bias_point: dict[QubitId, dict[QubitId, float]] = field( default_factory=dict ) """Resonator frequency at bias point.""" crosstalk_matrix: dict[QubitId, dict[QubitId, float]] = field(default_factory=dict) """Crosstalk matrix element.""" fitted_parameters: dict[tuple[QubitId, QubitId], dict] = field(default_factory=dict) """Fitted parameters for each couple target-flux qubit.""" def __contains__(self, key: QubitId): """Checking if qubit is in crosstalk_matrix attribute.""" return key in self.crosstalk_matrix
[docs]@dataclass class ResCrosstalkData(ResonatorFluxData): """ResFlux acquisition outputs when ``flux_qubits`` are given.""" coupling: dict[QubitId, float] = field(default_factory=dict) """Coupling parameter g for each qubit.""" bias_point: dict[QubitId, float] = field(default_factory=dict) """Voltage provided to each qubit.""" bare_resonator_frequency: dict[QubitId, float] = field(default_factory=dict) """Readout resonator frequency for each qubit.""" resonator_frequency: dict[QubitId, float] = field(default_factory=dict) """Readout resonator frequency for each qubit.""" matrix_element: dict[QubitId, float] = field(default_factory=dict) """Diagonal crosstalk matrix element.""" data: dict[tuple[QubitId, QubitId], npt.NDArray[ResFluxType]] = field( default_factory=dict ) """Raw data acquired for (qubit, qubit_flux) pairs saved in nested dictionaries."""
[docs] def register_qubit(self, qubit, flux_qubit, freq, bias, signal, phase): """Store output for single qubit.""" ar = utils.create_data_array(freq, bias, signal, phase, dtype=ResFluxType) if (qubit, flux_qubit) in self.data: self.data[qubit, flux_qubit] = np.rec.array( np.concatenate((self.data[qubit, flux_qubit], ar)) ) else: self.data[qubit, flux_qubit] = ar
@property def diagonal(self) -> Optional[ResonatorFluxData]: instance = ResonatorFluxData( resonator_type=self.resonator_type, qubit_frequency=self.qubit_frequency, offset=self.offset, bare_resonator_frequency=self.bare_resonator_frequency, matrix_element=self.matrix_element, charging_energy=self.charging_energy, ) for qubit in self.qubits: try: instance.data[qubit] = self.data[qubit, qubit] except KeyError: log.info( f"Diagonal acquisition not found for qubit {qubit}. Runcard values will be used to perform the off-diagonal fit." ) if len(instance.data) > 0: return instance return ResonatorFluxData( resonator_type=self.resonator_type, qubit_frequency=self.qubit_frequency, offset=self.offset, bare_resonator_frequency=self.bare_resonator_frequency, matrix_element=self.matrix_element, charging_energy=self.charging_energy, )
[docs]def _acquisition( params: ResCrosstalkParameters, platform: Platform, targets: list[QubitId] ) -> ResCrosstalkData: """Data acquisition for ResonatorFlux experiment.""" # create a sequence of pulses for the experiment: # MZ # taking advantage of multiplexing, apply the same set of gates to all qubits in parallel sequence = PulseSequence() ro_pulses = {} bare_resonator_frequency = {} resonator_frequency = {} qubit_frequency = {} coupling = {} charging_energy = {} bias_point = {} offset = {} matrix_element = {} for qubit in targets: charging_energy[qubit] = -platform.qubits[qubit].anharmonicity bias_point[qubit] = params.bias_point.get( qubit, platform.qubits[qubit].sweetspot ) coupling[qubit] = platform.qubits[qubit].g matrix_element[qubit] = platform.qubits[qubit].crosstalk_matrix[qubit] offset[qubit] = -platform.qubits[qubit].sweetspot * matrix_element[qubit] bare_resonator_frequency[qubit] = platform.qubits[ qubit ].bare_resonator_frequency qubit_frequency[qubit] = platform.qubits[qubit].drive_frequency resonator_frequency[qubit] = platform.qubits[qubit].readout_frequency ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=0) sequence.add(ro_pulses[qubit]) # define the parameters to sweep and their range: delta_frequency_range = np.arange( -params.freq_width / 2, params.freq_width / 2, params.freq_step ) freq_sweeper = Sweeper( Parameter.frequency, delta_frequency_range, [ro_pulses[qubit] for qubit in targets], type=SweeperType.OFFSET, ) if params.flux_qubits is None: flux_qubits = list(platform.qubits) else: flux_qubits = params.flux_qubits delta_bias_range = np.arange( -params.bias_width / 2, params.bias_width / 2, params.bias_step ) sequences = [sequence] * len(flux_qubits) sweepers = [ Sweeper( Parameter.bias, delta_bias_range, qubits=[platform.qubits[flux_qubit]], type=SweeperType.OFFSET, ) for flux_qubit in flux_qubits ] data = ResCrosstalkData( resonator_type=platform.resonator_type, qubit_frequency=qubit_frequency, offset=offset, resonator_frequency=resonator_frequency, charging_energy=charging_energy, bias_point=bias_point, matrix_element=matrix_element, coupling=coupling, bare_resonator_frequency=bare_resonator_frequency, ) options = ExecutionParameters( nshots=params.nshots, relaxation_time=params.relaxation_time, acquisition_type=AcquisitionType.INTEGRATION, averaging_mode=AveragingMode.CYCLIC, ) for qubit in targets: if qubit in params.bias_point: platform.qubits[qubit].flux.offset = params.bias_point[qubit] for flux_qubit, bias_sweeper, sequence in zip(flux_qubits, sweepers, sequences): results = platform.sweep(sequence, options, bias_sweeper, freq_sweeper) # retrieve the results for every qubit for qubit in targets: result = results[ro_pulses[qubit].serial] if flux_qubit is None: sweetspot = platform.qubits[qubit].flux.offset else: sweetspot = platform.qubits[flux_qubit].flux.offset data.register_qubit( qubit, flux_qubit, signal=result.magnitude, phase=result.phase, freq=delta_frequency_range + ro_pulses[qubit].frequency, bias=delta_bias_range + sweetspot, ) return data
[docs]def _fit(data: ResCrosstalkData) -> ResCrosstalkResults: crosstalk_matrix = {qubit: {} for qubit in data.qubit_frequency} fitted_parameters = {} diagonal = diagonal_fit(data.diagonal) coupling = {} bare_resonator_frequency = {} resonator_frequency = {} resonator_frequency_bias_point = {} for qubit in data.qubits: condition = qubit in diagonal coupling[qubit] = ( diagonal.coupling[qubit] if condition else data.coupling[qubit] ) bare_resonator_frequency[qubit] = ( diagonal.bare_resonator_freq[qubit] if condition else data.bare_resonator_frequency[qubit] ) resonator_frequency[qubit] = ( diagonal.resonator_freq[qubit] if condition else data.resonator_frequency[qubit] ) for target_flux_qubit, qubit_data in data.data.items(): target_qubit, flux_qubit = target_flux_qubit frequencies, biases = extract_feature( qubit_data.freq, qubit_data.bias, qubit_data.signal, "min" if data.resonator_type == "2D" else "max", ) if target_qubit != flux_qubit: resonator_frequency_bias_point[target_qubit] = ( utils.transmon_readout_frequency( xi=data.bias_point[target_qubit], xj=0, d=0, w_max=data.qubit_frequency[target_qubit] * HZ_TO_GHZ, offset=data.offset[target_qubit], normalization=data.matrix_element[target_qubit], charging_energy=data.charging_energy[target_qubit] * HZ_TO_GHZ, g=coupling[target_qubit], resonator_freq=bare_resonator_frequency[target_qubit] * HZ_TO_GHZ, crosstalk_element=1, ) ) # fit function needs to be defined here to pass correct parameters # at runtime def fit_function(x, crosstalk_element, offset): return utils.transmon_readout_frequency( xi=data.bias_point[target_qubit], xj=x, d=0, w_max=data.qubit_frequency[target_qubit] * HZ_TO_GHZ, offset=offset, normalization=data.matrix_element[target_qubit], charging_energy=data.charging_energy[target_qubit] * HZ_TO_GHZ, g=coupling[target_qubit], resonator_freq=bare_resonator_frequency[target_qubit] * HZ_TO_GHZ, crosstalk_element=crosstalk_element, ) try: popt, _ = curve_fit( fit_function, biases, frequencies * HZ_TO_GHZ, bounds=((-np.inf, -1), (np.inf, 1)), ) fitted_parameters[target_qubit, flux_qubit] = dict( xi=data.bias_point[qubit], d=0, w_max=data.qubit_frequency[target_qubit] * HZ_TO_GHZ, offset=popt[1], normalization=data.matrix_element[target_qubit], charging_energy=data.charging_energy[target_qubit] * HZ_TO_GHZ, g=coupling[target_qubit], resonator_freq=bare_resonator_frequency[target_qubit] * HZ_TO_GHZ, crosstalk_element=float(popt[0]), ) crosstalk_matrix[target_qubit][flux_qubit] = ( popt[0] * data.matrix_element[target_qubit] ) except ValueError as e: log.error( f"Off-diagonal flux fit failed for qubit {flux_qubit} due to {e}." ) else: fitted_parameters[target_qubit, flux_qubit] = diagonal.fitted_parameters[ target_qubit ] # TODO: to be fixed crosstalk_matrix[target_qubit][flux_qubit] = data.matrix_element[qubit] return ResCrosstalkResults( resonator_freq=resonator_frequency, bare_resonator_freq=bare_resonator_frequency, resonator_frequency_bias_point=resonator_frequency_bias_point, coupling=coupling, crosstalk_matrix=crosstalk_matrix, fitted_parameters=fitted_parameters, )
[docs]def _plot(data: ResCrosstalkData, fit: ResCrosstalkResults, target: QubitId): """Plotting function for ResonatorFlux Experiment.""" figures, fitting_report = utils.flux_crosstalk_plot( data, target, fit, fit_function=utils.transmon_readout_frequency ) if fit is not None: labels = [ "Resonator Frequency at Sweetspot [Hz]", "Coupling g [MHz]", "Resonaor Frequency at Bias point [Hz]", "Bare Resonator Frequency [Hz]", "Chi [MHz]", ] values = [ np.round(fit.resonator_freq[target], 4), np.round(fit.coupling[target] * 1e3, 2), np.round(fit.resonator_frequency_bias_point[target], 4), np.round(fit.bare_resonator_freq[target], 4), np.round( (fit.bare_resonator_freq[target] - fit.resonator_freq[target]) * 1e-6, 2, ), ] for flux_qubit in fit.crosstalk_matrix[target]: if flux_qubit != target: labels.append(f"Crosstalk with qubit {flux_qubit}") else: labels.append(f"Flux dependence") values.append(np.round(fit.crosstalk_matrix[target][flux_qubit], 4)) fitting_report = table_html( table_dict( target, labels, values, ) ) return figures, fitting_report
[docs]def _update(results: ResCrosstalkResults, platform: Platform, qubit: QubitId): """Update crosstalk matrix.""" for flux_qubit, element in results.crosstalk_matrix[qubit].items(): update.crosstalk_matrix(element, platform, qubit, flux_qubit)
resonator_crosstalk = Routine(_acquisition, _fit, _plot, _update) """Resonator crosstalk Routine object"""