From 4bc30a20bfa7842d57597484c08f307b2787a040 Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Fri, 19 Apr 2024 14:51:16 +0200 Subject: [PATCH 1/5] adding IQChannel.RF/LO/intermediate_frequency --- CHANGELOG.md | 7 ++++++ quam/components/channels.py | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c64eadfe..3e6e2c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [Unreleased] +### Changed +- Added the following parameters to `IQChannel`: `RF_frequency`, `LO_frequency`, `intermediate_frequency` +- Added the following properties to `IQChannel`: `inferred_RF_frequency`, `inferred_LO_frequency`, `inferred_intermediate_frequency` + These properties can be attached to the relevant parameters to infer the frequency from the remaining two parameters. +- Deprecated the `rf_frequency` property in favor of the `RF_frequency` parameter in `IQChannel` + ## [0.3.1] ### Added - Add optional `config_settings` property to quam components indicating that they should be called before/after other components when generating QUA configuration diff --git a/quam/components/channels.py b/quam/components/channels.py index 1d4aa2e6..da9e022f 100644 --- a/quam/components/channels.py +++ b/quam/components/channels.py @@ -778,9 +778,53 @@ class IQChannel(Channel): frequency_converter_up: BaseFrequencyConverter intermediate_frequency: float = 0.0 + LO_frequency: float = "#./frequency_converter_up/LO_frequency" + RF_frequency: float = "#./inferred_RF_frequency" _default_label: ClassVar[str] = "IQ" + @property + def inferred_RF_frequency(self) -> float: + if not isinstance(self.LO_frequency, (float, int)): + raise ValueError( + f"Error inferring RF frequency for channel {self.name}: " + f"LO_frequency is not a number: {self.LO_frequency}" + ) + if not isinstance(self.intermediate_frequency, (float, int)): + raise ValueError( + f"Error inferring RF frequency for channel {self.name}: " + f"intermediate_frequency is not a number: {self.intermediate_frequency}" + ) + return self.LO_frequency + self.intermediate_frequency + + @property + def inferred_intermediate_frequency(self) -> float: + if not isinstance(self.LO_frequency, (float, int)): + raise ValueError( + f"Error inferring intermediate frequency for channel {self.name}: " + f"LO_frequency is not a number: {self.LO_frequency}" + ) + if not isinstance(self.RF_frequency, (float, int)): + raise ValueError( + f"Error inferring intermediate frequency for channel {self.name}: " + f"RF_frequency is not a number: {self.RF_frequency}" + ) + return self.RF_frequency - self.LO_frequency + + @property + def inferred_LO_frequency(self) -> float: + if not isinstance(self.RF_frequency, (float, int)): + raise ValueError( + f"Error inferring LO frequency for channel {self.name}: " + f"RF_frequency is not a number: {self.RF_frequency}" + ) + if not isinstance(self.intermediate_frequency, (float, int)): + raise ValueError( + f"Error inferring LO frequency for channel {self.name}: " + f"intermediate_frequency is not a number: {self.intermediate_frequency}" + ) + return self.RF_frequency - self.intermediate_frequency + @property def local_oscillator(self) -> Optional[LocalOscillator]: return getattr(self.frequency_converter_up, "local_oscillator", None) @@ -791,6 +835,9 @@ def mixer(self) -> Optional[Mixer]: @property def rf_frequency(self): + warnings.warn( + "rf_frequency is deprecated, use RF_frequency instead", DeprecationWarning + ) return self.frequency_converter_up.LO_frequency + self.intermediate_frequency def set_dc_offset(self, offset: QuaNumberType, element_input: Literal["I", "Q"]): From ab01b8534f8d460461aca9a08afd050bdb739bba Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Fri, 19 Apr 2024 14:51:35 +0200 Subject: [PATCH 2/5] modify changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6e2c89..4a49e2ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ ## [Unreleased] -### Changed +### Added - Added the following parameters to `IQChannel`: `RF_frequency`, `LO_frequency`, `intermediate_frequency` - Added the following properties to `IQChannel`: `inferred_RF_frequency`, `inferred_LO_frequency`, `inferred_intermediate_frequency` These properties can be attached to the relevant parameters to infer the frequency from the remaining two parameters. + +### Changed - Deprecated the `rf_frequency` property in favor of the `RF_frequency` parameter in `IQChannel` ## [0.3.1] From 49bfc4e1355b73e14b7bdd04bdad352a1f392b64 Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Thu, 16 May 2024 15:00:13 +0200 Subject: [PATCH 3/5] Added tests --- quam/components/channels.py | 27 ++++---- tests/components/channels/test_IQ_channel.py | 63 +++++++++++++++++++ .../channels/test_in_single_out_IQ_channel.py | 2 + 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/quam/components/channels.py b/quam/components/channels.py index b58b74da..b7fb726d 100644 --- a/quam/components/channels.py +++ b/quam/components/channels.py @@ -786,42 +786,45 @@ class IQChannel(Channel): @property def inferred_RF_frequency(self) -> float: + name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.LO_frequency, (float, int)): - raise ValueError( - f"Error inferring RF frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring RF frequency for channel {name}: " f"LO_frequency is not a number: {self.LO_frequency}" ) if not isinstance(self.intermediate_frequency, (float, int)): - raise ValueError( - f"Error inferring RF frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring RF frequency for channel {name}: " f"intermediate_frequency is not a number: {self.intermediate_frequency}" ) return self.LO_frequency + self.intermediate_frequency @property def inferred_intermediate_frequency(self) -> float: + name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.LO_frequency, (float, int)): - raise ValueError( - f"Error inferring intermediate frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring intermediate frequency for channel {name}: " f"LO_frequency is not a number: {self.LO_frequency}" ) if not isinstance(self.RF_frequency, (float, int)): - raise ValueError( - f"Error inferring intermediate frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring intermediate frequency for channel {name}: " f"RF_frequency is not a number: {self.RF_frequency}" ) return self.RF_frequency - self.LO_frequency @property def inferred_LO_frequency(self) -> float: + name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.RF_frequency, (float, int)): - raise ValueError( - f"Error inferring LO frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring LO frequency for channel {name}: " f"RF_frequency is not a number: {self.RF_frequency}" ) if not isinstance(self.intermediate_frequency, (float, int)): - raise ValueError( - f"Error inferring LO frequency for channel {self.name}: " + raise AttributeError( + f"Error inferring LO frequency for channel {name}: " f"intermediate_frequency is not a number: {self.intermediate_frequency}" ) return self.RF_frequency - self.intermediate_frequency diff --git a/tests/components/channels/test_IQ_channel.py b/tests/components/channels/test_IQ_channel.py index 65596bba..3c3d06f2 100644 --- a/tests/components/channels/test_IQ_channel.py +++ b/tests/components/channels/test_IQ_channel.py @@ -25,3 +25,66 @@ def test_IQ_channel_set_dc_offset(mocker): set_dc_offset.assert_called_once_with( element="channel", element_input="I", offset=0.5 ) + + +def test_IQ_channel_inferred_RF_frequency(): + channel = IQChannel( + opx_output_I=("con1", 1), + opx_output_Q=("con1", 2), + frequency_converter_up=None, + ) + + assert channel.intermediate_frequency == 0.0 + assert channel.LO_frequency == "#./frequency_converter_up/LO_frequency" + assert channel.RF_frequency == "#./inferred_RF_frequency" + with pytest.raises(AttributeError): + channel.inferred_RF_frequency() + + channel.LO_frequency = None + channel.LO_frequency = 5e9 + channel.intermediate_frequency = 100e6 + assert channel.inferred_RF_frequency == 5.1e9 + + +def test_IQ_channel_inferred_intermediate_frequency(): + channel = IQChannel( + opx_output_I=("con1", 1), + opx_output_Q=("con1", 2), + frequency_converter_up=None, + intermediate_frequency="#./inferred_intermediate_frequency", + LO_frequency=5.1e9, + RF_frequency=5.2e9, + ) + + assert channel.intermediate_frequency == 100e6 + + channel.LO_frequency = None + with pytest.raises(AttributeError): + channel.inferred_intermediate_frequency + + channel.LO_frequency = 5.1e9 + channel.RF_frequency = None + with pytest.raises(AttributeError): + channel.inferred_intermediate_frequency + + +def test_IQ_channel_inferred_LO_frequency(): + channel = IQChannel( + opx_output_I=("con1", 1), + opx_output_Q=("con1", 2), + frequency_converter_up=None, + intermediate_frequency=100e6, + LO_frequency="#./inferred_LO_frequency", + RF_frequency=5.2e9, + ) + + assert channel.LO_frequency == 5.1e9 + + channel.RF_frequency = None + with pytest.raises(AttributeError): + channel.inferred_LO_frequency + + channel.RF_frequency = 5.2e9 + channel.intermediate_frequency = None + with pytest.raises(AttributeError): + channel.inferred_LO_frequency diff --git a/tests/components/channels/test_in_single_out_IQ_channel.py b/tests/components/channels/test_in_single_out_IQ_channel.py index 1c20b116..b0ac736c 100644 --- a/tests/components/channels/test_in_single_out_IQ_channel.py +++ b/tests/components/channels/test_in_single_out_IQ_channel.py @@ -13,6 +13,8 @@ def test_in_single_channel_attr_annotations(): assert set(attr_annotations["optional"]) == { "operations", "intermediate_frequency", + "LO_frequency", + "RF_frequency", "opx_output_offset_I", "opx_output_offset_Q", "id", From 66fa4abde8c9445cb4d6a9a57ac09b7ff69183c4 Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Thu, 16 May 2024 15:04:14 +0200 Subject: [PATCH 4/5] Add docstring --- quam/components/channels.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/quam/components/channels.py b/quam/components/channels.py index b7fb726d..4a201d12 100644 --- a/quam/components/channels.py +++ b/quam/components/channels.py @@ -766,6 +766,11 @@ class IQChannel(Channel): opx_output_offset_I float: The offset of the I channel. Default is 0. opx_output_offset_Q float: The offset of the Q channel. Default is 0. intermediate_frequency (float): Intermediate frequency of the mixer. + Default is 0.0 + LO_frequency (float): Local oscillator frequency. Default is the LO frequency + of the frequency converter up component. + RF_frequency (float): RF frequency of the mixer. By default, the RF frequency + is inferred by adding the LO frequency and the intermediate frequency. frequency_converter_up (FrequencyConverter): Frequency converter QuAM component for the IQ output. """ @@ -786,6 +791,12 @@ class IQChannel(Channel): @property def inferred_RF_frequency(self) -> float: + """Inferred RF frequency by adding LO and IF + + Can be used by having reference `RF_frequency = "#./inferred_RF_frequency"` + Returns: + self.LO_frequency + self.intermediate_frequency + """ name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.LO_frequency, (float, int)): raise AttributeError( @@ -801,6 +812,14 @@ def inferred_RF_frequency(self) -> float: @property def inferred_intermediate_frequency(self) -> float: + """Inferred intermediate frequency by subtracting LO from RF + + Can be used by having reference + `intermediate_frequency = "#./inferred_intermediate_frequency"` + + Returns: + self.RF_frequency - self.LO_frequency + """ name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.LO_frequency, (float, int)): raise AttributeError( @@ -816,6 +835,13 @@ def inferred_intermediate_frequency(self) -> float: @property def inferred_LO_frequency(self) -> float: + """Inferred LO frequency by subtracting IF from RF + + Can be used by having reference `LO_frequency = "#./inferred_LO_frequency"` + + Returns: + self.RF_frequency - self.intermediate_frequency + """ name = getattr(self, "name", self.__class__.__name__) if not isinstance(self.RF_frequency, (float, int)): raise AttributeError( @@ -1320,6 +1346,11 @@ class InOutIQChannel(IQChannel, InIQChannel): 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. intermediate_frequency (float): Intermediate frequency of the mixer. + Default is 0.0 + LO_frequency (float): Local oscillator frequency. Default is the LO frequency + of the frequency converter up component. + RF_frequency (float): RF frequency of the mixer. By default, the RF frequency + is inferred by adding the LO frequency and the intermediate frequency. frequency_converter_up (FrequencyConverter): Frequency converter QuAM component for the IQ output. frequency_converter_down (Optional[FrequencyConverter]): Frequency converter @@ -1353,6 +1384,11 @@ class InSingleOutIQChannel(IQChannel, InSingleChannel): a tuple of (controller_name, port). opx_input_offset (float): DC offset for the input port. intermediate_frequency (float): Intermediate frequency of the mixer. + Default is 0.0 + LO_frequency (float): Local oscillator frequency. Default is the LO frequency + of the frequency converter up component. + RF_frequency (float): RF frequency of the mixer. By default, the RF frequency + is inferred by adding the LO frequency and the intermediate frequency. frequency_converter_up (FrequencyConverter): Frequency converter QuAM component for the IQ output. time_of_flight (int): Round-trip signal duration in nanoseconds. From 3e283c0dce8a55a0c051a66d265a8c581a85b27f Mon Sep 17 00:00:00 2001 From: Serwan Asaad Date: Thu, 16 May 2024 15:06:09 +0200 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8d6231..32e2f3fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,15 @@ - Added the following parameters to `IQChannel`: `RF_frequency`, `LO_frequency`, `intermediate_frequency` - Added the following properties to `IQChannel`: `inferred_RF_frequency`, `inferred_LO_frequency`, `inferred_intermediate_frequency` These properties can be attached to the relevant parameters to infer the frequency from the remaining two parameters. - +- Added `IQChannel.inferred_RF/LO/intermediate_frequency` + These can be used to infer the frequency from the remaining two frequencies + ### Changed - Deprecated the `rf_frequency` property in favor of the `RF_frequency` parameter in `IQChannel` - Added channel types: `InSingleChannel`, `InIQChannel`, `InSingleOutIQChannel`, `InIQOutSingleChannel` - Restructured channels to allow for other channel types. +- `IQChannel` now has all three frequency parameters: `RF_frequency`, `LO_frequency`, `intermediate_frequency` +- Deprecated `IQChannel.rf_frequency` in favor of `IQChannel.RF_frequency` ### Fixed - Fixed dataclass ClassVar parameters being wrongly classified as optional or required dataclass args