Skip to content

QUAM Ports API

Welcome to the QUAM Ports API Documentation. The QUAM Ports module provides abstractions for hardware connection points including analog and digital inputs/outputs. Information can be found in QUAM Ports Documentation in the User Guide.

This section provides detailed API references for port types—from base ports to specific analog and digital implementations—that represent physical connections between the quantum control hardware and quantum devices.

BasePort

Bases: QuamComponent, ABC

Source code in quam/components/ports/base_ports.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@quam_dataclass
class BasePort(QuamComponent, ABC):
    port_type: ClassVar[str]

    @abstractmethod
    def get_port_config(
        self, config: Dict[str, Any], create: bool = True
    ) -> Dict[str, Any]:
        pass

    @abstractmethod
    def get_port_properties(self) -> Dict[str, Any]:
        pass

    @property
    @abstractmethod
    def port_tuple(self) -> Union[Tuple[str, int], Tuple[str, int, int]]:
        pass

    def _update_port_config(self, port_config, port_properties):
        for key, value in port_properties.items():
            try:
                if key in port_config and value != port_config[key]:
                    warnings.warn(
                        f"Error generating QUA config: Controller {self.port_type} "
                        f"port {self.port_tuple} already has entry for {key}. This "
                        f"likely means that the port is being configured multiple "
                        f"times. Overwriting {port_config[key]}{value}."
                    )
            except Exception:
                pass
            port_config[key] = value

    def apply_to_config(self, config: Dict) -> None:
        super().apply_to_config(config)

        port_cfg = self.get_port_config(config)
        port_properties = self.get_port_properties()
        self._update_port_config(port_cfg, port_properties)

OPXPlusPort

Bases: BasePort, ABC

Source code in quam/components/ports/base_ports.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@quam_dataclass(kw_only=False)
class OPXPlusPort(BasePort, ABC):
    controller_id: Union[str, int]
    port_id: int

    @property
    def port_tuple(self) -> Tuple[Union[str, int], int]:
        return self.controller_id, self.port_id

    def get_port_config(
        self, config: Dict[str, Any], create: bool = True
    ) -> Dict[str, Any]:

        if not create:
            try:
                controller_cfg = config["controllers"][self.controller_id]
                return controller_cfg[f"{self.port_type}"][self.port_id]
            except KeyError:
                raise KeyError(
                    f"Error generating config: controller {self.controller_id} does "
                    f"not have entry {self.port_type}s for port {self.port_tuple}"
                )

        controller_cfg = config["controllers"].setdefault(self.controller_id, {})
        ports_cfg = controller_cfg.setdefault(f"{self.port_type}s", {})
        port_cfg = ports_cfg.setdefault(self.port_id, {})
        return port_cfg

FEMPort

Bases: BasePort, ABC

Source code in quam/components/ports/base_ports.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
@quam_dataclass(kw_only=False)
class FEMPort(BasePort, ABC):
    fem_type: ClassVar[str]
    controller_id: Union[str, int]
    fem_id: int
    port_id: int

    @property
    def port_tuple(self) -> Tuple[Union[str, int], int, int]:
        return self.controller_id, self.fem_id, self.port_id

    def get_port_config(
        self, config: Dict[str, Any], create: bool = True
    ) -> Dict[str, Any]:

        if not create:
            try:
                controller_cfg = config["controllers"][self.controller_id]
                fem_cfg = controller_cfg["fems"][self.fem_id]
            except KeyError:
                raise KeyError(
                    f"Error generating config: controller {self.controller_id} does "
                    f"not have entry for FEM {self.fem_id} for "
                    f"port {self.port_id}"
                )
            try:
                return fem_cfg[f"{self.port_type}s"][self.port_id]
            except KeyError:
                raise KeyError(
                    f"Error generating config: controller {self.controller_id} does "
                    f"not have entry {self.port_type}s for port {self.port_tuple}"
                )

        controller_cfg = config["controllers"].setdefault(self.controller_id, {})
        fems_cfg = controller_cfg.setdefault("fems", {})
        fem_cfg = fems_cfg.setdefault(self.fem_id, {})
        if hasattr(self, "fem_type"):
            if fem_cfg.get("type", self.fem_type) != self.fem_type:
                raise ValueError(
                    f"Error generating config: FEM {self.fem_id} is not of type "
                    f"{self.fem_type}"
                )
            fem_cfg["type"] = self.fem_type

        ports_cfg = fem_cfg.setdefault(f"{self.port_type}s", {})
        port_cfg = ports_cfg.setdefault(self.port_id, {})
        return port_cfg

OPXPlusAnalogOutputPort

Bases: LFAnalogOutputPort, OPXPlusPort

Source code in quam/components/ports/analog_outputs.py
47
48
49
50
@quam_dataclass
class OPXPlusAnalogOutputPort(LFAnalogOutputPort, OPXPlusPort):
    sampling_rate: ClassVar[float] = 1e9
    pass

OPXPlusAnalogInputPort

Bases: LFAnalogInputPort, OPXPlusPort

Source code in quam/components/ports/analog_inputs.py
34
35
36
@quam_dataclass
class OPXPlusAnalogInputPort(LFAnalogInputPort, OPXPlusPort):
    pass

LFAnalogOutputPort

Bases: BasePort, ABC

Source code in quam/components/ports/analog_outputs.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@quam_dataclass
class LFAnalogOutputPort(BasePort, ABC):
    fem_type: ClassVar[str] = "LF"
    port_type: ClassVar[str] = "analog_output"

    offset: Optional[float] = None
    delay: int = 0
    crosstalk: Optional[Dict[int, float]] = None
    feedforward_filter: Optional[List[float]] = None
    feedback_filter: Optional[List[float]] = None
    shareable: bool = False

    def get_port_properties(self):
        port_properties = {
            "delay": self.delay,
            "shareable": self.shareable,
        }
        if self.crosstalk is not None:
            port_properties["crosstalk"] = dict(self.crosstalk)
        if self.feedforward_filter is not None:
            port_properties.setdefault("filter", {})["feedforward"] = list(
                self.feedforward_filter
            )
        if self.feedback_filter is not None:
            port_properties.setdefault("filter", {})["feedback"] = list(
                self.feedback_filter
            )
        if self.offset is not None:
            port_properties["offset"] = self.offset
        return port_properties

LFAnalogInputPort

Bases: BasePort, ABC

Source code in quam/components/ports/analog_inputs.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@quam_dataclass
class LFAnalogInputPort(BasePort, ABC):
    fem_type: ClassVar[str] = "LF"
    port_type: ClassVar[str] = "analog_input"

    offset: Optional[float] = None
    gain_db: int = 0
    shareable: bool = False

    def get_port_properties(self):
        port_cfg = {
            "gain_db": self.gain_db,
            "shareable": self.shareable,
        }
        if self.offset is not None:
            port_cfg["offset"] = self.offset
        return port_cfg

LFFEMAnalogOutputPort

Bases: LFAnalogOutputPort, FEMPort

Source code in quam/components/ports/analog_outputs.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@quam_dataclass
class LFFEMAnalogOutputPort(LFAnalogOutputPort, FEMPort):
    fem_type: ClassVar[str] = "LF"
    sampling_rate: float = 1e9  # Either 1e9 or 2e9
    upsampling_mode: Literal["mw", "pulse"] = "mw"
    exponential_filter: Optional[List[Tuple[float, float]]] = None
    exponential_dc_gain: Optional[float] = None
    high_pass_filter: Optional[float] = None
    output_mode: Literal["direct", "amplified"] = "direct"

    def get_port_properties(self) -> Dict[str, Any]:
        port_properties = super().get_port_properties()

        if (
            self.exponential_filter is not None
            or self.high_pass_filter is not None
            or self.exponential_dc_gain is not None
        ):
            if self.feedback_filter is not None:
                raise ValueError(
                    "LFFEMAnalogOutputPort: Please only specify 'exponential_filter' / "
                    "'high_pass_filter' / 'exponential_dc_gain' if QOP >=3.3.0, or 'feedback_filter' if "
                    "QOP < 3.3.0, not both"
                )

        if self.exponential_filter is not None:
            filter_properties = port_properties.setdefault("filter", {})
            filter_properties["exponential"] = list(self.exponential_filter)

        if self.exponential_dc_gain is not None:
            filter_properties = port_properties.setdefault("filter", {})
            filter_properties["exponential_dc_gain"] = self.exponential_dc_gain

        if self.high_pass_filter is not None:
            filter_properties = port_properties.setdefault("filter", {})
            filter_properties["high_pass"] = self.high_pass_filter

        port_properties["sampling_rate"] = self.sampling_rate
        if self.sampling_rate == 1e9:
            port_properties["upsampling_mode"] = self.upsampling_mode
        port_properties["output_mode"] = self.output_mode
        return port_properties

LFFEMAnalogInputPort

Bases: LFAnalogInputPort, FEMPort

Source code in quam/components/ports/analog_inputs.py
39
40
41
42
43
44
45
46
@quam_dataclass
class LFFEMAnalogInputPort(LFAnalogInputPort, FEMPort):
    sampling_rate: float = 1e9  # Either 1e9 or 2e9

    def get_port_properties(self) -> Dict[str, Any]:
        port_properties = super().get_port_properties()
        port_properties["sampling_rate"] = self.sampling_rate
        return port_properties

MWFEMAnalogOutputPort

Bases: FEMPort

Source code in quam/components/ports/analog_outputs.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@quam_dataclass
class MWFEMAnalogOutputPort(FEMPort):
    fem_type: ClassVar[str] = "MW"
    port_type: ClassVar[str] = "analog_output"

    band: int
    upconverter_frequency: Optional[float] = None
    upconverters: Optional[Dict[int, Dict[str, float]]] = None
    delay: int = 0
    shareable: bool = False
    sampling_rate: float = 1e9  # Either 1e9 or 2e9
    full_scale_power_dbm: int = -11

    def __post_init__(self) -> None:
        super().__post_init__()
        if self.upconverter_frequency is None and self.upconverters is None:
            raise ValueError(
                "MWAnalogOutputPort: Either upconverter_frequency or upconverters must "
                "be provided"
            )

    def get_port_properties(self) -> Dict[str, Any]:
        port_cfg = {
            "band": self.band,
            "delay": self.delay,
            "shareable": self.shareable,
            "sampling_rate": self.sampling_rate,
            "full_scale_power_dbm": self.full_scale_power_dbm,
        }
        if self.upconverter_frequency is not None:
            port_cfg["upconverter_frequency"] = self.upconverter_frequency
        if self.upconverters is not None:
            port_cfg["upconverters"] = {
                key: dict(val) for key, val in self.upconverters.items()
            }
        return port_cfg

MWFEMAnalogInputPort

Bases: FEMPort

Source code in quam/components/ports/analog_inputs.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@quam_dataclass
class MWFEMAnalogInputPort(FEMPort):
    fem_type: ClassVar[str] = "MW"
    port_type: ClassVar[str] = "analog_input"

    band: int
    downconverter_frequency: float
    gain_db: Optional[int] = None
    sampling_rate: float = 1e9  # Either 1e9 or 2e9
    shareable: bool = False

    def get_port_properties(self) -> Dict[str, Any]:
        port_properties = {
            "band": self.band,
            "downconverter_frequency": self.downconverter_frequency,
            "sampling_rate": self.sampling_rate,
            "shareable": self.shareable,
        }
        if self.gain_db is not None:
            port_properties["gain_db"] = self.gain_db
        return port_properties

OPXPlusDigitalOutputPort

Bases: DigitalOutputPort, OPXPlusPort

Source code in quam/components/ports/digital_outputs.py
21
22
23
@quam_dataclass
class OPXPlusDigitalOutputPort(DigitalOutputPort, OPXPlusPort):
    pass

OPXPlusDigitalInputPort

Bases: OPXPlusPort

Source code in quam/components/ports/digital_inputs.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@quam_dataclass
class OPXPlusDigitalInputPort(OPXPlusPort):
    port_type: ClassVar[str] = "digital_input"

    deadtime: int = 4
    polarity: Literal["rising", "falling"] = "rising"
    threshold: float = 2.0
    shareable: bool = False

    def get_port_properties(self) -> Dict[str, Any]:
        return {
            "deadtime": self.deadtime,
            "polarity": self.polarity,
            "threshold": self.threshold,
            "shareable": self.shareable,
        }

DigitalOutputPort

Bases: BasePort, ABC

Source code in quam/components/ports/digital_outputs.py
10
11
12
13
14
15
16
17
18
@quam_dataclass
class DigitalOutputPort(BasePort, ABC):
    port_type: ClassVar[str] = "digital_output"

    inverted: bool = False
    shareable: bool = False

    def get_port_properties(self) -> Dict[str, Any]:
        return {"inverted": self.inverted, "shareable": self.shareable}

FEMDigitalOutputPort

Bases: DigitalOutputPort, FEMPort

Source code in quam/components/ports/digital_outputs.py
26
27
28
29
30
31
32
33
@quam_dataclass
class FEMDigitalOutputPort(DigitalOutputPort, FEMPort):
    level: Literal["TTL", "LVTTL"] = "LVTTL"

    def get_port_properties(self) -> Dict[str, Any]:
        port_properties = super().get_port_properties()
        port_properties["level"] = self.level
        return port_properties

OPXPlusPortsContainer

Bases: QuamComponent

Source code in quam/components/ports/ports_containers.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@quam_dataclass
class OPXPlusPortsContainer(QuamComponent):
    analog_outputs: Dict[Union[str, int], Dict[int, OPXPlusAnalogOutputPort]] = field(
        default_factory=dict
    )
    analog_inputs: Dict[Union[str, int], Dict[int, OPXPlusAnalogInputPort]] = field(
        default_factory=dict
    )
    digital_outputs: Dict[Union[str, int], Dict[int, OPXPlusDigitalOutputPort]] = field(
        default_factory=dict
    )
    digital_inputs: Dict[Union[str, int], Dict[int, OPXPlusDigitalInputPort]] = field(
        default_factory=dict
    )

    def _get_port(
        self,
        controller_id: Union[str, int],
        port_id: int,
        port_type: str,
        create: bool = False,
        **kwargs,
    ):
        if port_type not in {
            "analog_output",
            "analog_input",
            "digital_output",
            "digital_input",
        }:
            raise ValueError(f"Invalid port type: {port_type}")

        controllers = getattr(self, f"{port_type}s")

        try:
            return controllers[controller_id][port_id]
        except KeyError:
            if not create:
                raise KeyError(
                    f"Could not find existing {port_type} port: "
                    f"{port_type} ({controller_id}, {port_id}"
                )

        controllers.setdefault(controller_id, {})
        ports = controllers[controller_id]

        if port_type == "analog_output":
            ports[port_id] = OPXPlusAnalogOutputPort(controller_id, port_id, **kwargs)
        elif port_type == "analog_input":
            ports[port_id] = OPXPlusAnalogInputPort(controller_id, port_id, **kwargs)
        elif port_type == "digital_output":
            ports[port_id] = OPXPlusDigitalOutputPort(controller_id, port_id, **kwargs)
        elif port_type == "digital_input":
            ports[port_id] = OPXPlusDigitalInputPort(controller_id, port_id, **kwargs)

        return ports[port_id]

    def reference_to_port(
        self,
        port_reference: Union[QuamComponent, str],
        attr: Optional[str] = None,
        create=False,
    ) -> OPXPlusPortTypes:
        if isinstance(port_reference, QuamComponent):
            reference = port_reference.get_reference(attr=attr)
            if reference is None:
                raise ValueError("Cannot get port from reference {port_reference}")
            port_reference = reference

        try:
            elems = port_reference.split("/")
            port_type, controller_id, port_id = elems[-3:]

            port_type = port_type[:-1]
            if controller_id.isdigit():
                controller_id = int(controller_id)
            port_id = int(port_id)
        except Exception as e:
            raise ValueError(
                f"Unable to parse port reference for OPX+: {port_reference}"
            ) from e

        return self._get_port(controller_id, port_id, port_type, create=create)

    def get_analog_output(
        self,
        controller_id: Union[str, int],
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> OPXPlusAnalogOutputPort:
        return self._get_port(
            controller_id, port_id, port_type="analog_output", create=create, **kwargs
        )

    def get_analog_input(
        self,
        controller_id: Union[str, int],
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> OPXPlusAnalogInputPort:
        return self._get_port(
            controller_id, port_id, port_type="analog_input", create=create, **kwargs
        )

    def get_digital_output(
        self,
        controller_id: Union[str, int],
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> OPXPlusDigitalOutputPort:
        return self._get_port(
            controller_id, port_id, port_type="digital_output", create=create, **kwargs
        )

    def get_digital_input(
        self,
        controller_id: Union[str, int],
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> OPXPlusDigitalInputPort:
        return self._get_port(
            controller_id, port_id, port_type="digital_input", create=create, **kwargs
        )

FEMPortsContainer

Bases: QuamComponent

Source code in quam/components/ports/ports_containers.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
@quam_dataclass
class FEMPortsContainer(QuamComponent):
    num_port_elems: ClassVar[int] = 3
    analog_outputs: Dict[
        Union[str, int], Dict[int, Dict[int, LFFEMAnalogOutputPort]]
    ] = field(default_factory=dict)
    analog_inputs: Dict[Union[str, int], Dict[int, Dict[int, LFFEMAnalogInputPort]]] = (
        field(default_factory=dict)
    )
    mw_outputs: Dict[Union[str, int], Dict[int, Dict[int, MWFEMAnalogOutputPort]]] = (
        field(default_factory=dict)
    )
    mw_inputs: Dict[Union[str, int], Dict[int, Dict[int, MWFEMAnalogInputPort]]] = (
        field(default_factory=dict)
    )
    digital_outputs: Dict[
        Union[str, int], Dict[int, Dict[int, FEMDigitalOutputPort]]
    ] = field(default_factory=dict)

    def _get_port(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        port_type: str,
        create: bool = False,
        **kwargs,
    ):
        if port_type not in {
            "analog_output",
            "analog_input",
            "mw_output",
            "mw_input",
            "digital_output",
        }:
            raise ValueError(f"Invalid port type: {port_type}")

        controllers = getattr(self, f"{port_type}s")

        try:
            return controllers[controller_id][fem_id][port_id]
        except KeyError:
            if not create:
                raise KeyError(
                    f"Could not find existing {port_type} port: "
                    f"{port_type} ({controller_id}, {fem_id}, {port_id}"
                )

        controllers.setdefault(controller_id, {})
        fems = controllers[controller_id]
        fems.setdefault(fem_id, {})
        ports = fems[fem_id]

        if port_type == "analog_output":
            ports[port_id] = LFFEMAnalogOutputPort(
                controller_id, fem_id, port_id, **kwargs
            )
        elif port_type == "analog_input":
            ports[port_id] = LFFEMAnalogInputPort(
                controller_id, fem_id, port_id, **kwargs
            )
        elif port_type == "mw_output":
            # Set default values in kwargs before passing to constructor
            # Safe to mutate since kwargs is used only once per port creation
            if "upconverter_frequency" not in kwargs and "upconverters" not in kwargs:
                kwargs["upconverter_frequency"] = 5e9
            if "band" not in kwargs:
                kwargs["band"] = 1
            ports[port_id] = MWFEMAnalogOutputPort(
                controller_id, fem_id, port_id, **kwargs
            )
        elif port_type == "mw_input":
            # Set default values in kwargs before passing to constructor
            # Safe to mutate since kwargs is used only once per port creation
            if "band" not in kwargs:
                kwargs["band"] = 1
            if "downconverter_frequency" not in kwargs:
                kwargs["downconverter_frequency"] = 5e9
            ports[port_id] = MWFEMAnalogInputPort(
                controller_id,
                fem_id,
                port_id,
                **kwargs,
            )
        elif port_type == "digital_output":
            ports[port_id] = FEMDigitalOutputPort(
                controller_id, fem_id, port_id, **kwargs
            )

        return ports[port_id]

    def reference_to_port(
        self,
        port_reference: Union[QuamComponent, str],
        attr: Optional[str] = None,
        create=False,
    ) -> FEMPortTypes:
        if isinstance(port_reference, QuamBase):
            reference = port_reference.get_reference(attr=attr)
            if reference is None:
                raise ValueError("Cannot get port from reference {port_reference}")
            port_reference = reference

        try:
            elems = port_reference.split("/")
            port_type, controller_id, fem_id, port_id = elems[-4:]

            port_type = port_type[:-1]
            if controller_id.isdigit():
                controller_id = int(controller_id)
            fem_id = int(fem_id)
            port_id = int(port_id)
        except Exception as e:
            raise ValueError(
                f"Unable to parse port reference for OPX1000 FEM: {port_reference}"
            ) from e

        return self._get_port(controller_id, fem_id, port_id, port_type, create=create)

    def get_analog_output(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> LFFEMAnalogOutputPort:
        return self._get_port(
            controller_id,
            fem_id,
            port_id,
            port_type="analog_output",
            create=create,
            **kwargs,
        )

    def get_analog_input(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> LFFEMAnalogInputPort:
        return self._get_port(
            controller_id,
            fem_id,
            port_id,
            port_type="analog_input",
            create=create,
            **kwargs,
        )

    def get_mw_output(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> MWFEMAnalogOutputPort:
        return self._get_port(
            controller_id,
            fem_id,
            port_id,
            port_type="mw_output",
            create=create,
            **kwargs,
        )

    def get_mw_input(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> MWFEMAnalogInputPort:
        return self._get_port(
            controller_id,
            fem_id,
            port_id,
            port_type="mw_input",
            create=create,
            **kwargs,
        )

    def get_digital_output(
        self,
        controller_id: Union[str, int],
        fem_id: int,
        port_id: int,
        create: bool = False,
        **kwargs,
    ) -> FEMDigitalOutputPort:
        return self._get_port(
            controller_id,
            fem_id,
            port_id,
            port_type="digital_output",
            create=create,
            **kwargs,
        )