diff --git a/quam_builder/architecture/superconducting/__init__.py b/quam_builder/architecture/superconducting/__init__.py index 49990dff..20977fc0 100644 --- a/quam_builder/architecture/superconducting/__init__.py +++ b/quam_builder/architecture/superconducting/__init__.py @@ -1,12 +1,22 @@ -from .components import cross_resonance, flux_line, mixer, readout_resonator, tunable_coupler, xy_drive, zz_drive +from .components import ( + cross_resonance, + flux_line, + mixer, + readout_resonator, + tunable_coupler, + xy_drive, + zz_drive, +) from .custom_gates import CZGate -from .qpu import BaseQuam, FixedFrequencyQuam, FluxTunableQuam +from .qpu import BaseQuam, FixedFrequencyQuam, FluxTunableQuam, CavityQuam from .qubit import BaseTransmon, FixedFrequencyTransmon, FluxTunableTransmon from .qubit_pair import FixedFrequencyTransmonPair, FluxTunableTransmonPair +from .cavity import Cavity, CavityMode __all__ = [ *qpu.__all__, *qubit.__all__, *qubit_pair.__all__, *custom_gates.__all__, + *cavity.__all__, ] diff --git a/quam_builder/architecture/superconducting/cavity/__init__.py b/quam_builder/architecture/superconducting/cavity/__init__.py new file mode 100644 index 00000000..87605397 --- /dev/null +++ b/quam_builder/architecture/superconducting/cavity/__init__.py @@ -0,0 +1,14 @@ +from typing import Union +from quam_builder.architecture.superconducting.cavity.cavity import ( + Cavity, +) +from quam_builder.architecture.superconducting.cavity.cavity_mode import ( + CavityMode, +) + +__all__ = [ + *cavity.__all__, + *cavity_mode.__all__, +] + +AnyTransmonPair = Union[Cavity, CavityMode] diff --git a/quam_builder/architecture/superconducting/cavity/cavity.py b/quam_builder/architecture/superconducting/cavity/cavity.py new file mode 100644 index 00000000..d48dbb73 --- /dev/null +++ b/quam_builder/architecture/superconducting/cavity/cavity.py @@ -0,0 +1,433 @@ +from typing import Callable, Dict, Any, Union, Optional, Literal, Tuple +from dataclasses import field +from logging import getLogger + +from quam.core import quam_dataclass +from quam.components.quantum_components import Qubit + +# from quam_builder.architecture.superconducting.components.readout_resonator import ( +# ReadoutResonatorIQ, +# ReadoutResonatorMW, +# ) +from quam_builder.architecture.superconducting.components.xy_drive import ( + XYDriveIQ, + XYDriveMW, +) +from quam_builder.architecture.superconducting.cavity.cavity_mode import CavityMode +from qm import QuantumMachine, logger +from qm.qua.type_hints import QuaVariable +from qm.octave.octave_mixer_calibration import MixerCalibrationResults +from qm.qua import ( + save, + declare, + fixed, + assign, + wait, + while_, + StreamType, + if_, + update_frequency, + Math, + Cast, +) + + +__all__ = ["Cavity"] + + +@quam_dataclass +class Cavity(Qubit): + """ + Example QUAM component for a transmon qubit. + + Attributes: + id (Union[int, str]): The id of the Transmon, used to generate the name. + Can be a string, or an integer in which case it will add `Channel._default_label`. + xy (Union[MWChannel, IQChannel]): The xy drive component. + resonator (Union[ReadoutResonatorIQ, ReadoutResonatorMW]): The readout resonator component. + f_01 (float): The 0-1 transition frequency in Hz. Default is None. + f_12 (float): The 1-2 transition frequency in Hz. Default is None. + anharmonicity (int): The transmon anharmonicity in Hz. Default is None. + T1 (float): The transmon T1 in seconds. Default is None. + T2ramsey (float): The transmon T2* in seconds. + T2echo (float): The transmon T2 in seconds. + thermalization_time_factor (int): Thermalization time in units of T1. Default is 5. + sigma_time_factor (int): Sigma time factor for pulse shaping. Default is 5. + GEF_frequency_shift (int): The frequency shift for the GEF states. Default is None. + chi (float): The dispersive shift in Hz. Default is None. + grid_location (str): Qubit location in the plot grid as "column, row". + gate_fidelity (Dict[str, Any]): Collection of single qubit gate fidelity. + extras (Dict[str, Any]): Additional attributes for the transmon. + + Methods: + name: Returns the name of the transmon. + inferred_f_12: Returns the 0-2 (e-f) transition frequency in Hz, derived from f_01 and anharmonicity. + inferred_anharmonicity: Returns the transmon anharmonicity in Hz, derived from f_01 and f_12. + sigma: Returns the sigma value for a given pulse. + thermalization_time: Returns the transmon thermalization time in ns. + calibrate_octave: Calibrates the Octave channels (xy and resonator) linked to this transmon. + set_gate_shape: Sets the shape of the single qubit gates. + readout_state: Performs a readout of the qubit state using the specified pulse. + reset_qubit: Reset the qubit to the ground state ('g') with the specified method. + reset_qubit_thermal: Reset the qubit to the ground state ('g') using thermalization. + reset_qubit_active: Reset the qubit to the ground state ('g') using active reset. + reset_qubit_active_gef: Reset the qubit to the ground state ('g') using active reset with GEF state readout. + readout_state_gef: Perform a GEF state readout using the specified pulse and update the state variable. + """ + + id: Union[int, str] + + alice: CavityMode = None + bob: CavityMode = None + + f_01: float = None + f_12: float = None + anharmonicity: float = None + + T1: float = None + T2ramsey: float = None + T2echo: float = None + thermalization_time_factor: int = 5 + sigma_time_factor: int = 5 + + GEF_frequency_shift: int = None + chi: float = None + grid_location: str = None + gate_fidelity: Dict[str, Any] = field(default_factory=dict) + extras: Dict[str, Any] = field(default_factory=dict) + + @property + def inferred_f_12(self) -> float: + """The 0-2 (e-f) transition frequency in Hz, derived from f_01 and anharmonicity""" + name = getattr(self, "name", self.__class__.__name__) + if not isinstance(self.f_01, (float, int)): + raise AttributeError( + f"Error inferring f_12 for channel {name}: {self.f_01=} is not a number" + ) + if not isinstance(self.anharmonicity, (float, int)): + raise AttributeError( + f"Error inferring f_12 for channel {name}: {self.anharmonicity=} is not a number" + ) + return self.f_01 + self.anharmonicity + + @property + def inferred_anharmonicity(self) -> float: + """The transmon anharmonicity in Hz, derived from f_01 and f_12.""" + name = getattr(self, "name", self.__class__.__name__) + if not isinstance(self.f_01, (float, int)): + raise AttributeError( + f"Error inferring anharmonicity for channel {name}: {self.f_01=} is not a number" + ) + if not isinstance(self.f_12, (float, int)): + raise AttributeError( + f"Error inferring anharmonicity for channel {name}: {self.f_12=} is not a number" + ) + return self.f_12 - self.f_01 + + @property + def thermalization_time(self): + """The transmon thermalization time in ns.""" + if self.T1 is not None: + return int(self.thermalization_time_factor * self.T1 * 1e9 / 4) * 4 + else: + return int(self.thermalization_time_factor * 10e-6 * 1e9 / 4) * 4 + + def calibrate_octave( + self, + QM: QuantumMachine, + calibrate_drive: bool = True, + calibrate_resonator: bool = True, + ) -> Tuple[ + Union[None, MixerCalibrationResults], Union[None, MixerCalibrationResults] + ]: + """Calibrate the Octave channels (xy and resonator) linked to this transmon for the LO frequency, intermediate + frequency and Octave gain as defined in the state. + + Args: + QM (QuantumMachine): the running quantum machine. + calibrate_drive (bool): flag to calibrate xy line. + calibrate_resonator (bool): flag to calibrate the resonator line. + + Return: + The Octave calibration results as (resonator, xy_drive) + """ + if calibrate_resonator and self.resonator is not None: + if hasattr(self.resonator, "frequency_converter_up"): + logger.info(f"Calibrating {self.resonator.name}") + resonator_calibration_output = QM.calibrate_element( + self.resonator.name, + { + self.resonator.frequency_converter_up.LO_frequency: ( + self.resonator.intermediate_frequency, + ) + }, + ) + else: + raise RuntimeError( + f"{self.resonator.name} doesn't have a 'frequency_converter_up' attribute, it is thus most likely " + "not connected to an Octave." + ) + else: + resonator_calibration_output = None + + if calibrate_drive and self.xy is not None: + if hasattr(self.xy, "frequency_converter_up"): + logger.info(f"Calibrating {self.xy.name}") + xy_drive_calibration_output = QM.calibrate_element( + self.xy.name, + { + self.xy.frequency_converter_up.LO_frequency: ( + self.xy.intermediate_frequency, + ) + }, + ) + else: + raise RuntimeError( + f"{self.xy.name} doesn't have a 'frequency_converter_up' attribute, it is thus most likely not " + "connected to an Octave." + ) + else: + xy_drive_calibration_output = None + return resonator_calibration_output, xy_drive_calibration_output + + def set_gate_shape(self, gate_shape: str) -> None: + """Set the shape fo the single qubit gates defined as ["x180", "x90" "-x90", "y180", "y90", "-y90"]""" + for gate in ["x180", "x90", "-x90", "y180", "y90", "-y90"]: + if f"{gate}_{gate_shape}" in self.xy.operations: + self.xy.operations[gate] = f"#./{gate}_{gate_shape}" + else: + raise AttributeError( + f"The gate '{gate}_{gate_shape}' is not part of the existing operations for {self.xy.name} --> {self.xy.operations.keys()}." + ) + + def readout_state( + self, state, pulse_name: str = "readout", threshold: Optional[float] = None + ): + """ + Perform a readout of the qubit state using the specified pulse. + + This function measures the qubit state using the specified readout pulse and assigns the result to the given state variable. + If no threshold is provided, the default threshold for the specified pulse is used. + + Args: + state: The variable to assign the readout result to. + pulse_name (str): The name of the readout pulse to use. Default is "readout". + threshold (float, optional): The threshold value for the readout. If None, the default threshold for the pulse is used. + + Returns: + None + + The function declares fixed variables I and Q, measures the qubit state using the specified pulse, and assigns the result to the state variable based on the threshold. + It then waits for the resonator depletion time. + """ + I = declare(fixed) + Q = declare(fixed) + if threshold is None: + threshold = self.resonator.operations[pulse_name].threshold + self.resonator.measure(pulse_name, qua_vars=(I, Q)) + assign(state, Cast.to_int(I > threshold)) + wait(self.resonator.depletion_time // 4, self.resonator.name) + + def reset( + self, + reset_type: Literal["thermal", "active", "active_gef"] = "thermal", + simulate: bool = False, + log_callable: Optional[Callable] = None, + **kwargs, + ): + """ + Reset the qubit with the specified method. + + This function resets the qubit using the specified method: thermal reset, active reset, or active GEF reset. + When simulating the QUA program, the qubit reset is skipped to save simulated samples. + + Args: + reset_type (Literal["thermal", "active", "active_gef"]): The type of reset to perform. Default is "thermal". + simulate (bool): If True, the qubit reset is skipped for simulation purposes. Default is False. + log_callable (optional): Logger instance to log warnings. If None, a default logger is used. + **kwargs: Additional keyword arguments passed to the active reset methods. + + Returns: + None + + Raises: + Warning: If the function is called in simulation mode, a warning is issued indicating + that the qubit reset has been skipped. + """ + if not simulate: + if reset_type == "thermal": + self.reset_qubit_thermal() + elif reset_type == "active": + self.reset_qubit_active(**kwargs) + elif reset_type == "active_gef": + self.reset_qubit_active_gef(**kwargs) + else: + if log_callable is None: + log_callable = getLogger(__name__).warning + log_callable( + "For simulating the QUA program, the qubit reset has been skipped." + ) + + def reset_qubit_thermal(self): + """ + Perform a thermal reset of the qubit. + + This function waits for a duration specified by the thermalization time + to allow the qubit to return to its ground state through natural thermal + relaxation. + """ + self.wait(self.thermalization_time // 4) + + def reset_qubit_active( + self, + save_qua_var: Optional[StreamType] = None, + pi_pulse_name: str = "x180", + readout_pulse_name: str = "readout", + max_attempts: int = 15, + ): + """ + Perform an active reset of the qubit. + + This function performs an active reset of the qubit by repeatedly measuring the qubit state and applying a pi pulse + until the qubit is in the ground state or the maximum number of attempts is reached. + + Args: + save_qua_var (Optional[StreamType]): The QUA variable to save the number of attempts to. + pi_pulse_name (str): The name of the pi pulse to use for the reset. Default is "x180". + readout_pulse_name (str): The name of the readout pulse to use for measuring the qubit state. Default is "readout". + max_attempts (int): The maximum number of attempts to reset the qubit. Default is 15. + + Returns: + None + + The function measures the qubit state using the specified readout pulse, applies a pi pulse if the qubit is not in the ground state, + and repeats this process until the qubit is in the ground state or the maximum number of attempts is reached. + If `save_qua_var` is provided, the number of attempts is saved to this variable. + """ + pulse = self.resonator.operations[readout_pulse_name] + + I = declare(fixed) + Q = declare(fixed) + state = declare(bool) + attempts = declare(int, value=1) + assign(attempts, 1) + self.align() + self.resonator.measure("readout", qua_vars=(I, Q)) + assign(state, I > pulse.threshold) + wait(self.resonator.depletion_time // 2, self.resonator.name) + self.xy.play(pi_pulse_name, condition=state) + with while_((I > pulse.rus_exit_threshold) & (attempts < max_attempts)): + self.xy.align(self.resonator.name) + self.resonator.measure("readout", qua_vars=(I, Q)) + assign(state, I > pulse.threshold) + wait(self.resonator.depletion_time // 2, self.resonator.name) + self.xy.play(pi_pulse_name, condition=state) + self.xy.align(self.resonator.name) + assign(attempts, attempts + 1) + wait(500, self.xy.name) + self.xy.align(self.resonator.name) + if save_qua_var is not None: + save(attempts, save_qua_var) + + def reset_qubit_active_gef( + self, + readout_pulse_name: str = "readout", + pi_01_pulse_name: str = "x180", + pi_12_pulse_name: str = "EF_x180", + ): + """ + Reset the qubit to the ground state ('g') using active reset with GEF state readout. + + This function performs an active reset of the qubit by repeatedly measuring its state + and applying appropriate pulses to bring it back to the ground state ('g'). The process + continues until the qubit is measured in the ground state twice in a row to ensure high + confidence in the reset. + + Args: + readout_pulse_name (str, optional): The name of the pulse to use for the readout. Defaults to "readout". + pi_01_pulse_name (str, optional): The name of the pulse to use for the 0-1 transition. Defaults to "x180". + pi_12_pulse_name (str, optional): The name of the pulse to use for the 1-2 transition. Defaults to "EF_x180". + + Returns: + None + """ + res_ar = declare(int) + success = declare(int) + assign(success, 0) + attempts = declare(int) + assign(attempts, 0) + self.align() + with while_(success < 2): + self.readout_state_gef(res_ar, readout_pulse_name) + wait(self.rr.res_deplete_time // 4, self.xy.name) + self.align() + with if_(res_ar == 0): + assign( + success, success + 1 + ) # we need to measure 'g' two times in a row to increase our confidence + with if_(res_ar == 1): + update_frequency(self.xy.name, int(self.xy.intermediate_frequency)) + self.xy.play(pi_01_pulse_name) + assign(success, 0) + with if_(res_ar == 2): + update_frequency( + self.xy.name, + int(self.xy.intermediate_frequency - self.anharmonicity), + ) + self.xy.play(pi_12_pulse_name) + update_frequency(self.xy.name, int(self.xy.intermediate_frequency)) + self.xy.play(pi_01_pulse_name) + assign(success, 0) + self.align() + assign(attempts, attempts + 1) + + def readout_state_gef(self, state: QuaVariable, pulse_name: str = "readout"): + """ + Perform a GEF state readout using the specified pulse and update the state variable. + + This function measures the 'I' and 'Q' quadrature components of the resonator's response + to a given pulse, calculates the squared Euclidean distance between the measured + (I, Q) values and the predefined GEF state centers, and assigns the state variable + to the index of the closest GEF state. + + Args: + state (QuaVariableBool): The variable to store the readout state (0 for 'g', 1 for 'e', 2 for 'f'). + pulse_name (str, optional): The name of the pulse to use for the readout. Defaults to "readout". + + Returns: + None + """ + I = declare(fixed) + Q = declare(fixed) + diff = declare(fixed, size=3) + + self.resonator.update_frequency( + int( + self.resonator.intermediate_frequency + + self.resonator.GEF_frequency_shift + ) + ) + self.resonator.measure(pulse_name, qua_vars=(I, Q)) + self.resonator.update_frequency(self.resonator.intermediate_frequency) + + gef_centers = [ + self.resonator.gef_centers[0], + self.resonator.gef_centers[1], + self.resonator.gef_centers[2], + ] + for p in range(3): + assign( + diff[p], + Math.abs(I - gef_centers[p][0]) + Math.abs(Q - gef_centers[p][1]), + ) + assign(state, Math.argmin(diff)) + wait(self.resonator.depletion_time // 4, self.resonator.name) + + def wait(self, duration: int): + """Wait for a given duration on all channels of the qubit. + + Args: + duration (int): The duration to wait for in unit of clock cycles (4ns). + """ + channel_names = [channel.name for channel in self.channels.values()] + wait(duration, *channel_names) diff --git a/quam_builder/architecture/superconducting/cavity/cavity_mode.py b/quam_builder/architecture/superconducting/cavity/cavity_mode.py new file mode 100644 index 00000000..e418ce10 --- /dev/null +++ b/quam_builder/architecture/superconducting/cavity/cavity_mode.py @@ -0,0 +1,428 @@ +from typing import Callable, Dict, Any, Union, Optional, Literal, Tuple +from dataclasses import field +from logging import getLogger + +from quam.core import quam_dataclass +from quam.components.quantum_components import Qubit +from quam_builder.architecture.superconducting.components.readout_resonator import ( + ReadoutResonatorIQ, + ReadoutResonatorMW, +) +from quam_builder.architecture.superconducting.components.xy_drive import ( + XYDriveIQ, + XYDriveMW, +) + +from qm import QuantumMachine, logger +from qm.qua.type_hints import QuaVariable +from qm.octave.octave_mixer_calibration import MixerCalibrationResults +from qm.qua import ( + save, + declare, + fixed, + assign, + wait, + while_, + StreamType, + if_, + update_frequency, + Math, + Cast, +) + + +__all__ = ["CavityMode"] + + +@quam_dataclass +class CavityMode(Qubit): + """ + Example QUAM component for a transmon qubit. + + Attributes: + id (Union[int, str]): The id of the Transmon, used to generate the name. + Can be a string, or an integer in which case it will add `Channel._default_label`. + xy (Union[MWChannel, IQChannel]): The xy drive component. + resonator (Union[ReadoutResonatorIQ, ReadoutResonatorMW]): The readout resonator component. + f_01 (float): The 0-1 transition frequency in Hz. Default is None. + f_12 (float): The 1-2 transition frequency in Hz. Default is None. + anharmonicity (int): The transmon anharmonicity in Hz. Default is None. + T1 (float): The transmon T1 in seconds. Default is None. + T2ramsey (float): The transmon T2* in seconds. + T2echo (float): The transmon T2 in seconds. + thermalization_time_factor (int): Thermalization time in units of T1. Default is 5. + sigma_time_factor (int): Sigma time factor for pulse shaping. Default is 5. + GEF_frequency_shift (int): The frequency shift for the GEF states. Default is None. + chi (float): The dispersive shift in Hz. Default is None. + grid_location (str): Qubit location in the plot grid as "column, row". + gate_fidelity (Dict[str, Any]): Collection of single qubit gate fidelity. + extras (Dict[str, Any]): Additional attributes for the transmon. + + Methods: + name: Returns the name of the transmon. + inferred_f_12: Returns the 0-2 (e-f) transition frequency in Hz, derived from f_01 and anharmonicity. + inferred_anharmonicity: Returns the transmon anharmonicity in Hz, derived from f_01 and f_12. + sigma: Returns the sigma value for a given pulse. + thermalization_time: Returns the transmon thermalization time in ns. + calibrate_octave: Calibrates the Octave channels (xy and resonator) linked to this transmon. + set_gate_shape: Sets the shape of the single qubit gates. + readout_state: Performs a readout of the qubit state using the specified pulse. + reset_qubit: Reset the qubit to the ground state ('g') with the specified method. + reset_qubit_thermal: Reset the qubit to the ground state ('g') using thermalization. + reset_qubit_active: Reset the qubit to the ground state ('g') using active reset. + reset_qubit_active_gef: Reset the qubit to the ground state ('g') using active reset with GEF state readout. + readout_state_gef: Perform a GEF state readout using the specified pulse and update the state variable. + """ + + id: Union[int, str] + + xy: Union[XYDriveIQ, XYDriveMW] = None + f0g1: Union[XYDriveIQ, XYDriveMW] = None + + T1: float = None + T2ramsey: float = None + T2echo: float = None + thermalization_time_factor: int = 5 + sigma_time_factor: int = 5 + + GEF_frequency_shift: int = None + chi: float = None + grid_location: str = None + gate_fidelity: Dict[str, Any] = field(default_factory=dict) + extras: Dict[str, Any] = field(default_factory=dict) + + @property + def inferred_f_12(self) -> float: + """The 0-2 (e-f) transition frequency in Hz, derived from f_01 and anharmonicity""" + name = getattr(self, "name", self.__class__.__name__) + if not isinstance(self.f_01, (float, int)): + raise AttributeError( + f"Error inferring f_12 for channel {name}: {self.f_01=} is not a number" + ) + if not isinstance(self.anharmonicity, (float, int)): + raise AttributeError( + f"Error inferring f_12 for channel {name}: {self.anharmonicity=} is not a number" + ) + return self.f_01 + self.anharmonicity + + @property + def inferred_anharmonicity(self) -> float: + """The transmon anharmonicity in Hz, derived from f_01 and f_12.""" + name = getattr(self, "name", self.__class__.__name__) + if not isinstance(self.f_01, (float, int)): + raise AttributeError( + f"Error inferring anharmonicity for channel {name}: {self.f_01=} is not a number" + ) + if not isinstance(self.f_12, (float, int)): + raise AttributeError( + f"Error inferring anharmonicity for channel {name}: {self.f_12=} is not a number" + ) + return self.f_12 - self.f_01 + + @property + def thermalization_time(self): + """The transmon thermalization time in ns.""" + if self.T1 is not None: + return int(self.thermalization_time_factor * self.T1 * 1e9 / 4) * 4 + else: + return int(self.thermalization_time_factor * 10e-6 * 1e9 / 4) * 4 + + def calibrate_octave( + self, + QM: QuantumMachine, + calibrate_drive: bool = True, + calibrate_resonator: bool = True, + ) -> Tuple[ + Union[None, MixerCalibrationResults], Union[None, MixerCalibrationResults] + ]: + """Calibrate the Octave channels (xy and resonator) linked to this transmon for the LO frequency, intermediate + frequency and Octave gain as defined in the state. + + Args: + QM (QuantumMachine): the running quantum machine. + calibrate_drive (bool): flag to calibrate xy line. + calibrate_resonator (bool): flag to calibrate the resonator line. + + Return: + The Octave calibration results as (resonator, xy_drive) + """ + if calibrate_resonator and self.resonator is not None: + if hasattr(self.resonator, "frequency_converter_up"): + logger.info(f"Calibrating {self.resonator.name}") + resonator_calibration_output = QM.calibrate_element( + self.resonator.name, + { + self.resonator.frequency_converter_up.LO_frequency: ( + self.resonator.intermediate_frequency, + ) + }, + ) + else: + raise RuntimeError( + f"{self.resonator.name} doesn't have a 'frequency_converter_up' attribute, it is thus most likely " + "not connected to an Octave." + ) + else: + resonator_calibration_output = None + + if calibrate_drive and self.xy is not None: + if hasattr(self.xy, "frequency_converter_up"): + logger.info(f"Calibrating {self.xy.name}") + xy_drive_calibration_output = QM.calibrate_element( + self.xy.name, + { + self.xy.frequency_converter_up.LO_frequency: ( + self.xy.intermediate_frequency, + ) + }, + ) + else: + raise RuntimeError( + f"{self.xy.name} doesn't have a 'frequency_converter_up' attribute, it is thus most likely not " + "connected to an Octave." + ) + else: + xy_drive_calibration_output = None + return resonator_calibration_output, xy_drive_calibration_output + + def set_gate_shape(self, gate_shape: str) -> None: + """Set the shape fo the single qubit gates defined as ["x180", "x90" "-x90", "y180", "y90", "-y90"]""" + for gate in ["x180", "x90", "-x90", "y180", "y90", "-y90"]: + if f"{gate}_{gate_shape}" in self.xy.operations: + self.xy.operations[gate] = f"#./{gate}_{gate_shape}" + else: + raise AttributeError( + f"The gate '{gate}_{gate_shape}' is not part of the existing operations for {self.xy.name} --> {self.xy.operations.keys()}." + ) + + def readout_state( + self, state, pulse_name: str = "readout", threshold: Optional[float] = None + ): + """ + Perform a readout of the qubit state using the specified pulse. + + This function measures the qubit state using the specified readout pulse and assigns the result to the given state variable. + If no threshold is provided, the default threshold for the specified pulse is used. + + Args: + state: The variable to assign the readout result to. + pulse_name (str): The name of the readout pulse to use. Default is "readout". + threshold (float, optional): The threshold value for the readout. If None, the default threshold for the pulse is used. + + Returns: + None + + The function declares fixed variables I and Q, measures the qubit state using the specified pulse, and assigns the result to the state variable based on the threshold. + It then waits for the resonator depletion time. + """ + I = declare(fixed) + Q = declare(fixed) + if threshold is None: + threshold = self.resonator.operations[pulse_name].threshold + self.resonator.measure(pulse_name, qua_vars=(I, Q)) + assign(state, Cast.to_int(I > threshold)) + wait(self.resonator.depletion_time // 4, self.resonator.name) + + def reset( + self, + reset_type: Literal["thermal", "active", "active_gef"] = "thermal", + simulate: bool = False, + log_callable: Optional[Callable] = None, + **kwargs, + ): + """ + Reset the qubit with the specified method. + + This function resets the qubit using the specified method: thermal reset, active reset, or active GEF reset. + When simulating the QUA program, the qubit reset is skipped to save simulated samples. + + Args: + reset_type (Literal["thermal", "active", "active_gef"]): The type of reset to perform. Default is "thermal". + simulate (bool): If True, the qubit reset is skipped for simulation purposes. Default is False. + log_callable (optional): Logger instance to log warnings. If None, a default logger is used. + **kwargs: Additional keyword arguments passed to the active reset methods. + + Returns: + None + + Raises: + Warning: If the function is called in simulation mode, a warning is issued indicating + that the qubit reset has been skipped. + """ + if not simulate: + if reset_type == "thermal": + self.reset_qubit_thermal() + elif reset_type == "active": + self.reset_qubit_active(**kwargs) + elif reset_type == "active_gef": + self.reset_qubit_active_gef(**kwargs) + else: + if log_callable is None: + log_callable = getLogger(__name__).warning + log_callable( + "For simulating the QUA program, the qubit reset has been skipped." + ) + + def reset_qubit_thermal(self): + """ + Perform a thermal reset of the qubit. + + This function waits for a duration specified by the thermalization time + to allow the qubit to return to its ground state through natural thermal + relaxation. + """ + self.wait(self.thermalization_time // 4) + + def reset_qubit_active( + self, + save_qua_var: Optional[StreamType] = None, + pi_pulse_name: str = "x180", + readout_pulse_name: str = "readout", + max_attempts: int = 15, + ): + """ + Perform an active reset of the qubit. + + This function performs an active reset of the qubit by repeatedly measuring the qubit state and applying a pi pulse + until the qubit is in the ground state or the maximum number of attempts is reached. + + Args: + save_qua_var (Optional[StreamType]): The QUA variable to save the number of attempts to. + pi_pulse_name (str): The name of the pi pulse to use for the reset. Default is "x180". + readout_pulse_name (str): The name of the readout pulse to use for measuring the qubit state. Default is "readout". + max_attempts (int): The maximum number of attempts to reset the qubit. Default is 15. + + Returns: + None + + The function measures the qubit state using the specified readout pulse, applies a pi pulse if the qubit is not in the ground state, + and repeats this process until the qubit is in the ground state or the maximum number of attempts is reached. + If `save_qua_var` is provided, the number of attempts is saved to this variable. + """ + pulse = self.resonator.operations[readout_pulse_name] + + I = declare(fixed) + Q = declare(fixed) + state = declare(bool) + attempts = declare(int, value=1) + assign(attempts, 1) + self.align() + self.resonator.measure("readout", qua_vars=(I, Q)) + assign(state, I > pulse.threshold) + wait(self.resonator.depletion_time // 2, self.resonator.name) + self.xy.play(pi_pulse_name, condition=state) + with while_((I > pulse.rus_exit_threshold) & (attempts < max_attempts)): + self.xy.align(self.resonator.name) + self.resonator.measure("readout", qua_vars=(I, Q)) + assign(state, I > pulse.threshold) + wait(self.resonator.depletion_time // 2, self.resonator.name) + self.xy.play(pi_pulse_name, condition=state) + self.xy.align(self.resonator.name) + assign(attempts, attempts + 1) + wait(500, self.xy.name) + self.xy.align(self.resonator.name) + if save_qua_var is not None: + save(attempts, save_qua_var) + + def reset_qubit_active_gef( + self, + readout_pulse_name: str = "readout", + pi_01_pulse_name: str = "x180", + pi_12_pulse_name: str = "EF_x180", + ): + """ + Reset the qubit to the ground state ('g') using active reset with GEF state readout. + + This function performs an active reset of the qubit by repeatedly measuring its state + and applying appropriate pulses to bring it back to the ground state ('g'). The process + continues until the qubit is measured in the ground state twice in a row to ensure high + confidence in the reset. + + Args: + readout_pulse_name (str, optional): The name of the pulse to use for the readout. Defaults to "readout". + pi_01_pulse_name (str, optional): The name of the pulse to use for the 0-1 transition. Defaults to "x180". + pi_12_pulse_name (str, optional): The name of the pulse to use for the 1-2 transition. Defaults to "EF_x180". + + Returns: + None + """ + res_ar = declare(int) + success = declare(int) + assign(success, 0) + attempts = declare(int) + assign(attempts, 0) + self.align() + with while_(success < 2): + self.readout_state_gef(res_ar, readout_pulse_name) + wait(self.rr.res_deplete_time // 4, self.xy.name) + self.align() + with if_(res_ar == 0): + assign( + success, success + 1 + ) # we need to measure 'g' two times in a row to increase our confidence + with if_(res_ar == 1): + update_frequency(self.xy.name, int(self.xy.intermediate_frequency)) + self.xy.play(pi_01_pulse_name) + assign(success, 0) + with if_(res_ar == 2): + update_frequency( + self.xy.name, + int(self.xy.intermediate_frequency - self.anharmonicity), + ) + self.xy.play(pi_12_pulse_name) + update_frequency(self.xy.name, int(self.xy.intermediate_frequency)) + self.xy.play(pi_01_pulse_name) + assign(success, 0) + self.align() + assign(attempts, attempts + 1) + + def readout_state_gef(self, state: QuaVariable, pulse_name: str = "readout"): + """ + Perform a GEF state readout using the specified pulse and update the state variable. + + This function measures the 'I' and 'Q' quadrature components of the resonator's response + to a given pulse, calculates the squared Euclidean distance between the measured + (I, Q) values and the predefined GEF state centers, and assigns the state variable + to the index of the closest GEF state. + + Args: + state (QuaVariableBool): The variable to store the readout state (0 for 'g', 1 for 'e', 2 for 'f'). + pulse_name (str, optional): The name of the pulse to use for the readout. Defaults to "readout". + + Returns: + None + """ + I = declare(fixed) + Q = declare(fixed) + diff = declare(fixed, size=3) + + self.resonator.update_frequency( + int( + self.resonator.intermediate_frequency + + self.resonator.GEF_frequency_shift + ) + ) + self.resonator.measure(pulse_name, qua_vars=(I, Q)) + self.resonator.update_frequency(self.resonator.intermediate_frequency) + + gef_centers = [ + self.resonator.gef_centers[0], + self.resonator.gef_centers[1], + self.resonator.gef_centers[2], + ] + for p in range(3): + assign( + diff[p], + Math.abs(I - gef_centers[p][0]) + Math.abs(Q - gef_centers[p][1]), + ) + assign(state, Math.argmin(diff)) + wait(self.resonator.depletion_time // 4, self.resonator.name) + + def wait(self, duration: int): + """Wait for a given duration on all channels of the qubit. + + Args: + duration (int): The duration to wait for in unit of clock cycles (4ns). + """ + channel_names = [channel.name for channel in self.channels.values()] + wait(duration, *channel_names) diff --git a/quam_builder/architecture/superconducting/components/twpa.py b/quam_builder/architecture/superconducting/components/twpa.py index 8402df1f..dec775a5 100644 --- a/quam_builder/architecture/superconducting/components/twpa.py +++ b/quam_builder/architecture/superconducting/components/twpa.py @@ -16,23 +16,23 @@ class TWPA(QuamComponent): Args: id (str, int): The id of the TWPA, used to generate the name. Can be a string, or an integer in which case it will add`Channel._default_label`. - pump (IQChannel): The pump component(sticky element) used for continuous output. + pump (IQChannel): The pump component(sticky element) used for continuous output. pump_ (IQChannel): The pump component(non sticky element)used for TWPA calibration spectroscopy (IQChannel): Probe tone used for calibrating the saturation power of the TWPA max_avg_gain (float): The maximum average gain around the readout resonators related to the TWPA max_avg_snr_improvement (float): The maximum average SNR improvement around the readout resonators related to the TWPA - pump_frequency (float): calibrated pump frequency at which twpa gives the maximum average snr improvement - pump_amplitude (float): calibrated pump amplitude at which twpa gives the maximum average snr improvement + pump_frequency (float): calibrated pump frequency at which twpa gives the maximum average snr improvement + pump_amplitude (float): calibrated pump amplitude at which twpa gives the maximum average snr improvement mltpx_pump_frequency (float): calibrated pump frequency at which twpa gives proper snr improvement for multiplexed readout mltpx_pump_amplitude (float): calibrated pump amplitude at which twpa gives proper snr improvement for multiplexed readout - p_saturation (float): calibrated saturation power of the twpa + p_saturation (float): calibrated saturation power of the twpa avg_std_gain (float): standard deviation of the average gain around the readout resonators related to the TWPA avg_std_snr_improvement (float): standard deviation of the average snr improvement around the readout resonators related to the TWPA - + dispersive_feature (float): dispersive feature of the twpa defined from it's designed parameters qubits (list): list of qubits of which the signals are amplified by the twpa - + initialization (bool): whether to use the twpa in the QUA program or not _initialized_ids (ClassVar[set]): A class-level set to track initialized twpa object IDs externally. This won't be serialized since it's not an instance attribute. @@ -47,48 +47,43 @@ class TWPA(QuamComponent): max_avg_gain: float = None max_avg_snr_improvement: float = None - pump_frequency : float = None - pump_amplitude : float = None - mltpx_pump_frequency : float = None - mltpx_pump_amplitude : float = None + pump_frequency: float = None + pump_amplitude: float = None + mltpx_pump_frequency: float = None + mltpx_pump_amplitude: float = None p_saturation: float = None - avg_std_gain: float=None - avg_std_snr_improvement: float= None + avg_std_gain: float = None + avg_std_snr_improvement: float = None dispersive_feature: float = None qubits: list = None - + initialization: bool = True _initialized_ids: ClassVar[set] = set() - @property def name(self): """The name of the twpa""" return self.id if isinstance(self.id, str) else f"twpa{self.id}" - - def initialize(self): # dont use twpa for the QUA program if initialization is set to False if not self.initialization: - return + return # Check initialization state using object ID (memory address) - # Initialize TWPA pump only when it hasn't been initialized yet + # Initialize TWPA pump only when it hasn't been initialized yet # This won't be serialized since it's stored in a class-level set obj_id = id(self) if obj_id in self._initialized_ids: return - + f_p = self.pump_frequency p_p = self.pump_amplitude update_frequency( self.pump.name, - f_p+ self.pump.intermediate_frequency, + f_p + self.pump.intermediate_frequency, ) self.pump.play("pump", amplitude_scale=p_p) # Store object ID externally (won't be serialized) # guarantee initializing twpa pump only once per QUA program execution self._initialized_ids.add(obj_id) - - diff --git a/quam_builder/architecture/superconducting/components/xy_drive.py b/quam_builder/architecture/superconducting/components/xy_drive.py index 6af81284..344c6af5 100644 --- a/quam_builder/architecture/superconducting/components/xy_drive.py +++ b/quam_builder/architecture/superconducting/components/xy_drive.py @@ -104,7 +104,11 @@ class XYDriveMW(MWChannel, XYDriveBase): @property def upconverter_frequency(self): """Returns the up-converter/LO frequency in Hz.""" - return self.opx_output.upconverter_frequency + return ( + self.opx_output.upconverter_frequency + if self.opx_output.upconverter_frequency is not None + else super().upconverter_frequency + ) def get_output_power(self, operation, Z=50) -> float: """ diff --git a/quam_builder/architecture/superconducting/custom_gates/flux_tunable_transmon_pair/two_qubit_gates.py b/quam_builder/architecture/superconducting/custom_gates/flux_tunable_transmon_pair/two_qubit_gates.py index ffca1c44..d2b161ee 100644 --- a/quam_builder/architecture/superconducting/custom_gates/flux_tunable_transmon_pair/two_qubit_gates.py +++ b/quam_builder/architecture/superconducting/custom_gates/flux_tunable_transmon_pair/two_qubit_gates.py @@ -6,9 +6,8 @@ from quam.components.macro import QubitPairMacro from quam.components.pulses import Pulse from quam.core import quam_dataclass -from quam.utils.qua_types import ( - ScalarInt -) +from quam.utils.qua_types import ScalarInt + __all__ = ["CZGate"] @@ -65,7 +64,7 @@ class CZGate(QubitPairMacro): unwanted frequency shifts during the CZ gate. - Maintaining qubit states: keeping spectator qubits in specific states during the gate. - Multi-qubit gate synchronization: ensuring all qubits are properly aligned and synchronized. - + The three spectator qubit parameters work together: - ``spectator_qubits``: Dictionary mapping qubit names (str) to qubit objects. These are the qubit instances that will be controlled during the gate. @@ -75,7 +74,7 @@ class CZGate(QubitPairMacro): - ``spectator_qubits_phase_shift``: Dictionary mapping qubit names (str) to phase shift values (float, in units of 2π). These frame rotations are applied to the spectator qubits after the flux pulses, similar to phase_shift_control and phase_shift_target. - + Usage Example: ```python # Configure spectator qubits for crosstalk compensation @@ -101,7 +100,7 @@ class CZGate(QubitPairMacro): "q1": 0.01, # Small phase correction for q1 (0.01 * 2π) "q2": 0.0 # No phase correction needed for q2 } - + # When apply() is called, spectator qubits will: # 1. Be aligned with control and target qubits # 2. Have their flux pulses played in parallel with the control qubit pulse @@ -110,7 +109,7 @@ class CZGate(QubitPairMacro): ``` Note: The keys in all three dictionaries must match (same qubit names). Only qubits listed in both ``spectator_qubits`` and ``spectator_qubits_control`` will have flux pulses applied. - + spectator_qubits: dict[str, Any] Optional dictionary of spectator qubit objects. spectator_qubits_control: dict[str, Pulse] @@ -175,7 +174,7 @@ class CZGate(QubitPairMacro): spectator_qubits: dict[str, Any] = field(default_factory=dict) spectator_qubits_control: dict[str, Pulse] = field(default_factory=dict) spectator_qubits_phase_shift: dict[str, float] = field(default_factory=dict) - + fidelity: dict[str, Any] = field(default_factory=dict) extras: dict[str, Any] = field(default_factory=dict) duration_control: ScalarInt = None @@ -207,9 +206,8 @@ def apply( phase_shift_control=None, phase_shift_target=None, **kwargs, - ) -> None: - + # Build list of spectator qubits and their pulse names spectator_qubits_list = [] spectator_pulse_names = {} @@ -224,17 +222,19 @@ def apply( self.qubit_pair.qubit_control.align(align_list) # Spectator qubit flux pulses - for qubit_name, spectator_qubit in zip(self.spectator_qubits.keys(), spectator_qubits_list): + for qubit_name, spectator_qubit in zip( + self.spectator_qubits.keys(), spectator_qubits_list + ): if qubit_name in spectator_pulse_names: spectator_qubit.z.play(spectator_pulse_names[qubit_name]) - + # Control qubit flux self.qubit_pair.qubit_control.z.play( self.flux_pulse_control_label, amplitude_scale=amplitude_scale_control, - duration=duration_control + duration=duration_control, ) - + # Coupler flux if self.coupler_flux_pulse is not None: self.qubit_pair.coupler.play( @@ -244,8 +244,10 @@ def apply( ) # Align all resources after playing pulses - self.qubit_pair.qubit_control.align([self.qubit_pair.qubit_target] + spectator_qubits_list) - + self.qubit_pair.qubit_control.align( + [self.qubit_pair.qubit_target] + spectator_qubits_list + ) + # Apply phase shifts if phase_shift_control is not None: self.qubit_pair.qubit_control.xy.frame_rotation_2pi(phase_shift_control) @@ -264,4 +266,6 @@ def apply( self.spectator_qubits[qubit_name].xy.frame_rotation_2pi(phase_shift) # Final alignment - self.qubit_pair.qubit_control.align([self.qubit_pair.qubit_target] + spectator_qubits_list) \ No newline at end of file + self.qubit_pair.qubit_control.align( + [self.qubit_pair.qubit_target] + spectator_qubits_list + ) diff --git a/quam_builder/architecture/superconducting/qpu/__init__.py b/quam_builder/architecture/superconducting/qpu/__init__.py index acc76940..c3e105e8 100644 --- a/quam_builder/architecture/superconducting/qpu/__init__.py +++ b/quam_builder/architecture/superconducting/qpu/__init__.py @@ -5,12 +5,17 @@ from quam_builder.architecture.superconducting.qpu.flux_tunable_quam import ( FluxTunableQuam, ) +from quam_builder.architecture.superconducting.qpu.cavity_quam import ( + CavityQuam, +) + from typing import Union __all__ = [ *base_quam.__all__, *fixed_frequency_quam.__all__, *flux_tunable_quam.__all__, + *cavity_quam.__all__, ] -AnyQuam = Union[BaseQuam, FixedFrequencyQuam, FluxTunableQuam] +AnyQuam = Union[BaseQuam, FixedFrequencyQuam, FluxTunableQuam, CavityQuam] diff --git a/quam_builder/architecture/superconducting/qpu/cavity_quam.py b/quam_builder/architecture/superconducting/qpu/cavity_quam.py new file mode 100644 index 00000000..d837c3b0 --- /dev/null +++ b/quam_builder/architecture/superconducting/qpu/cavity_quam.py @@ -0,0 +1,152 @@ +import warnings +from dataclasses import field +from typing import Dict, Union, ClassVar, Type + +from quam.core import quam_dataclass +from qm.qua import update_frequency + +from quam_builder.architecture.superconducting.qubit import ( + FluxTunableTransmon, + FixedFrequencyTransmon, +) +from quam_builder.architecture.superconducting.qubit_pair import FluxTunableTransmonPair +from quam_builder.architecture.superconducting.qpu.base_quam import BaseQuam +from quam_builder.architecture.superconducting.qpu.flux_tunable_quam import ( + FluxTunableQuam, +) +from quam_builder.architecture.superconducting.cavity.cavity import Cavity + +__all__ = [ + "CavityQuam", + "FluxTunableQuam", + "FluxTunableTransmon", + "FluxTunableTransmonPair", + "Cavity", +] + + +@quam_dataclass +class CavityQuam(FluxTunableQuam): + """Example of a QUAM composed of flux tunable transmons. + + Attributes: + qubit_type (ClassVar[Type[FixedFrequencyTransmon]]): The type of the qubits in the QUAM for type hinting. + qubit_pair_type (ClassVar[Type[FixedFrequencyTransmonPair]]): The type of the qubit pairs in the QUAM for type hinting. + qubits (Dict[str, FixedFrequencyTransmon]): A dictionary of qubits composing the QUAM. + qubit_pairs (Dict[str, FixedFrequencyTransmonPair]): A dictionary of qubit pairs composing the QUAM. + + Methods: + load: Loads the QUAM from the state.json file. + apply_all_couplers_to_min: Apply the offsets that bring all the active qubit pairs to a decoupled point. + apply_all_flux_to_joint_idle: Apply the offsets that bring all the active qubits to the joint sweet spot. + apply_all_flux_to_min: Apply the offsets that bring all the active qubits to the minimum frequency point. + apply_all_flux_to_zero: Apply the offsets that bring all the active qubits to the zero bias point. + set_all_fluxes: Set the fluxes to the specified point for the target qubit or qubit pair. + initialize_qpu: Initialize the QPU with the calibrated TWPA pumping points and with the specified flux point and target . + """ + + qubit_type: ClassVar[Type[FluxTunableTransmon]] = FluxTunableTransmon + qubit_pair_type: ClassVar[Type[FluxTunableTransmonPair]] = FluxTunableTransmonPair + + qubits: Dict[str, FluxTunableTransmon] = field(default_factory=dict) + qubit_pairs: Dict[str, FluxTunableTransmonPair] = field(default_factory=dict) + cavities: Dict[str, Cavity] = field(default_factory=dict) + + @classmethod + def load(cls, *args, **kwargs) -> "FluxTunableQuam": + return super().load(*args, **kwargs) + + def apply_all_couplers_to_min(self) -> None: + """Apply the offsets that bring all the active qubit pairs to a decoupled point.""" + for qp in self.active_qubit_pairs: + if qp.coupler is not None: + qp.coupler.to_decouple_idle() + + def apply_all_flux_to_joint_idle(self) -> None: + """Apply the offsets that bring all the active qubits to the joint sweet spot.""" + for q in self.active_qubits: + if q.z is not None: + q.z.to_joint_idle() + else: + warnings.warn( + f"Didn't find z-element on qubit {q.name}, didn't set to joint-idle" + ) + for q in self.qubits: + if self.qubits[q] not in self.active_qubits: + if self.qubits[q].z is not None: + self.qubits[q].z.to_min() + else: + warnings.warn( + f"Didn't find z-element on qubit {q}, didn't set to min" + ) + self.apply_all_couplers_to_min() + + def apply_all_flux_to_min(self) -> None: + """Apply the offsets that bring all the active qubits to the minimum frequency point.""" + for q in self.qubits: + if self.qubits[q].z is not None: + self.qubits[q].z.to_min() + else: + warnings.warn(f"Didn't find z-element on qubit {q}, didn't set to min") + self.apply_all_couplers_to_min() + + def apply_all_flux_to_zero(self) -> None: + """Apply the offsets that bring all the active qubits to the zero bias point.""" + for q in self.active_qubits: + q.z.to_zero() + + def set_all_fluxes( + self, + flux_point: str, + target: Union[FluxTunableTransmon, FluxTunableTransmonPair], + ): + """Set the fluxes to the specified point for the target qubit or qubit pair. + + Args: + flux_point (str): The flux point to set ('independent', 'pairwise', 'joint', 'min'). + target (Union[FluxTunableTransmon, FluxTunableTransmonPair]): The target qubit or qubit pair. + """ + if flux_point == "independent": + assert isinstance( + target, FluxTunableTransmon + ), "Independent flux point is only supported for individual transmons" + elif flux_point == "pairwise": + assert isinstance( + target, FluxTunableTransmonPair + ), "Pairwise flux point is only supported for transmon pairs" + + target_bias = None + if flux_point == "joint": + self.apply_all_flux_to_joint_idle() + if isinstance(target, FluxTunableTransmonPair): + target_bias = target.mutual_flux_bias + else: + target_bias = target.z.joint_offset + else: + self.apply_all_flux_to_min() + + if flux_point == "independent": + target.z.to_independent_idle() + target_bias = target.z.independent_offset + + elif flux_point == "pairwise": + target.to_mutual_idle() + target_bias = target.mutual_flux_bias + + target.z.settle() + target.align() + return target_bias + + def initialize_qpu(self, **kwargs): + """Initialize the QPU with the calibrated TWPA pumping points and + with the specified flux point and target + + Args: + flux_point (str): The flux point to set. Default is 'joint'. + target: The qubit under study. + """ + for twpa in self.twpas.values(): + twpa.initialize() + flux_point = kwargs.get("flux_point", "joint") + target = kwargs.get("target", None) + self.set_all_fluxes(flux_point, target) diff --git a/quam_builder/architecture/superconducting/qpu/flux_tunable_quam.py b/quam_builder/architecture/superconducting/qpu/flux_tunable_quam.py index fb94fd6e..08711fbb 100644 --- a/quam_builder/architecture/superconducting/qpu/flux_tunable_quam.py +++ b/quam_builder/architecture/superconducting/qpu/flux_tunable_quam.py @@ -124,7 +124,6 @@ def set_all_fluxes( target.align() return target_bias - def initialize_qpu(self, **kwargs): """Initialize the QPU with the calibrated TWPA pumping points and with the specified flux point and target @@ -134,14 +133,7 @@ def initialize_qpu(self, **kwargs): target: The qubit under study. """ for twpa in self.twpas.values(): - twpa.initialize() + twpa.initialize() flux_point = kwargs.get("flux_point", "joint") target = kwargs.get("target", None) self.set_all_fluxes(flux_point, target) - - - - - - - \ No newline at end of file diff --git a/quam_builder/architecture/superconducting/qubit/__init__.py b/quam_builder/architecture/superconducting/qubit/__init__.py index 349f4339..bdc187dd 100644 --- a/quam_builder/architecture/superconducting/qubit/__init__.py +++ b/quam_builder/architecture/superconducting/qubit/__init__.py @@ -5,6 +5,7 @@ from quam_builder.architecture.superconducting.qubit.flux_tunable_transmon import ( FluxTunableTransmon, ) + from typing import Union __all__ = [ diff --git a/quam_builder/architecture/superconducting/qubit/flux_tunable_transmon.py b/quam_builder/architecture/superconducting/qubit/flux_tunable_transmon.py index d98271a7..cf16b68b 100644 --- a/quam_builder/architecture/superconducting/qubit/flux_tunable_transmon.py +++ b/quam_builder/architecture/superconducting/qubit/flux_tunable_transmon.py @@ -1,3 +1,5 @@ +from typing import Union + from quam.core import quam_dataclass from quam_builder.architecture.superconducting.qubit.fixed_frequency_transmon import (