diff --git a/src/qibocal/protocols/flux_dependence/resonator_crosstalk.py b/src/qibocal/protocols/flux_dependence/resonator_crosstalk.py index 44046c8b1..7a16456fa 100644 --- a/src/qibocal/protocols/flux_dependence/resonator_crosstalk.py +++ b/src/qibocal/protocols/flux_dependence/resonator_crosstalk.py @@ -10,10 +10,9 @@ 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 ... import update +from ...auto.operation import Routine +from ...config import log from ..utils import HZ_TO_GHZ, extract_feature, table_dict, table_html from . import utils from .resonator_flux_dependence import ( @@ -71,6 +70,10 @@ class ResCrosstalkData(ResonatorFluxData): """Readout resonator frequency for each qubit.""" matrix_element: dict[QubitId, float] = field(default_factory=dict) """Diagonal crosstalk matrix element.""" + offset: dict[QubitId, float] = field(default_factory=dict) + """Diagonal offset.""" + asymmetry: dict[QubitId, float] = field(default_factory=dict) + """Diagonal asymmetry.""" data: dict[tuple[QubitId, QubitId], npt.NDArray[ResFluxType]] = field( default_factory=dict ) @@ -87,13 +90,12 @@ def register_qubit(self, qubit, flux_qubit, freq, bias, signal, phase): self.data[qubit, flux_qubit] = ar @property - def diagonal(self) -> Optional[ResonatorFluxData]: + def diagonal(self) -> ResonatorFluxData: + """Returns diagonal data acquired.""" 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: @@ -104,32 +106,20 @@ def diagonal(self) -> Optional[ResonatorFluxData]: 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, - ) + return instance 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 = {} + asymmetry = {} charging_energy = {} bias_point = {} offset = {} @@ -140,6 +130,7 @@ def _acquisition( qubit, platform.qubits[qubit].sweetspot ) coupling[qubit] = platform.qubits[qubit].g + asymmetry[qubit] = platform.qubits[qubit].asymmetry matrix_element[qubit] = platform.qubits[qubit].crosstalk_matrix[qubit] offset[qubit] = -platform.qubits[qubit].sweetspot * matrix_element[qubit] bare_resonator_frequency[qubit] = platform.qubits[ @@ -147,7 +138,6 @@ def _acquisition( ].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]) @@ -185,6 +175,7 @@ def _acquisition( resonator_type=platform.resonator_type, qubit_frequency=qubit_frequency, offset=offset, + asymmetry=asymmetry, resonator_frequency=resonator_frequency, charging_energy=charging_energy, bias_point=bias_point, @@ -201,6 +192,7 @@ def _acquisition( 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 @@ -222,34 +214,46 @@ def _acquisition( def _fit(data: ResCrosstalkData) -> ResCrosstalkResults: - crosstalk_matrix = {qubit: {} for qubit in data.qubit_frequency} - fitted_parameters = {} + """ "PostProcessing for resonator crosstalk protocol.""" + + # perform first fit where corresponding qubit is moved diagonal = diagonal_fit(data.diagonal) + fitted_parameters = {} + crosstalk_matrix = {qubit: {} for qubit in data.qubit_frequency} + offset = {} coupling = {} - bare_resonator_frequency = {} + matrix_element = {} + asymmetry = {} resonator_frequency = {} resonator_frequency_bias_point = {} + for qubit in data.qubits: + + # retrieve parameters from diagonal fit if performed 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] + asymmetry[qubit] = ( + diagonal.asymmetry[qubit] if condition else data.asymmetry[qubit] + ) + matrix_element[qubit] = ( + diagonal.matrix_element[qubit] if condition else data.matrix_element[qubit] ) resonator_frequency[qubit] = ( diagonal.resonator_freq[qubit] if condition else data.resonator_frequency[qubit] ) + offset[qubit] = ( + diagonal.fitted_parameters[qubit]["offset"] + if condition + else data.offset[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, @@ -257,36 +261,37 @@ def _fit(data: ResCrosstalkData) -> ResCrosstalkResults: "min" if data.resonator_type == "2D" else "max", ) + # fit valid only for non-diagonal case + # (the diagonal case was handled before) 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, + d=asymmetry[target_qubit], w_max=data.qubit_frequency[target_qubit] * HZ_TO_GHZ, offset=data.offset[target_qubit], - normalization=data.matrix_element[target_qubit], + normalization=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, + resonator_freq=data.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): + def fit_function(x, crosstalk_element): 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, + offset=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, + resonator_freq=data.bare_resonator_frequency[target_qubit] + * HZ_TO_GHZ, crosstalk_element=crosstalk_element, ) @@ -295,17 +300,18 @@ def fit_function(x, crosstalk_element, offset): fit_function, biases, frequencies * HZ_TO_GHZ, - bounds=((-np.inf, -1), (np.inf, 1)), + bounds=(-1, 1), ) fitted_parameters[target_qubit, flux_qubit] = dict( xi=data.bias_point[qubit], - d=0, + d=asymmetry[qubit], w_max=data.qubit_frequency[target_qubit] * HZ_TO_GHZ, - offset=popt[1], + offset=offset[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, + resonator_freq=data.bare_resonator_frequency[target_qubit] + * HZ_TO_GHZ, crosstalk_element=float(popt[0]), ) crosstalk_matrix[target_qubit][flux_qubit] = ( @@ -319,12 +325,11 @@ def fit_function(x, crosstalk_element, offset): 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] + crosstalk_matrix[target_qubit][flux_qubit] = matrix_element[qubit] return ResCrosstalkResults( resonator_freq=resonator_frequency, - bare_resonator_freq=bare_resonator_frequency, + asymmetry=asymmetry, resonator_frequency_bias_point=resonator_frequency_bias_point, coupling=coupling, crosstalk_matrix=crosstalk_matrix, @@ -341,19 +346,14 @@ def _plot(data: ResCrosstalkData, fit: ResCrosstalkResults, target: QubitId): labels = [ "Resonator Frequency at Sweetspot [Hz]", "Coupling g [MHz]", - "Resonaor Frequency at Bias point [Hz]", - "Bare Resonator Frequency [Hz]", - "Chi [MHz]", + "Asymmetry d", + "Resonator Frequency at Bias point [Hz]", ] values = [ np.round(fit.resonator_freq[target], 4), np.round(fit.coupling[target] * 1e3, 2), + np.round(fit.asymmetry[target], 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: diff --git a/src/qibocal/protocols/flux_dependence/resonator_flux_dependence.py b/src/qibocal/protocols/flux_dependence/resonator_flux_dependence.py index e8ad497a8..f39abd054 100644 --- a/src/qibocal/protocols/flux_dependence/resonator_flux_dependence.py +++ b/src/qibocal/protocols/flux_dependence/resonator_flux_dependence.py @@ -10,10 +10,9 @@ from qibolab.sweeper import Parameter, Sweeper, SweeperType from scipy.optimize import curve_fit -from qibocal import update -from qibocal.auto.operation import Data, Parameters, Results, Routine -from qibocal.config import log - +from ... import update +from ...auto.operation import Data, Parameters, Results, Routine +from ...config import log from ..utils import GHZ_TO_HZ, HZ_TO_GHZ, extract_feature, table_dict, table_html from . import utils @@ -37,9 +36,15 @@ class ResonatorFluxResults(Results): """ResonatoFlux outputs.""" resonator_freq: dict[QubitId, float] = field(default_factory=dict) - bare_resonator_freq: dict[QubitId, float] = field(default_factory=dict) + """Readout frequency.""" coupling: dict[QubitId, float] = field(default_factory=dict) """Qubit-resonator coupling.""" + asymmetry: dict[QubitId, float] = field(default_factory=dict) + """Asymmetry between junctions.""" + sweetspot: dict[QubitId, float] = field(default_factory=dict) + """Sweetspot for each qubit.""" + matrix_element: dict[QubitId, float] = field(default_factory=dict) + """Sweetspot for each qubit.""" fitted_parameters: dict[QubitId, float] = field(default_factory=dict) @@ -62,12 +67,10 @@ class ResonatorFluxData(Data): """Resonator type.""" qubit_frequency: dict[QubitId, float] = field(default_factory=dict) """Qubit frequencies.""" - offset: dict[QubitId, float] = field(default_factory=dict) - """Qubit bias offset.""" bare_resonator_frequency: dict[QubitId, int] = field(default_factory=dict) """Qubit bare resonator frequency power provided by the user.""" - matrix_element: dict[QubitId, float] = field(default_factory=dict) charging_energy: dict[QubitId, float] = field(default_factory=dict) + """Qubit charging energy in Hz.""" data: dict[QubitId, npt.NDArray[ResFluxType]] = field(default_factory=dict) """Raw data acquired.""" @@ -83,24 +86,17 @@ def _acquisition( params: ResonatorFluxParameters, platform: Platform, targets: list[QubitId] ) -> ResonatorFluxData: """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 = {} qubit_frequency = {} bare_resonator_frequency = {} - offset = {} - matrix_element = {} charging_energy = {} for qubit in targets: qubit_frequency[qubit] = platform.qubits[qubit].drive_frequency bare_resonator_frequency[qubit] = platform.qubits[ qubit ].bare_resonator_frequency - matrix_element[qubit] = platform.qubits[qubit].crosstalk_matrix[qubit] - offset[qubit] = -platform.qubits[qubit].sweetspot * matrix_element[qubit] charging_energy[qubit] = -platform.qubits[qubit].anharmonicity ro_pulses[qubit] = platform.create_qubit_readout_pulse(qubit, start=0) sequence.add(ro_pulses[qubit]) @@ -131,8 +127,6 @@ def _acquisition( data = ResonatorFluxData( resonator_type=platform.resonator_type, qubit_frequency=qubit_frequency, - offset=offset, - matrix_element=matrix_element, bare_resonator_frequency=bare_resonator_frequency, charging_energy=charging_energy, ) @@ -159,67 +153,80 @@ def _acquisition( def _fit(data: ResonatorFluxData) -> ResonatorFluxResults: - """ - Post-processing for QubitFlux Experiment. See arxiv:0703002 - Fit frequency as a function of current for the flux qubit spectroscopy - data (QubitFluxData): data object with information on the feature response at each current point. + """PostProcessing for resonator_flux protocol. + + After applying a mask on the 2D data, the signal is fitted using + the expected resonator_freq vs flux behavior. + The fitting procedure requires the knowledge of the bare resonator frequency, + the charging energy Ec and the maximum qubit frequency which is assumed to be + the frequency at which the qubit is placed. + The protocol aims at extracting the sweetspot, the flux coefficient, the coupling, + the asymmetry and the dressed resonator frequency. """ - qubits = data.qubits coupling = {} resonator_freq = {} - bare_resonator_freq = {} + asymmetry = {} fitted_parameters = {} - for qubit in qubits: + sweetspot = {} + matrix_element = {} + + for qubit in data.qubits: qubit_data = data[qubit] biases = qubit_data.bias frequencies = qubit_data.freq signal = qubit_data.signal + + # extract signal from 2D plot based on SNR mask frequencies, biases = extract_feature( frequencies, biases, signal, "min" if data.resonator_type == "2D" else "max" ) - def fit_function(x, g, resonator_freq): + # define fit function + def fit_function( + x: float, g: float, d: float, offset: float, normalization: float + ): + """Fit function for resonator flux dependence.""" return utils.transmon_readout_frequency( xi=x, w_max=data.qubit_frequency[qubit] * HZ_TO_GHZ, xj=0, - d=0, - normalization=data.matrix_element[qubit], - offset=data.offset[qubit], + d=d, + normalization=normalization, + offset=offset, crosstalk_element=1, charging_energy=data.charging_energy[qubit] * HZ_TO_GHZ, - resonator_freq=resonator_freq, + resonator_freq=data.bare_resonator_frequency[qubit] * HZ_TO_GHZ, g=g, ) try: - popt, perr = curve_fit( + popt, _ = curve_fit( fit_function, biases, frequencies * HZ_TO_GHZ, bounds=( - [0, data.bare_resonator_frequency[qubit] * HZ_TO_GHZ - 0.2], - [0.5, data.bare_resonator_frequency[qubit] * HZ_TO_GHZ + 0.2], + [0, 0, -1, 0.5], + [0.5, 1, 1, +1], ), maxfev=100000, ) - fitted_parameters[qubit] = { "w_max": data.qubit_frequency[qubit] * HZ_TO_GHZ, "xj": 0, - "d": 0, - "normalization": data.matrix_element[qubit], - "offset": data.offset[qubit], + "d": popt[1], + "normalization": popt[3], + "offset": popt[2], "crosstalk_element": 1, "charging_energy": data.charging_energy[qubit] * HZ_TO_GHZ, - "resonator_freq": popt[1], + "resonator_freq": data.bare_resonator_frequency[qubit] * HZ_TO_GHZ, "g": popt[0], } - sweetspot = -data.offset[qubit] / data.matrix_element[qubit] - resonator_freq[qubit] = fit_function(sweetspot, *popt) * GHZ_TO_HZ - coupling[qubit] = popt[0] * GHZ_TO_HZ - bare_resonator_freq[qubit] = popt[1] * GHZ_TO_HZ + matrix_element[qubit] = popt[3] + sweetspot[qubit] = (np.round(popt[2]) - popt[2]) / popt[3] + resonator_freq[qubit] = fit_function(sweetspot[qubit], *popt) * GHZ_TO_HZ + coupling[qubit] = popt[0] + asymmetry[qubit] = popt[1] except ValueError as e: log.error( f"Error in resonator_flux protocol fit: {e} " @@ -229,8 +236,10 @@ def fit_function(x, g, resonator_freq): ) return ResonatorFluxResults( resonator_freq=resonator_freq, - bare_resonator_freq=bare_resonator_freq, coupling=coupling, + matrix_element=matrix_element, + sweetspot=sweetspot, + asymmetry=asymmetry, fitted_parameters=fitted_parameters, ) @@ -247,16 +256,23 @@ def _plot(data: ResonatorFluxData, fit: ResonatorFluxResults, target: QubitId): target, [ "Coupling g [MHz]", - "Bare resonator freq [Hz]", "Dressed resonator freq [Hz]", + "Asymmetry", + "Sweetspot [V]", + "Flux dependence [V]^-1", "Chi [MHz]", ], [ np.round(fit.coupling[target] * 1e3, 2), - np.round(fit.bare_resonator_freq[target], 6), np.round(fit.resonator_freq[target], 6), + np.round(fit.asymmetry[target], 3), + np.round(fit.sweetspot[target], 4), + np.round(fit.matrix_element[target], 4), np.round( - (fit.bare_resonator_freq[target] - fit.resonator_freq[target]) + ( + data.bare_resonator_frequency[target] + - fit.resonator_freq[target] + ) * 1e-6, 2, ), @@ -268,9 +284,11 @@ def _plot(data: ResonatorFluxData, fit: ResonatorFluxResults, target: QubitId): def _update(results: ResonatorFluxResults, platform: Platform, qubit: QubitId): - update.bare_resonator_frequency(results.bare_resonator_freq[qubit], platform, qubit) update.readout_frequency(results.resonator_freq[qubit], platform, qubit) update.coupling(results.coupling[qubit], platform, qubit) + update.asymmetry(results.coupling[qubit], platform, qubit) + update.sweetspot(results.sweetspot[qubit], platform, qubit) + update.crosstalk_matrix(results.matrix_element[qubit], platform, qubit, qubit) resonator_flux = Routine(_acquisition, _fit, _plot, _update)