From deaeabb42087bdc1fa9d787e0e5b32f480a63b5a Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Mon, 2 Sep 2024 16:43:30 +0200 Subject: [PATCH] Add measure capabilities to MW FEM --- quam/components/channels.py | 209 ++++++++++++++++++------------------ 1 file changed, 106 insertions(+), 103 deletions(-) diff --git a/quam/components/channels.py b/quam/components/channels.py index e328f5d7..12e648f7 100644 --- a/quam/components/channels.py +++ b/quam/components/channels.py @@ -1086,100 +1086,7 @@ def apply_to_config(self, config: dict): @quam_dataclass -class InIQChannel(Channel): - """QuAM component for an IQ input channel - - Args: - operations (Dict[str, Pulse]): A dictionary of pulses to be played on this - channel. The key is the pulse label (e.g. "readout") and value is a - ReadoutPulse. - id (str, int): The id of the channel, used to generate the name. - Can be a string, or an integer in which case it will add - `Channel._default_label`. - opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective, - a tuple of (controller_name, port). - opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective, - a tuple of (controller_name, port). - opx_input_offset_I float: The offset of the I channel. Default is 0. - opx_input_offset_Q float: The offset of the Q channel. Default is 0. - frequency_converter_down (Optional[FrequencyConverter]): Frequency converter - QuAM component for the IQ input port. Only needed for the old Octave. - time_of_flight (int): Round-trip signal duration in nanoseconds. - smearing (int): Additional window of ADC integration in nanoseconds. - Used to account for signal smearing. - input_gain (float): The gain of the input channel. Default is None. - """ - - opx_input_I: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogInputPort] - opx_input_Q: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogInputPort] - - time_of_flight: int = 24 - smearing: int = 0 - - opx_input_offset_I: float = None - opx_input_offset_Q: float = None - - input_gain: Optional[int] = None - - frequency_converter_down: BaseFrequencyConverter = None - - _default_label: ClassVar[str] = "IQ" - - def apply_to_config(self, config: dict): - """Adds this InOutIQChannel to the QUA configuration. - - See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config] - for details. - """ - super().apply_to_config(config) - - # Note outputs instead of inputs because it's w.r.t. the QPU - element_config = config["elements"][self.name] - element_config["smearing"] = self.smearing - element_config["time_of_flight"] = self.time_of_flight - - from quam.components.octave import OctaveDownConverter - - if isinstance(self.frequency_converter_down, OctaveDownConverter): - octave = self.frequency_converter_down.octave - if octave is None: - raise ValueError( - f"Error generating config: channel {self.name} has an " - f"OctaveDownConverter (id={self.frequency_converter_down.id}) " - "without an attached Octave" - ) - element_config["RF_outputs"] = { - "port": (octave.name, self.frequency_converter_down.id) - } - elif str_ref.is_reference(self.frequency_converter_down): - raise ValueError( - f"Error generating config: channel {self.name} could not determine " - f'"frequency_converter_down", it seems to point to a non-existent ' - f"reference: {self.frequency_converter_down}" - ) - else: - # To be filled in next section - element_config["outputs"] = {} - - opx_inputs = [self.opx_input_I, self.opx_input_Q] - offsets = [self.opx_input_offset_I, self.opx_input_offset_Q] - input_gain = int(self.input_gain if self.input_gain is not None else 0) - for k, (opx_input, offset) in enumerate(zip(opx_inputs, offsets), start=1): - if isinstance(opx_input, LFAnalogInputPort): - opx_port = opx_input - elif len(opx_input) == 2: - opx_port = OPXPlusAnalogInputPort( - *opx_input, offset=offset, gain_db=input_gain - ) - opx_port.apply_to_config(config) - else: - opx_port = LFFEMAnalogInputPort( - *opx_input, offset=offset, gain_db=input_gain - ) - opx_port.apply_to_config(config) - if not isinstance(self.frequency_converter_down, OctaveDownConverter): - element_config["outputs"][f"out{k}"] = opx_port.port_tuple - +class _InComplexChannel(Channel, ABC): def measure( self, pulse_name: str, @@ -1246,10 +1153,10 @@ def measure( def measure_accumulated( self, pulse_name: str, - amplitude_scale: Union[float, AmpValuesType] = None, - num_segments: int = None, - segment_length: int = None, - qua_vars: Tuple[QuaVariableType, ...] = None, + amplitude_scale: Optional[Union[float, AmpValuesType]] = None, + num_segments: Optional[int] = None, + segment_length: Optional[int] = None, + qua_vars: Optional[Tuple[QuaVariableType, ...]] = None, stream=None, ) -> Tuple[QuaVariableType, QuaVariableType, QuaVariableType, QuaVariableType]: """Perform an accumulated dual demodulation measurement on this channel. @@ -1332,10 +1239,10 @@ def measure_accumulated( def measure_sliced( self, pulse_name: str, - amplitude_scale: Union[float, AmpValuesType] = None, - num_segments: int = None, - segment_length: int = None, - qua_vars: Tuple[QuaVariableType, ...] = None, + amplitude_scale: Optional[Union[float, AmpValuesType]] = None, + num_segments: Optional[int] = None, + segment_length: Optional[int] = None, + qua_vars: Optional[Tuple[QuaVariableType, ...]] = None, stream=None, ) -> Tuple[QuaVariableType, QuaVariableType, QuaVariableType, QuaVariableType]: """Perform a sliced dual demodulation measurement on this channel. @@ -1416,6 +1323,102 @@ def measure_sliced( return tuple(qua_vars) +@quam_dataclass +class InIQChannel(_InComplexChannel): + """QuAM component for an IQ input channel + + Args: + operations (Dict[str, Pulse]): A dictionary of pulses to be played on this + channel. The key is the pulse label (e.g. "readout") and value is a + ReadoutPulse. + id (str, int): The id of the channel, used to generate the name. + Can be a string, or an integer in which case it will add + `Channel._default_label`. + opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective, + a tuple of (controller_name, port). + opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective, + a tuple of (controller_name, port). + opx_input_offset_I float: The offset of the I channel. Default is 0. + opx_input_offset_Q float: The offset of the Q channel. Default is 0. + frequency_converter_down (Optional[FrequencyConverter]): Frequency converter + QuAM component for the IQ input port. Only needed for the old Octave. + time_of_flight (int): Round-trip signal duration in nanoseconds. + smearing (int): Additional window of ADC integration in nanoseconds. + Used to account for signal smearing. + input_gain (float): The gain of the input channel. Default is None. + """ + + opx_input_I: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogInputPort] + opx_input_Q: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogInputPort] + + time_of_flight: int = 24 + smearing: int = 0 + + opx_input_offset_I: float = None + opx_input_offset_Q: float = None + + input_gain: Optional[int] = None + + frequency_converter_down: BaseFrequencyConverter = None + + _default_label: ClassVar[str] = "IQ" + + def apply_to_config(self, config: dict): + """Adds this InOutIQChannel to the QUA configuration. + + See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config] + for details. + """ + super().apply_to_config(config) + + # Note outputs instead of inputs because it's w.r.t. the QPU + element_config = config["elements"][self.name] + element_config["smearing"] = self.smearing + element_config["time_of_flight"] = self.time_of_flight + + from quam.components.octave import OctaveDownConverter + + if isinstance(self.frequency_converter_down, OctaveDownConverter): + octave = self.frequency_converter_down.octave + if octave is None: + raise ValueError( + f"Error generating config: channel {self.name} has an " + f"OctaveDownConverter (id={self.frequency_converter_down.id}) " + "without an attached Octave" + ) + element_config["RF_outputs"] = { + "port": (octave.name, self.frequency_converter_down.id) + } + elif str_ref.is_reference(self.frequency_converter_down): + raise ValueError( + f"Error generating config: channel {self.name} could not determine " + f'"frequency_converter_down", it seems to point to a non-existent ' + f"reference: {self.frequency_converter_down}" + ) + else: + # To be filled in next section + element_config["outputs"] = {} + + opx_inputs = [self.opx_input_I, self.opx_input_Q] + offsets = [self.opx_input_offset_I, self.opx_input_offset_Q] + input_gain = int(self.input_gain if self.input_gain is not None else 0) + for k, (opx_input, offset) in enumerate(zip(opx_inputs, offsets), start=1): + if isinstance(opx_input, LFAnalogInputPort): + opx_port = opx_input + elif len(opx_input) == 2: + opx_port = OPXPlusAnalogInputPort( + *opx_input, offset=offset, gain_db=input_gain + ) + opx_port.apply_to_config(config) + else: + opx_port = LFFEMAnalogInputPort( + *opx_input, offset=offset, gain_db=input_gain + ) + opx_port.apply_to_config(config) + if not isinstance(self.frequency_converter_down, OctaveDownConverter): + element_config["outputs"][f"out{k}"] = opx_port.port_tuple + + @quam_dataclass class InOutSingleChannel(SingleChannel, InSingleChannel): """QuAM component for a single (not IQ) input + output channel. @@ -1587,7 +1590,7 @@ def apply_to_config(self, config: Dict) -> None: @quam_dataclass -class InMWChannel(Channel): +class InMWChannel(_InComplexChannel): """QuAM component for a MW FEM input channel Args: