Skip to content

Commit

Permalink
Feature/wirer external mixers (#253)
Browse files Browse the repository at this point in the history
* Add preliminary implementation of a 1-port up-/down-converter external mixer instrument.

* Add documentation and channel spec interface for external mixer.

* Apply black formatting.]
  • Loading branch information
deanpoulos authored Dec 19, 2024
1 parent be92404 commit 39b92da
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 10 deletions.
12 changes: 12 additions & 0 deletions qualang_tools/wirer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ instruments.add_octave(indices=1)
instruments.add_lf_fem(controller=1, slots=[1])
instruments.add_mw_fem(controller=1, slots=[2])
```
#### Setups with External Mixers
Note: An **external mixer** is defined as abstractly as a combined, IQ-upconverter and IQ-downconverter instrument.
```python
# Single LF-FEM and 2x External Mixers
instruments.add_lf_fem(controller=1, slots=[1])
instruments.add_external_mixer(indices=[1, 2])
```
```python
# Single OPX+ and 2x External Mixers
instruments.add_opx_plus(controllers=[1, 2])
instruments.add_external_mixer(indices=[1, 2])
```
<details>
<summary>Image</summary>
<img alt="Empty OPX1000" src=".img/empty_opx1000.png">
Expand Down
39 changes: 35 additions & 4 deletions qualang_tools/wirer/instruments/instrument_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,32 @@ class InstrumentChannelAnalog:
signal_type = "analog"


InstrumentIdType = Literal["lf-fem", "mw-fem", "opx+", "octave", "external-mixer"]


@dataclass(eq=False)
class InstrumentChannelLfFem:
instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "lf-fem"
instrument_id: InstrumentIdType = "lf-fem"


@dataclass(eq=False)
class InstrumentChannelMwFem:
instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "mw-fem"
instrument_id: InstrumentIdType = "mw-fem"


@dataclass(eq=False)
class InstrumentChannelOpxPlus:
instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "opx+"
instrument_id: InstrumentIdType = "opx+"


@dataclass(eq=False)
class InstrumentChannelOctave:
instrument_id: Literal["lf-fem", "mw-fem", "opx+", "octave"] = "octave"
instrument_id: InstrumentIdType = "octave"


@dataclass(eq=False)
class InstrumentChannelExternalMixer:
instrument_id: InstrumentIdType = "external-mixer"


@dataclass(eq=False)
Expand Down Expand Up @@ -122,6 +130,20 @@ class InstrumentChannelOpxPlusDigitalOutput(
pass


@dataclass(eq=False)
class InstrumentChannelExternalMixerInput(
InstrumentChannelAnalog, InstrumentChannelExternalMixer, InstrumentChannelInput, InstrumentChannel
):
pass


@dataclass(eq=False)
class InstrumentChannelExternalMixerOutput(
InstrumentChannelAnalog, InstrumentChannelExternalMixer, InstrumentChannelOutput, InstrumentChannel
):
pass


@dataclass(eq=False)
class InstrumentChannelOctaveInput(
InstrumentChannelAnalog, InstrumentChannelOctave, InstrumentChannelInput, InstrumentChannel
Expand All @@ -143,6 +165,13 @@ class InstrumentChannelOctaveDigitalInput(
pass


@dataclass(eq=False)
class InstrumentChannelExternalMixerDigitalInput(
InstrumentChannelDigital, InstrumentChannelExternalMixer, InstrumentChannelInput, InstrumentChannel
):
pass


AnyInstrumentChannel = Union[
InstrumentChannelLfFemInput,
InstrumentChannelLfFemOutput,
Expand All @@ -152,4 +181,6 @@ class InstrumentChannelOctaveDigitalInput(
InstrumentChannelOpxPlusOutput,
InstrumentChannelOctaveInput,
InstrumentChannelOctaveOutput,
InstrumentChannelExternalMixerInput,
InstrumentChannelExternalMixerOutput,
]
23 changes: 23 additions & 0 deletions qualang_tools/wirer/instruments/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
InstrumentChannelOctaveInput,
InstrumentChannelOctaveOutput,
InstrumentChannelOctaveDigitalInput,
InstrumentChannelExternalMixerInput,
InstrumentChannelExternalMixerOutput,
InstrumentChannelExternalMixerDigitalInput,
)
from .instrument_channels import *
from .constants import *
Expand All @@ -20,6 +23,26 @@ def __init__(self):
self.used_channels = InstrumentChannels()
self.available_channels = InstrumentChannels()

def add_external_mixer(self, indices: Union[List[int], int]):
"""
Add an external mixer, which is defined abstractly as a combined, IQ-upconverter and
IQ-downconverter.
`indices` (List[int] | int): Can be one or more indices for one or more external mixers.
"""
if isinstance(indices, int):
indices = [indices]

for index in indices:
channel = InstrumentChannelExternalMixerInput(con=index, port=1)
self.available_channels.add(channel)

channel = InstrumentChannelExternalMixerOutput(con=index, port=1)
self.available_channels.add(channel)

channel = InstrumentChannelExternalMixerDigitalInput(con=index, port=1)
self.available_channels.add(channel)

def add_octave(self, indices: Union[List[int], int]):
if isinstance(indices, int):
indices = [indices]
Expand Down
34 changes: 30 additions & 4 deletions qualang_tools/wirer/visualizer/instrument_figure_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@ def get_ax(self, con: int, slot: int, instrument_id: str) -> Axes:
if instrument_id == "OPX1000":
fig, axs = self._make_opx1000_figure()
self.figures[key] = {i + 1: ax for i, ax in enumerate(axs)}
fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14)
fig.suptitle(f"con{con} - {instrument_id} Wiring", fontweight="bold", fontsize=14)
elif instrument_id == "OPX+":
fig = self._make_opx_plus_figure()
self.figures[key] = fig.axes[0]
fig.suptitle(f"con{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14)
fig.suptitle(f"con{con} - {instrument_id} Wiring", fontweight="bold", fontsize=14)
elif instrument_id == "Octave":
fig = self._make_octave_figure()
self.figures[key] = fig.axes[0]
fig.suptitle(f"oct{con} - {instrument_id} Wiring Diagram", fontweight="bold", fontsize=14)
fig.suptitle(f"oct{con} - {instrument_id} Wiring", fontweight="bold", fontsize=14)
else:
raise NotImplementedError()
fig = self._make_external_mixer_figure()
self.figures[key] = fig.axes[0]
fig.suptitle(f"Mixers {con} Wiring", fontweight="bold", fontsize=14)

return self.figures[key][slot] if slot is not None else self.figures[key]

Expand Down Expand Up @@ -84,3 +86,27 @@ def _make_opx_plus_figure() -> Figure:
@classmethod
def _make_octave_figure(cls) -> Figure:
return cls._make_opx_plus_figure()

@classmethod
def _make_external_mixer_figure(cls) -> Figure:
fig, ax = plt.subplots(
1,
1,
figsize=(
INSTRUMENT_FIGURE_DIMENSIONS["Mixers"]["width"] * 2,
INSTRUMENT_FIGURE_DIMENSIONS["Mixers"]["height"] * 2,
),
)
ax.text(0.25, 0.25, "Up. RF", ha="center", va="center")
ax.text(0.25, 0.65, "Up. Mkr", ha="center")
ax.text(0.75, 0.25, "Down. RF", ha="center", va="center")
ax.set_ylim([0.15, 1.15])
# ax.set_xlim([0.15 / 8 * 3, 1.15 / 8 * 3])
ax.set_facecolor("darkgrey")
ax.set_xticks([])
ax.set_yticks([])
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_aspect("equal")

return fig
11 changes: 11 additions & 0 deletions qualang_tools/wirer/visualizer/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"mw-fem": "OPX1000",
"opx+": "OPX+",
"octave": "Octave",
"external-mixer": "Mixers",
}

# Define the chassis dimensions
INSTRUMENT_FIGURE_DIMENSIONS = {
"OPX1000": {"width": 8, "height": 3},
"OPX+": {"width": 8, "height": 1},
"Octave": {"width": 3, "height": 1},
"Mixers": {"width": 1, "height": 1},
}

OPX_PLUS_ASPECT = INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["height"] / INSTRUMENT_FIGURE_DIMENSIONS["OPX+"]["width"]
Expand Down Expand Up @@ -57,4 +59,13 @@
"input": [((0.3 + j * 0.06) * 3, 0.18) for j in range(5)],
},
},
"external-mixer": {
"analog": {
"input": [(0.75, 0.45)],
"output": [(0.25, 0.45)],
},
"digital": {
"input": [(0.25, 0.85)],
},
},
}
18 changes: 17 additions & 1 deletion qualang_tools/wirer/visualizer/port_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def draw(self, ax: Axes):
if self.signal_type == "digital" and self.instrument_id in ["lf-fem", "mw-fem"]:
port_size = PORT_SIZE / 2.4
bbox = dict(facecolor=fill_color, alpha=0.8, edgecolor="none")
else:
elif self.instrument_id in ["opx+", "octave"]:
port_size = PORT_SIZE
port_label_distance = PORT_SIZE * 1.3
ax.text(
Expand All @@ -46,6 +46,22 @@ def draw(self, ax: Axes):
color=outline_colour,
)
bbox = None
elif self.instrument_id in ["external-mixer"]:
port_size = PORT_SIZE * 2.4
port_label_distance = PORT_SIZE * 3.2
ax.text(
x - port_label_distance,
y,
str(self.port),
ha="center",
va="center",
fontsize=8,
fontweight="bold",
color=outline_colour,
)
bbox = None
else:
raise NotImplementedError(f"No port-annotation drawing for {self.instrument_id}")

ax.add_patch(patches.Circle((x, y), port_size, edgecolor=outline_colour, facecolor=fill_color))
labels = combine_labels_for_same_line_type(self.labels)
Expand Down
64 changes: 64 additions & 0 deletions qualang_tools/wirer/wirer/channel_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
InstrumentChannelOpxPlusOutput,
InstrumentChannelOctaveInput,
InstrumentChannelOctaveOutput,
InstrumentChannelExternalMixerInput,
InstrumentChannelExternalMixerOutput,
InstrumentChannelOpxPlusDigitalOutput,
InstrumentChannelMwFemDigitalOutput,
InstrumentChannelLfFemDigitalOutput,
InstrumentChannelOctaveDigitalInput,
InstrumentChannelExternalMixerDigitalInput,
)

# A channel template is a partially filled InstrumentChannel object
Expand Down Expand Up @@ -99,6 +102,15 @@ def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None):
]


class ChannelSpecExternalMixer(ChannelSpec):
def __init__(self, index: int = None, rf_in: int = None, rf_out: int = None):
super().__init__()
self.channel_templates = [
InstrumentChannelExternalMixerInput(con=index, port=rf_in),
InstrumentChannelExternalMixerOutput(con=index, port=rf_out),
]


class ChannelSpecLfFemBasebandAndOctave(ChannelSpec):
def __init__(
self,
Expand All @@ -123,6 +135,28 @@ def __init__(
]


class ChannelSpecLfFemBasebandAndExternalMixer(ChannelSpec):
def __init__(
self,
con: int = None,
slot: int = None,
in_port_i: int = None,
in_port_q: int = None,
out_port_i: int = None,
out_port_q: int = None,
mixer_index: int = None,
):
super().__init__()
self.channel_templates = [
InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_i),
InstrumentChannelLfFemInput(con=con, slot=slot, port=in_port_q),
InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_i),
InstrumentChannelLfFemOutput(con=con, slot=slot, port=out_port_q),
InstrumentChannelExternalMixerInput(con=mixer_index, port=1),
InstrumentChannelExternalMixerOutput(con=mixer_index, port=1),
]


class ChannelSpecOpxPlusBasebandAndOctave(ChannelSpec):
def __init__(
self,
Expand All @@ -146,6 +180,27 @@ def __init__(
]


class ChannelSpecOpxPlusBasebandAndExternalMixer(ChannelSpec):
def __init__(
self,
con: int = None,
in_port_i: int = None,
in_port_q: int = None,
out_port_i: int = None,
out_port_q: int = None,
mixer_index: int = None,
):
super().__init__()
self.channel_templates = [
InstrumentChannelOpxPlusInput(con=con, port=in_port_i),
InstrumentChannelOpxPlusInput(con=con, port=in_port_q),
InstrumentChannelOpxPlusOutput(con=con, port=out_port_i),
InstrumentChannelOpxPlusOutput(con=con, port=out_port_q),
InstrumentChannelExternalMixerInput(con=mixer_index, port=1),
InstrumentChannelExternalMixerOutput(con=mixer_index, port=1),
]


class ChannelSpecOpxPlusDigital(ChannelSpec):
def __init__(self, con: int = None, out_port: int = None):
super().__init__()
Expand All @@ -170,12 +225,21 @@ def __init__(self, con: int = None, in_port: int = None):
self.channel_templates = [InstrumentChannelOctaveDigitalInput(con=con, port=in_port)]


class ChannelSpecExternalMixerDigital(ChannelSpec):
def __init__(self, con: int = None, in_port: int = None):
super().__init__()
self.channel_templates = [InstrumentChannelExternalMixerDigitalInput(con=con, port=in_port)]


mw_fem_spec = ChannelSpecMwFemSingle
lf_fem_spec = ChannelSpecLfFemSingle
lf_fem_iq_spec = ChannelSpecLfFemBaseband
lf_fem_iq_octave_spec = ChannelSpecLfFemBasebandAndOctave
lf_fem_iq_ext_mixer_spec = ChannelSpecLfFemBasebandAndExternalMixer
opx_spec = ChannelSpecOpxPlusSingle
opx_iq_spec = ChannelSpecOpxPlusBaseband
opx_iq_octave_spec = ChannelSpecOpxPlusBasebandAndOctave
opx_iq_ext_mixer_spec = ChannelSpecOpxPlusBasebandAndExternalMixer
octave_spec = ChannelSpecOctave
ext_mixer_spec = ChannelSpecExternalMixer
opx_dig_spec = ChannelSpecOpxPlusDigital
15 changes: 15 additions & 0 deletions qualang_tools/wirer/wirer/wirer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
ChannelSpecMwFemSingle,
ChannelSpecLfFemBaseband,
ChannelSpecOctave,
ChannelSpecExternalMixer,
ChannelSpecOpxPlusBaseband,
ChannelSpecMwFemDigital,
ChannelSpecLfFemDigital,
ChannelSpecOctaveDigital,
ChannelSpecOpxPlusDigital,
ChannelSpecExternalMixerDigital,
)
from .wirer_assign_channels_to_spec import assign_channels_to_spec
from .wirer_exceptions import ConstraintsTooStrictException, NotEnoughChannelsException
Expand Down Expand Up @@ -98,9 +100,22 @@ def allocate_rf_channels(spec: WiringSpec, instruments: Instruments):
channels.
"""
rf_specs = [
# MW-FEM, Single RF output
ChannelSpecMwFemSingle() & ChannelSpecMwFemDigital(),
# LF-FEM I/Q output with Octave for upconversion
ChannelSpecLfFemBaseband() & ChannelSpecLfFemDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital(),
# LF-FEM I/Q output with External Mixer for upconversion
ChannelSpecLfFemBaseband()
& ChannelSpecLfFemDigital()
& ChannelSpecExternalMixer()
& ChannelSpecExternalMixerDigital(),
# OPX+ I/Q output with Octave for upconversion
ChannelSpecOpxPlusBaseband() & ChannelSpecOpxPlusDigital() & ChannelSpecOctave() & ChannelSpecOctaveDigital(),
# OPX+ I/Q output with External Mixer for upconversion
ChannelSpecOpxPlusBaseband()
& ChannelSpecOpxPlusDigital()
& ChannelSpecExternalMixer()
& ChannelSpecExternalMixerDigital(),
]

allocate_channels(spec, rf_specs, instruments, same_con=True, same_slot=True)
Expand Down
Loading

0 comments on commit 39b92da

Please sign in to comment.