Source code for qibocal.protocols.flux_dependence.utils

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibolab.platform import Platform
from qibolab.qubits import QubitId

from ..utils import HZ_TO_GHZ


[docs]def is_crosstalk(data): """Check if keys are tuple which corresponds to crosstalk data structure.""" return all(isinstance(key, tuple) for key in data.data.keys())
[docs]def create_data_array(freq, bias, signal, phase, dtype): """Create custom dtype array for acquired data.""" size = len(freq) * len(bias) ar = np.empty(size, dtype=dtype) frequency, biases = np.meshgrid(freq, bias) ar["freq"] = frequency.ravel() ar["bias"] = biases.ravel() ar["signal"] = signal.ravel() ar["phase"] = phase.ravel() return np.rec.array(ar)
[docs]def flux_dependence_plot(data, fit, qubit, fit_function=None): figures = [] qubit_data = data[qubit] frequencies = qubit_data.freq * HZ_TO_GHZ subplot_titles = ( "Signal [a.u.]", "Phase [rad]", ) fig = make_subplots( rows=1, cols=2, horizontal_spacing=0.1, vertical_spacing=0.1, subplot_titles=subplot_titles, ) fig.add_trace( go.Heatmap( x=qubit_data.freq * HZ_TO_GHZ, y=qubit_data.bias, z=qubit_data.signal, colorbar_x=0.46, ), row=1, col=1, ) # TODO: This fit is for frequency, can it be reused here, do we even want the fit ? if ( fit is not None and not data.__class__.__name__ == "CouplerSpectroscopyData" and qubit in fit.fitted_parameters ): params = fit.fitted_parameters[qubit] bias = np.unique(qubit_data.bias) fig.add_trace( go.Scatter( x=fit_function(bias, **params), y=bias, showlegend=True, name="Fit", marker=dict(color="green"), ), row=1, col=1, ) fig.update_xaxes( title_text=f"Frequency [GHz]", row=1, col=1, ) fig.update_yaxes(title_text="Bias [V]", row=1, col=1) fig.add_trace( go.Heatmap( x=qubit_data.freq * HZ_TO_GHZ, y=qubit_data.bias, z=qubit_data.phase, colorbar_x=1.01, ), row=1, col=2, ) fig.update_xaxes( title_text=f"Frequency [GHz]", row=1, col=2, ) fig.update_layout(xaxis1=dict(range=[np.min(frequencies), np.max(frequencies)])) fig.update_layout( showlegend=True, legend=dict(orientation="h"), ) figures.append(fig) return figures
[docs]def flux_crosstalk_plot(data, qubit, fit, fit_function): figures = [] fitting_report = "" all_qubit_data = { index: data_qubit for index, data_qubit in data.data.items() if index[0] == qubit } fig = make_subplots( rows=1, cols=len(all_qubit_data), horizontal_spacing=0.3 / len(all_qubit_data), vertical_spacing=0.1, subplot_titles=len(all_qubit_data) * ("Signal [a.u.]",), ) for col, (flux_qubit, qubit_data) in enumerate(all_qubit_data.items()): frequencies = qubit_data.freq * HZ_TO_GHZ fig.add_trace( go.Heatmap( x=frequencies, y=qubit_data.bias, z=qubit_data.signal, showscale=False, ), row=1, col=col + 1, ) if fit is not None: if flux_qubit[1] != qubit and flux_qubit in fit.fitted_parameters: fig.add_trace( go.Scatter( x=fit_function( xj=qubit_data.bias, **fit.fitted_parameters[flux_qubit] ), y=qubit_data.bias, showlegend=not any( isinstance(trace, go.Scatter) for trace in fig.data ), legendgroup="Fit", name="Fit", marker=dict(color="green"), ), row=1, col=col + 1, ) elif flux_qubit in fit.fitted_parameters: diagonal_params = fit.fitted_parameters[qubit, qubit] fig.add_trace( go.Scatter( x=fit_function( qubit_data.bias, **diagonal_params, ), y=qubit_data.bias, showlegend=not any( isinstance(trace, go.Scatter) for trace in fig.data ), legendgroup="Fit", name="Fit", marker=dict(color="green"), ), row=1, col=col + 1, ) fig.update_xaxes( title_text="Frequency [GHz]", row=1, col=col + 1, ) fig.update_yaxes( title_text=f"Qubit {flux_qubit[1]}: Bias [V]", row=1, col=col + 1 ) fig.update_layout(xaxis1=dict(range=[np.min(frequencies), np.max(frequencies)])) fig.update_layout(xaxis2=dict(range=[np.min(frequencies), np.max(frequencies)])) fig.update_layout(xaxis3=dict(range=[np.min(frequencies), np.max(frequencies)])) fig.update_layout( showlegend=True, ) figures.append(fig) return figures, fitting_report
[docs]def G_f_d(xi, xj, offset, d, crosstalk_element, normalization): """Auxiliary function to calculate qubit frequency as a function of bias. It also determines the flux dependence of :math:`E_J`,:math:`E_J(\\phi)=E_J(0)G_f_d`. For more details see: https://arxiv.org/pdf/cond-mat/0703002.pdf Args: xi (float): bias of target qubit xj (float): bias of neighbor qubit offset (float): phase_offset [V]. matrix_element(float): diagonal crosstalk matrix element crosstalk_element(float): off-diagonal crosstalk matrix element d (float): asymmetry between the two junctions of the transmon. Typically denoted as :math:`d`. :math:`d = (E_J^1 - E_J^2) / (E_J^1 + E_J^2)`. normalization (float): Normalize diagonal element to 1 Returns: (float) """ return ( d**2 + (1 - d**2) * np.cos( np.pi * (xi * normalization + normalization * xj * crosstalk_element + offset) ) ** 2 ) ** 0.25
[docs]def transmon_frequency( xi, xj, w_max, d, normalization, offset, crosstalk_element, charging_energy ): r"""Approximation to transmon frequency. The formula holds in the transmon regime Ej / Ec >> 1. See https://arxiv.org/pdf/cond-mat/0703002.pdf for the complete formula. Args: xi (float): bias of target qubit xj (float): bias of neighbor qubit w_max (float): maximum frequency :math:`w_{max} = \sqrt{8 E_j E_c} sweetspot (float): sweetspot [V]. matrix_element(float): diagonal crosstalk matrix element crosstalk_element(float): off-diagonal crosstalk matrix element d (float): asymmetry between the two junctions of the transmon. Typically denoted as :math:`d`. :math:`d = (E_J^1 - E_J^2) / (E_J^1 + E_J^2)`. charging_energy (float): Ec / h (GHz) Returns: (float): qubit frequency as a function of bias. """ return (w_max + charging_energy) * G_f_d( xi, xj, offset=offset, d=d, normalization=normalization, crosstalk_element=crosstalk_element, ) - charging_energy
[docs]def transmon_readout_frequency( xi, xj, w_max, d, normalization, crosstalk_element, offset, resonator_freq, g, charging_energy, ): r"""Approximation to flux dependent resonator frequency. The formula holds in the transmon regime Ej / Ec >> 1. See https://arxiv.org/pdf/cond-mat/0703002.pdf for the complete formula. Args: xi (float): bias of target qubit xj (float): bias of neighbor qubit w_max (float): maximum frequency :math:`w_{max} = \sqrt{8 E_j E_c} sweetspot (float): sweetspot [V]. matrix_element(float): diagonal crosstalk matrix element crosstalk_element(float): off-diagonal crosstalk matrix element d (float): asymmetry between the two junctions of the transmon. Typically denoted as :math:`d`. :math:`d = (E_J^1 - E_J^2) / (E_J^1 + E_J^2)`. resonator_freq (float): bare resonator frequency [GHz] g (float): readout coupling. Returns: (float): resonator frequency as a function of bias. """ return resonator_freq + g**2 * G_f_d( xi, xj, offset=offset, d=d, normalization=normalization, crosstalk_element=crosstalk_element, ) / ( resonator_freq - transmon_frequency( xi=xi, xj=xj, w_max=w_max, d=d, normalization=normalization, offset=offset, crosstalk_element=crosstalk_element, charging_energy=charging_energy, ) )
[docs]def qubit_flux_dependence_fit_bounds(qubit_frequency: float): """Returns bounds for qubit flux fit.""" return ( [ qubit_frequency * HZ_TO_GHZ - 1, 0, -1, ], [ qubit_frequency * HZ_TO_GHZ + 1, np.inf, 1, ], )
[docs]def crosstalk_matrix(platform: Platform, qubits: list[QubitId]) -> np.ndarray: """Computing crosstalk matrix for number of qubits selected. The matrix returns has the following matrix element: (M)ij = qubits[i].crosstalk_matrix[qubits[j]] """ size = len(qubits) matrix = np.ones((size, size)) for i in range(size): for j in range(size): matrix[i, j] = platform.qubits[qubits[i]].crosstalk_matrix[qubits[j]] return matrix
[docs]def compensation_matrix(platform: Platform, qubits: list[QubitId]) -> np.ndarray: """Compensation matrix C computed as M C = diag(M') where M is the crosstalk matrix. For more details check: https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf 8.2.3 """ size = len(qubits) matrix = np.ones((size, size)) crosstalk = crosstalk_matrix(platform, qubits) for i in range(size): for j in range(size): if i == j: matrix[i, j] = 1 else: matrix[i, j] = -crosstalk[i, j] / crosstalk[i, i] return matrix
[docs]def invert_transmon_freq(target_freq: float, platform: Platform, qubit: QubitId): """Return right side of equation matrix * total_flux = f(target_freq). Target frequency shoudl be expressed in GHz. """ charging_energy = -platform.qubits[qubit].anharmonicity * HZ_TO_GHZ offset = ( -platform.qubits[qubit].sweetspot * platform.qubits[qubit].crosstalk_matrix[qubit] ) w_max = platform.qubits[qubit].drive_frequency * HZ_TO_GHZ d = platform.qubits[qubit].asymmetry angle = np.sqrt( 1 / (1 - d**2) * (((target_freq + charging_energy) / (w_max + charging_energy)) ** 4 - d**2) ) return 1 / np.pi * np.arccos(angle) - offset
[docs]def frequency_to_bias( target_freqs: dict[QubitId, float], platform: Platform ) -> np.ndarray: """Starting from set of target_freqs computes bias points using the compensation matrix.""" qubits = list(target_freqs) inverted_crosstalk_matrix = np.linalg.inv( crosstalk_matrix(platform, qubits) @ compensation_matrix(platform, qubits) ) transmon_freq = np.array( [ invert_transmon_freq(freq, platform, qubit) for qubit, freq in target_freqs.items() ] ) bias_array = inverted_crosstalk_matrix @ transmon_freq return {qubit: bias_array[index] for index, qubit in enumerate(qubits)}