Skip to content

Commit 4e4ea9b

Browse files
authored
Merge pull request #7905 from jenshnielsen/jenshnielsen/strict_keysight
Keysight drivers avoid internal dependency on dynamic attributes
2 parents 69ebb2e + 1815e0b commit 4e4ea9b

9 files changed

Lines changed: 103 additions & 63 deletions

File tree

src/qcodes/instrument_drivers/Keysight/Infiniium.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import qcodes.validators as vals
1414
from qcodes.instrument import (
1515
ChannelList,
16+
ChannelTuple,
1617
InstrumentBase,
1718
InstrumentBaseKWArgs,
1819
InstrumentChannel,
@@ -89,7 +90,11 @@ def get_raw(self) -> npt.NDArray:
8990
)
9091

9192

92-
class DSOTraceParam(ParameterWithSetpoints):
93+
class DSOTraceParam(
94+
ParameterWithSetpoints[
95+
npt.NDArray, "KeysightInfiniiumChannel | KeysightInfiniiumFunction"
96+
]
97+
):
9398
"""
9499
Trace parameter for the Infiniium series DSO
95100
"""
@@ -180,7 +185,7 @@ def update_setpoints(self, preamble: "Sequence[str] | None" = None) -> None:
180185
acquisition if instr.cache_setpoints is False
181186
"""
182187
instrument: KeysightInfiniiumChannel | KeysightInfiniiumFunction
183-
instrument = self.instrument # type: ignore[assignment]
188+
instrument = self.instrument
184189
if preamble is None:
185190
instrument.write(f":WAV:SOUR {self._channel}")
186191
preamble = instrument.ask(":WAV:PRE?").strip().split(",")
@@ -257,13 +262,15 @@ def __init__(
257262
self,
258263
parent: InstrumentBase,
259264
name: str,
265+
channel: str,
260266
**kwargs: "Unpack[InstrumentBaseKWArgs]",
261267
) -> None:
262268
"""
263269
Add parameters to measurement subsystem. Note: This should not be initialized
264270
directly, rather initialize BoundMeasurementSubsystem
265271
or UnboundMeasurementSubsystem.
266272
"""
273+
self._channel = channel
267274
super().__init__(parent, name, **kwargs)
268275

269276
###################################
@@ -477,11 +484,8 @@ def __init__(
477484
"""
478485
Initialize measurement subsystem bound to a specific channel
479486
"""
480-
# Bind the channel
481-
self._channel = parent.channel_name
482-
483487
# Initialize measurement parameters
484-
super().__init__(parent, name, **kwargs)
488+
super().__init__(parent, name, channel=parent.channel_name, **kwargs)
485489

486490

487491
BoundMeasurement = KeysightInfiniiumBoundMeasurement
@@ -500,11 +504,8 @@ def __init__(
500504
"""
501505
Initialize measurement subsystem where target is set by the parameter `source`.
502506
"""
503-
# Blank channel
504-
self._channel = ""
505-
506507
# Initialize measurement parameters
507-
super().__init__(parent, name, **kwargs)
508+
super().__init__(parent, name, channel="", **kwargs)
508509

509510
self.source = Parameter(
510511
name="source",
@@ -515,6 +516,12 @@ def __init__(
515516
snapshot_value=False,
516517
)
517518

519+
@property
520+
def root_instrument(self) -> "KeysightInfiniium":
521+
root_instrument = super().root_instrument
522+
assert isinstance(root_instrument, KeysightInfiniium)
523+
return root_instrument
524+
518525
def _validate_source(self, source: str) -> str:
519526
"""Validate and set the source."""
520527
valid_channels = f"CHAN[1-{self.root_instrument.no_channels}]"
@@ -692,7 +699,7 @@ def _get_func(self) -> str:
692699
"""
693700

694701

695-
class KeysightInfiniiumChannel(InstrumentChannel):
702+
class KeysightInfiniiumChannel(InstrumentChannel["KeysightInfiniium"]):
696703
def __init__(
697704
self,
698705
parent: "KeysightInfiniium",
@@ -1081,7 +1088,10 @@ def __init__(
10811088
channel = KeysightInfiniiumChannel(self, f"chan{i}", i)
10821089
_channels.append(channel)
10831090
self.add_submodule(f"ch{i}", channel)
1084-
self.add_submodule("channels", _channels.to_channel_tuple())
1091+
self.channels: ChannelTuple[KeysightInfiniiumChannel] = self.add_submodule(
1092+
"channels", _channels.to_channel_tuple()
1093+
)
1094+
"""Tuple of oscilloscope channels."""
10851095

10861096
# Functions
10871097
_functions = ChannelList(
@@ -1093,11 +1103,17 @@ def __init__(
10931103
self.add_submodule(f"func{i}", function)
10941104
# Have to call channel list "funcs" here as functions is a
10951105
# reserved name in Instrument.
1096-
self.add_submodule("funcs", _functions.to_channel_tuple())
1106+
self.funcs: ChannelTuple[KeysightInfiniiumFunction] = self.add_submodule(
1107+
"funcs", _functions.to_channel_tuple()
1108+
)
1109+
"""Tuple of oscilloscope functions."""
10971110

10981111
# Submodules
10991112
meassubsys = KeysightInfiniiumUnboundMeasurement(self, "measure")
1100-
self.add_submodule("measure", meassubsys)
1113+
self.measure: KeysightInfiniiumUnboundMeasurement = self.add_submodule(
1114+
"measure", meassubsys
1115+
)
1116+
"""Unbound measurement subsystem."""
11011117

11021118
def _query_capabilities(self) -> None:
11031119
"""

src/qcodes/instrument_drivers/Keysight/KeysightAgilent_33XXX.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from qcodes import validators as vals
66
from qcodes.instrument import (
7-
Instrument,
87
InstrumentBaseKWArgs,
98
InstrumentChannel,
109
VisaInstrument,
@@ -26,14 +25,14 @@
2625
# 33200, 33500, and 33600
2726

2827

29-
class Keysight33xxxOutputChannel(InstrumentChannel):
28+
class Keysight33xxxOutputChannel(InstrumentChannel["Keysight33xxx"]):
3029
"""
3130
Class to hold the output channel of a Keysight 33xxxx waveform generator.
3231
"""
3332

3433
def __init__(
3534
self,
36-
parent: Instrument,
35+
parent: "Keysight33xxx",
3736
name: str,
3837
channum: int,
3938
**kwargs: "Unpack[InstrumentBaseKWArgs]",
@@ -317,15 +316,15 @@ def val_parser(parser: type, inputstring: str) -> float | int:
317316
OutputChannel = Keysight33xxxOutputChannel
318317

319318

320-
class Keysight33xxxSyncChannel(InstrumentChannel):
319+
class Keysight33xxxSyncChannel(InstrumentChannel["Keysight33xxx"]):
321320
"""
322321
Class to hold the sync output of a Keysight 33xxxx waveform generator.
323322
Has very few parameters for single channel instruments.
324323
"""
325324

326325
def __init__(
327326
self,
328-
parent: Instrument,
327+
parent: "Keysight33xxx",
329328
name: str,
330329
**kwargs: "Unpack[InstrumentBaseKWArgs]",
331330
):

src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ def __init__(
9898
**kwargs: Unpack[InstrumentBaseKWArgs],
9999
):
100100
super().__init__(parent, name, *arg, **kwargs)
101-
self.root_instrument: KeysightN9030B
102101

103102
self._additional_wait = additional_wait
104103
self._min_freq = -8e7
@@ -111,7 +110,7 @@ def __init__(
111110
}
112111
opt: str | None = None
113112
for hw_opt_for_max_freq in self._valid_max_freq:
114-
if hw_opt_for_max_freq in self.root_instrument.options():
113+
if hw_opt_for_max_freq in self.parent.options():
115114
opt = hw_opt_for_max_freq
116115
assert opt is not None
117116
self._max_freq = self._valid_max_freq[opt]
@@ -454,23 +453,22 @@ def _get_data(self, trace_num: int) -> npt.NDArray[np.float64]:
454453
"""
455454
Gets data from the measurement.
456455
"""
457-
root_instr = self.root_instrument
458456
# Check if we should run a new sweep
459-
auto_sweep = root_instr.auto_sweep()
457+
auto_sweep = self.parent.auto_sweep()
460458

461459
if auto_sweep:
462460
# If we need to run a sweep, we need to set the timeout to take into account
463461
# the sweep time
464462
timeout = self.sweep_time() + self._additional_wait
465-
with root_instr.timeout.set_to(timeout):
466-
data = root_instr.visa_handle.query_binary_values(
467-
f":READ:{root_instr.measurement()}{trace_num}?",
463+
with self.parent.timeout.set_to(timeout):
464+
data = self.parent.visa_handle.query_binary_values(
465+
f":READ:{self.parent.measurement()}{trace_num}?",
468466
datatype="d",
469467
is_big_endian=False,
470468
)
471469
else:
472-
data = root_instr.visa_handle.query_binary_values(
473-
f":FETC:{root_instr.measurement()}{trace_num}?",
470+
data = self.parent.visa_handle.query_binary_values(
471+
f":FETC:{self.parent.measurement()}{trace_num}?",
474472
datatype="d",
475473
is_big_endian=False,
476474
)
@@ -491,9 +489,9 @@ def setup_swept_sa_sweep(self, start: float, stop: float, npts: int) -> None:
491489
"""
492490
Sets up the Swept SA measurement sweep for Spectrum Analyzer Mode.
493491
"""
494-
self.root_instrument.mode("SA")
495-
if "SAN" in self.root_instrument.available_meas():
496-
self.root_instrument.measurement("SAN")
492+
self.parent.mode("SA")
493+
if "SAN" in self.parent.available_meas():
494+
self.parent.measurement("SAN")
497495
else:
498496
raise RuntimeError(
499497
"Swept SA measurement is not available on your "
@@ -537,7 +535,7 @@ def __init__(
537535
}
538536
opt: str | None = None
539537
for hw_opt_for_max_freq in self._valid_max_freq:
540-
if hw_opt_for_max_freq in self.root_instrument.options():
538+
if hw_opt_for_max_freq in self.parent.options():
541539
opt = hw_opt_for_max_freq
542540
assert opt is not None
543541
self._max_freq = self._valid_max_freq[opt]
@@ -668,9 +666,8 @@ def _get_data(self, trace_num: int) -> ParamRawDataType:
668666
"""
669667
Gets data from the measurement.
670668
"""
671-
root_instr = self.root_instrument
672-
measurement = root_instr.measurement()
673-
raw_data = root_instr.visa_handle.query_binary_values(
669+
measurement = self.parent.measurement()
670+
raw_data = self.parent.visa_handle.query_binary_values(
674671
f":READ:{measurement}1?",
675672
datatype="d",
676673
is_big_endian=False,
@@ -684,26 +681,26 @@ def _get_data(self, trace_num: int) -> ParamRawDataType:
684681
return -1 * np.ones(self.npts())
685682

686683
try:
687-
data = root_instr.visa_handle.query_binary_values(
684+
data = self.parent.visa_handle.query_binary_values(
688685
f":READ:{measurement}{trace_num}?",
689686
datatype="d",
690687
is_big_endian=False,
691688
)
692-
data = np.array(data).reshape((-1, 2))
689+
data_array = np.array(data).reshape((-1, 2))
693690
except TimeoutError as e:
694691
raise TimeoutError("Couldn't receive any data. Command timed out.") from e
695692

696-
return data[:, 1]
693+
return data_array[:, 1]
697694

698695
def setup_log_plot_sweep(
699696
self, start_offset: float, stop_offset: float, npts: int
700697
) -> None:
701698
"""
702699
Sets up the Log Plot measurement sweep for Phase Noise Mode.
703700
"""
704-
self.root_instrument.mode("PNOISE")
705-
if "LPL" in self.root_instrument.available_meas():
706-
self.root_instrument.measurement("LPL")
701+
self.parent.mode("PNOISE")
702+
if "LPL" in self.parent.available_meas():
703+
self.parent.measurement("LPL")
707704
else:
708705
raise RuntimeError(
709706
"Log Plot measurement is not available on your "

src/qcodes/instrument_drivers/Keysight/N52xx.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from qcodes.instrument import (
1010
ChannelList,
11+
ChannelTuple,
1112
InstrumentBaseKWArgs,
1213
InstrumentChannel,
1314
VisaInstrument,
@@ -69,7 +70,7 @@ def get_raw(self) -> npt.NDArray:
6970
return np.linspace(0, self._stopparam(), self._pointsparam())
7071

7172

72-
class FormattedSweep(ParameterWithSetpoints):
73+
class FormattedSweep(ParameterWithSetpoints[npt.NDArray, "KeysightPNATrace"]):
7374
"""
7475
Mag will run a sweep, including averaging, before returning data.
7576
As such, wait time in a loop is not needed.
@@ -96,7 +97,7 @@ def setpoints(self) -> "Sequence[ParameterBase]":
9697
"""
9798
if self.instrument is None:
9899
raise RuntimeError("Cannot return setpoints if not attached to instrument")
99-
root_instrument: KeysightPNABase = self.root_instrument # type: ignore[assignment]
100+
root_instrument: KeysightPNABase = self.root_instrument
100101
sweep_type = root_instrument.sweep_type()
101102
if sweep_type == "LIN":
102103
return (root_instrument.frequency_axis,)
@@ -115,10 +116,16 @@ def setpoints(self, setpoints: Any) -> None:
115116
"""
116117
return
117118

119+
@property
120+
def root_instrument(self) -> "KeysightPNABase":
121+
root_instrument = super().root_instrument
122+
assert isinstance(root_instrument, KeysightPNABase)
123+
return root_instrument
124+
118125
def get_raw(self) -> npt.NDArray:
119126
if self.instrument is None:
120127
raise RuntimeError("Cannot get data without instrument")
121-
root_instr = self.instrument.root_instrument
128+
root_instr = self.root_instrument
122129
# Check if we should run a new sweep
123130
auto_sweep = root_instr.auto_sweep()
124131

@@ -130,12 +137,12 @@ def get_raw(self) -> npt.NDArray:
130137
data = root_instr.visa_handle.query_binary_values(
131138
"CALC:DATA? FDATA", datatype="f", is_big_endian=True
132139
)
133-
data = np.array(data)
140+
data_array = np.array(data)
134141
# Restore previous state if it was changed
135142
if auto_sweep:
136143
root_instr.sweep_mode(prev_mode)
137144

138-
return data
145+
return data_array
139146

140147

141148
class KeysightPNAPort(InstrumentChannel):
@@ -182,7 +189,7 @@ def _set_power_limits(self, min_power: float, max_power: float) -> None:
182189
"Alis for backwards compatibility"
183190

184191

185-
class KeysightPNATrace(InstrumentChannel):
192+
class KeysightPNATrace(InstrumentChannel["KeysightPNABase"]):
186193
"""
187194
Allow operations on individual PNA traces.
188195
"""
@@ -292,6 +299,12 @@ def __init__(
292299
)
293300
"""Parameter polar"""
294301

302+
@property
303+
def root_instrument(self) -> "KeysightPNABase":
304+
root_instrument = super().root_instrument
305+
assert isinstance(root_instrument, KeysightPNABase)
306+
return root_instrument
307+
295308
def disable(self) -> None:
296309
"""
297310
Disable this trace on the PNA
@@ -438,7 +451,11 @@ def __init__(
438451
)
439452
ports.append(port)
440453
self.add_submodule(f"port{port_num}", port)
441-
self.add_submodule("ports", ports.to_channel_tuple())
454+
455+
self.ports: ChannelTuple[KeysightPNAPort] = self.add_submodule(
456+
"ports", ports.to_channel_tuple()
457+
)
458+
"""Tuple of KeysightPNAPort submodules"""
442459

443460
# RF output
444461
self.output: Parameter = self.add_parameter(

src/qcodes/instrument_drivers/Keysight/keysight_34934a.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
from qcodes.instrument import (
1515
InstrumentBaseKWArgs,
16-
InstrumentChannel,
17-
VisaInstrument,
1816
)
1917
from qcodes.parameters import Parameter
2018

19+
from .keysight_34980a import Keysight34980A
20+
2121

2222
class Keysight34934A(Keysight34980ASwitchMatrixSubModule):
2323
"""
@@ -32,7 +32,7 @@ class Keysight34934A(Keysight34980ASwitchMatrixSubModule):
3232

3333
def __init__(
3434
self,
35-
parent: "VisaInstrument | InstrumentChannel",
35+
parent: "Keysight34980A",
3636
name: str,
3737
slot: int,
3838
**kwargs: "Unpack[InstrumentBaseKWArgs]",

0 commit comments

Comments
 (0)