Skip to content

Commit

Permalink
Merge pull request #29 from qua-platform/feat/channel-IF-LO-RF
Browse files Browse the repository at this point in the history
Feat: Add IQChannel.LO/RF/intermediate_frequency
  • Loading branch information
nulinspiratie authored May 16, 2024
2 parents a50e0ed + 3e283c0 commit 130ff2f
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 1 deletion.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
## [Unreleased]
### Added
- Added channel types: `InSingleChannel`, `InIQChannel`, `InSingleOutIQChannel`, `InIQOutSingleChannel`
- 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
Expand Down
86 changes: 86 additions & 0 deletions quam/components/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -779,9 +784,77 @@ 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:
"""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(
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 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:
"""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(
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 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:
"""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(
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 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

@property
def local_oscillator(self) -> Optional[LocalOscillator]:
return getattr(self.frequency_converter_up, "local_oscillator", None)
Expand All @@ -792,6 +865,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"]):
Expand Down Expand Up @@ -1270,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
Expand Down Expand Up @@ -1303,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.
Expand Down
63 changes: 63 additions & 0 deletions tests/components/channels/test_IQ_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions tests/components/channels/test_in_single_out_IQ_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 130ff2f

Please sign in to comment.