Source code for squadds.calcs.transmon_cross

"""
#!TODO: Generalize the half-wave cavity method usage
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import psutil
from joblib import Parallel, delayed
from numba import jit, prange
from pyEPR.calcs import Convert
from scipy.constants import Planck, e, h, hbar, pi
from scqubits.core.transmon import Transmon

from squadds.calcs.qubit import QubitHamiltonian

"""
========================================================
Constants
========================================================
"""
# constants
ϕ0 = hbar / (2 * e)   # Flux quantum (Weber)

"""
========================================================
Numba decorated methods
========================================================
"""

[docs] @jit(nopython=True) def Ec_from_Cs(Cs): """ Calculate the charging energy (Ec) in GHz from the capacitance (Cs) in fF. """ Cs_SI = Cs * 1e-15 Ec_Joules = (e ** 2) / (2 * Cs_SI) Ec_Hz = Ec_Joules / Planck Ec_GHz = Ec_Hz * 1e-9 return Ec_GHz
[docs] @jit(nopython=True) def EC_numba(cross_to_claw, cross_to_ground): C_eff_fF = np.abs(cross_to_ground) + np.abs(cross_to_claw) EC = Ec_from_Cs(C_eff_fF) return EC
[docs] @jit(nopython=True, parallel=True) def g_from_cap_matrix_numba(C, C_c, EJ, f_r, res_type, Z0=50): """ !TODO: resolve the error : \"The keyword argument 'parallel=True' was specified but no transformation for parallel execution was possible.\" for this method """ C = np.abs(C) C_c = np.abs(C_c) C_q = C_c + C # fF omega_r = 2 * np.pi * f_r * 1e9 EC = Ec_from_Cs(C_q) res_type_factor = 2 if res_type == "half" else 4 if res_type == "quarter" else 1 g = (np.abs(C_c) / C_q) * omega_r * np.sqrt(res_type_factor * Z0 * e ** 2 / (hbar * np.pi)) * (EJ / (8 * EC)) ** (1 / 4) return (g * 1E-6) / (2 * np.pi) # MHz
[docs] class TransmonCrossHamiltonian(QubitHamiltonian): """ Class representing the Hamiltonian for a transmon qubit in a cross-coupled configuration. """ def __init__(self, analysis): """ Initialize the TransmonCrossHamiltonian object. Attributes: - analysis: The analysis object associated with the Hamiltonian. """ import scqubits as scq super().__init__(analysis) self.selected_resonator_type = analysis.selected_resonator_type scq.set_units("GHz")
[docs] def plot_data(self, data_frame): """ Plot the data from the given DataFrame. Args: - data_frame: The DataFrame containing the data to be plotted. """ data_frame.plot(kind='box', subplots=True, layout=(1, 3), sharex=False, sharey=False) plt.show()
[docs] def EC(self, cross_to_claw, cross_to_ground): """ Calculate the charging energy (EC) of the transmon qubit. Args: - cross_to_claw: Capacitance between the cross and the claw. - cross_to_ground: Capacitance between the cross and the ground. Returns: - EC: The charging energy of the transmon qubit. """ C_eff_fF = abs(cross_to_ground) + abs(cross_to_claw) EC = Convert.Ec_from_Cs(C_eff_fF, units_in='fF',units_out='GHz') return EC
def _calculate_target_qubit_params(self, w_q, alpha, Z_0=50): """ Calculate the target qubit parameters (EJ, EC, EJEC, Lj) based on the given qubit frequency (w_q) and anharmonicity (alpha). Args: - w_q: The qubit frequency. - alpha: The anharmonicity of the qubit. Returns: - EJ: The Josephson energy of the qubit. - EC: The charging energy of the qubit. - EJEC: The ratio of EJ to EC. - Lj: The Josephson inductance of the qubit. """ EJ, EC = Transmon.find_EJ_EC(w_q, alpha) EJEC = EJ / EC Lj = Convert.Lj_from_Ej(EJ, units_in='GHz', units_out='nH') self.EJ = EJ self.EC = EC self.EJEC = EJEC self.Lj = Lj return EJ, EC, EJEC, Lj
[docs] def EJ_and_LJ(self, w_q, alpha, *args, **kwargs): """ Calculate the Josephson energy (EJ) and Josephson inductance (Lj) based on the given qubit frequency (w_q) and anharmonicity (alpha). Args: - w_q: The qubit frequency. - alpha: The anharmonicity of the qubit. Returns: - EJ: The Josephson energy of the qubit. - Lj: The Josephson inductance of the qubit. """ EJ, EC = Transmon.find_EJ_EC(w_q, alpha) Lj = Convert.Lj_from_Ej(EJ, units_in='GHz', units_out='nH') self.EJ = EJ self.Lj = Lj return EJ, Lj
[docs] def EJ(self, w_q, alpha): """ Calculate the Josephson energy (EJ) based on the given qubit frequency (w_q) and anharmonicity (alpha). Args: - w_q: The qubit frequency. - alpha: The anharmonicity of the qubit. Returns: - EJ: The Josephson energy of the qubit. """ EJ, EC = Transmon.find_EJ_EC(w_q, alpha) self.EJ = EJ return EJ
[docs] def calculate_target_quantities(self, f_res, alpha, g, w_q, res_type, Z_0=50): """ Calculate the target quantities (C_q, C_c, EJ, EC, EJ_EC_ratio) based on the given parameters. Args: - f_res: The resonator frequency. - alpha: The anharmonicity of the qubit. - g: The coupling strength between the qubit and the resonator. - w_q: The qubit frequency. - res_type: The type of resonator. - Z_0: The characteristic impedance of the resonator. Returns: - C_q: The total capacitance of the qubit. - C_c: The coupling capacitance between the qubit and the resonator. - EJ: The Josephson energy of the qubit. - EC: The charging energy of the qubit. - EJ_EC_ratio: The ratio of EJ to EC. """ EJ, EC = Transmon.find_EJ_EC(w_q, alpha) C_q = Convert.Cs_from_Ec(EC, units_in='GHz', units_out='fF') omega_r = 2 * np.pi * f_res if res_type == "half": res_type = 2 elif res_type == "quarter": res_type = 4 else: raise ValueError("res_type must be either 'half' or 'quarter'") prefactor = np.sqrt(res_type * Z_0 * e**2 / (hbar * np.pi)) * (EJ / (8 * EC))**(1/4) denominator = omega_r * prefactor numerator = g * C_q C_c = numerator / denominator EJ_EC_ratio = EJ / EC return C_q, C_c, EJ, EC, EJ_EC_ratio
[docs] def g_and_alpha(self, C, C_c, f_q, EJ, f_r, res_type, Z0=50): """ Calculate the coupling strength (g) and anharmonicity (alpha) based on the given parameters. Args: - C: The capacitance between the qubit and the ground. - C_c: The coupling capacitance between the qubit and the resonator. - f_q: The qubit frequency. - EJ: The Josephson energy of the qubit. - f_r: The resonator frequency. - res_type: The type of resonator. - Z0: The characteristic impedance of the resonator. Returns: - g: The coupling strength between the qubit and the resonator. - alpha: The anharmonicity of the qubit. """ C, C_c = abs(C) * 1e-15, abs(C_c) * 1e-15 C_q = C + C_c g = self.g_from_cap_matrix(C, C_c, EJ, f_r, res_type, Z0) EC = Convert.Ec_from_Cs(C_q, units_in='F', units_out='GHz') transmon = Transmon(EJ=EJ, EC=EC, ng=0, ncut=30) alpha = transmon.anharmonicity() * 1E3 # MHz return g, alpha
[docs] def g_alpha_freq(self, C, C_c, EJ, f_r, res_type, Z0=50): """ Calculate the coupling strength, anharmonicity, and transition frequency of a transmon qubit. Args: - C (float): Total capacitance of the transmon qubit. - C_c (float): Coupling capacitance between the transmon qubit and the resonator. - EJ (float): Josephson energy of the transmon qubit. - f_r (float): Resonator frequency. - res_type (str): Type of resonator. Must be either 'half' or 'quarter'. - Z0 (float, optional): Characteristic impedance of the transmission line. Defaults to 50. Returns: - (g, alpha, freq) (tuple): A tuple containing the coupling strength (g), anharmonicity (alpha), and transition frequency (freq). """ import scqubits as scq scq.set_units("GHz") C_q = C + C_c if res_type == "half": res_type = 2 elif res_type == "quarter": res_type = 4 else: raise ValueError("res_type must be either 'half' or 'quarter'") g = self.g_from_cap_matrix(C, C_c, EJ, f_r, res_type, Z0) EC = Convert.Ec_from_Cs(C_q, units_in='fF', units_out='GHz') transmon = Transmon(EJ=EJ, EC=EC, ng=0, ncut=30) alpha = transmon.anharmonicity() * 1E3 # MHz freq = transmon.E01() return g, alpha, freq
[docs] def g_from_cap_matrix(self, C, C_c, EJ, f_r, res_type, Z0=50): """ Calculate the coupling strength 'g' between a transmon qubit and a resonator based on the capacitance matrix. Args: - C (float): Capacitance between the qubit and the resonator (in femtofarads, fF). - C_c (float): Coupling capacitance of the resonator (in femtofarads, fF). - EJ (float): Josephson energy of the qubit (in GHz). - f_r (float): Resonator frequency (in GHz). - res_type (str): Type of resonator. Can be 'half' or 'quarter'. - Z0 (float, optional): Characteristic impedance of the transmission line (in ohms). Default is 50 ohms. Returns: - g (float): Coupling strength 'g' between the qubit and the resonator (in MHz). """ C = abs(C) * 1e-15 # F C_c = abs(C_c) * 1e-15 # F C_q = C_c + C omega_r = 2 * np.pi * f_r * 1e9 EC = Convert.Ec_from_Cs(C_q, units_in='F', units_out='GHz') if res_type == "half": res_type = 2 elif res_type == "quarter": res_type = 4 g = (abs(C_c) / C_q) * omega_r * np.sqrt(res_type * Z0 * e**2 / (hbar * np.pi)) * (EJ / (8 * EC))**(1/4) return (g * 1E-6) / (2 * np.pi) # MHz
[docs] def get_freq_alpha_fixed_LJ(self, fig4_df, LJ_target): """ Calculate the frequencies and anharmonicities of transmons with a fixed LJ value. Args: - fig4_df: DataFrame containing the values of EC for different transmons. - LJ_target: Target value of LJ in nH. Returns: - freq: List of frequencies of the transmons. - alpha: List of anharmonicities of the transmons. """ EJ = Convert.Ej_from_Lj(LJ_target, units_in='nH', units_out='GHz') EC = fig4_df["EC"].values transmons = [Transmon(EJ=EJ, EC=EC[i], ng=0, ncut=30) for i in range(len(EC))] alpha = [transmon.anharmonicity() * 1E3 for transmon in transmons] freq = [transmon.E01() for transmon in transmons] return freq, alpha
[docs] def E01_and_anharmonicity(self, EJ, EC, ng=0, ncut=30): """ Calculate the energy of the first excited state (E01) and the anharmonicity (alpha) of a transmon qubit. Args: - EJ (float): Josephson energy of the transmon qubit. - EC (float): Charging energy of the transmon qubit. - ng (float, optional): Offset charge on the transmon qubit. Defaults to 0. - ncut (int, optional): Truncation level for the transmon qubit's Hilbert space. Defaults to 30. Returns: - E01 (float): Energy of the first excited state (E01) in GHz. - alpha (float): Anharmonicity (alpha) in MHz. """ transmon = Transmon(EJ=EJ, EC=EC, ng=ng, ncut=ncut) E01 = transmon.E01() alpha = transmon.anharmonicity() * 1E3 # MHz return E01, alpha
[docs] def E01(self, EJ, EC, ng=0, ncut=30): """ Calculate the energy of the first excited state (E01) of a transmon qubit. Args: - EJ (float): Josephson energy of the transmon qubit. - EC (float): Charging energy of the transmon qubit. - ng (float, optional): Offset charge on the transmon qubit. Default is 0. - ncut (int, optional): Truncation level for the transmon qubit's Hilbert space. Default is 30. Returns: - E01 (float): Energy of the first excited state (E01) of the transmon qubit. """ transmon = Transmon(EJ=EJ, EC=EC, ng=ng, ncut=ncut) E01 = transmon.E01() return E01
[docs] def add_qubit_H_params(self): """ Add qubit Hamiltonian parameters to the DataFrame. This method calculates and adds the qubit Hamiltonian parameters, such as EC, EJ, and EJEC, to the DataFrame. Args: None Returns: None """ EJ_target = self.EJ(self.target_params["qubit_frequency_GHz"], self.target_params["anharmonicity_MHz"] * 1e-3) self.df["EC"] = self.df.apply(lambda row: self.EC(row["cross_to_claw"], row["cross_to_ground"]), axis=1) self.df['EJ'] = EJ_target self.df['qubit_frequency_GHz'], self.df['anharmonicity_MHz'] = zip(*self.df.apply(lambda row: self.E01_and_anharmonicity(row['EJ'], row['EC']), axis=1))
[docs] def add_qubit_H_params_chunk(self, df): """ Add qubit Hamiltonian parameters to the DataFrame chunk. This method calculates and adds the qubit Hamiltonian parameters, such as EC, EJ, and EJEC, Args: - df: The DataFrame chunk to which the parameters will be added. Returns: - df: The DataFrame chunk with the added parameters """ EJ_target = self.EJ(self.target_params["qubit_frequency_GHz"], self.target_params["anharmonicity_MHz"] * 1e-3) EJ_target = np.float32(EJ_target) # Ensure memory-efficient data type cross_to_claw_values = df["cross_to_claw"].values cross_to_ground_values = df["cross_to_ground"].values EC_values = np.array([EC_numba(claw, ground) for claw, ground in zip(cross_to_claw_values, cross_to_ground_values)], dtype=np.float32) df["EC"] = EC_values df['EJ'] = EJ_target df['qubit_frequency_GHz'], df['anharmonicity_MHz'] = np.vectorize(self.E01_and_anharmonicity)(df['EJ'].values, df['EC'].values) df['qubit_frequency_GHz'] = df['qubit_frequency_GHz'].astype(np.float32) df['anharmonicity_MHz'] = df['anharmonicity_MHz'].astype(np.float32) return df
[docs] def add_cavity_coupled_H_params(self, num_chunks="auto",Z_0=50): """ Add cavity-coupled Hamiltonian parameters to the dataframe. This method calculates the coupling strength 'g_MHz' between the transmon qubit and the cavity, based on the capacitance matrix, transmon parameters, cavity frequency, resonator type, and characteristic impedance. Args: - num_chunks: The number of chunks to split the DataFrame into for parallel processing. Default is "auto" which sets the number of chunks to the number of logical CPUs. - Z_0: The characteristic impedance of the transmission line. Default is 50 ohms. Returns: None """ if self.selected_resonator_type == "half": if num_chunks == "auto": num_chunks = psutil.cpu_count(logical=True) elif num_chunks > psutil.cpu_count(logical=True): raise ValueError(f"num_chunks must be less than or equal to {psutil.cpu_count(logical=True)}") else: num_chunks = 2 raise UserWarning("`num_chunk`s must be an integer greater than 0. Defaulting to 2.") print(f"Using {num_chunks} chunks for parallel processing") self.df = self.parallel_process_dataframe(self.df, num_chunks) else: self.add_qubit_H_params() self.df['g_MHz'] = self.df.apply(lambda row: self.g_from_cap_matrix(C=row['cross_to_ground'], C_c=row['cross_to_claw'], EJ=row['EJ'], f_r=row['cavity_frequency_GHz'], res_type=row['resonator_type'], Z0=50), axis=1)
[docs] def add_cavity_coupled_H_params_chunk(self, chunk, Z_0=50): """ Add cavity-coupled Hamiltonian parameters to the DataFrame chunk. This method calculates the coupling strength 'g_MHz' between the transmon qubit and the cavity, Args: - chunk: The DataFrame chunk to which the parameters will be added. - Z_0: The characteristic impedance of the transmission line. Default is 50 ohms. Returns: - chunk: The DataFrame chunk with the added parameters. """ chunk = self.add_qubit_H_params_chunk(chunk) cross_to_ground_values = chunk['cross_to_ground'].values cross_to_claw_values = chunk['cross_to_claw'].values EJ_values = chunk['EJ'].values cavity_frequency_values = chunk['cavity_frequency_GHz'].values g_values = np.array([g_from_cap_matrix_numba(ground, claw, ej, freq, 'half', Z_0) for ground, claw, ej, freq in zip(cross_to_ground_values, cross_to_claw_values, EJ_values, cavity_frequency_values)], dtype=np.float32) chunk['g_MHz'] = g_values return chunk
[docs] def parallel_process_dataframe(self, df, num_chunks, Z_0 = 50): """ Process the DataFrame in parallel. This method splits the DataFrame into chunks and processes each chunk in parallel. Args: - df: The DataFrame to be processed. - num_chunks: The number of chunks to split the DataFrame into. - Z_0: The characteristic impedance of the transmission line. Default is 50 Returns: - df: The DataFrame with the added parameters. """ chunks = np.array_split(df, num_chunks) with Parallel(n_jobs=num_chunks) as parallel: results = parallel(delayed(self.add_cavity_coupled_H_params_chunk)(chunk, Z_0) for chunk in chunks) return pd.concat(results)
[docs] def chi(self, EJ, EC, g, f_r): """ Calculate the full cavity frequency shift between |0> and |1> states of a qubit using g, f_r, f_q, and alpha. It uses the result derived using 2nd-order perturbation theory (equation 9 in SQuaDDS paper). Args: - EJ (float): Josephson energy of the transmon qubit. - EC (float): Charging energy of the transmon qubit. - g (float): The coupling strength between the qubit and the cavity. - f_r (float): The resonant frequency of the cavity. - f_q (float): The frequency spacing between the first two qubit levels. Returns: - chi (float): The full dispersive shift of the cavity """ #!TODO: Speed up this calculation using numba omega_r = 2 * np.pi * f_r * 1e9 transmon = Transmon(EJ=EJ, EC=EC, ng=0, ncut=30) alpha = transmon.anharmonicity() * 1E3 # MHz, linear or angular omega_q = transmon.E01() # is this in linear or angular frequency units delta = omega_r - omega_q sigma = omega_r + omega_q chi = 2 * g**2 * (alpha /(delta * (delta - alpha))- alpha/(sigma * (sigma + alpha))) return chi