diff --git a/docs/changes/newsfragments/8695.breaking b/docs/changes/newsfragments/8695.breaking new file mode 100644 index 000000000000..e2ff1f7248be --- /dev/null +++ b/docs/changes/newsfragments/8695.breaking @@ -0,0 +1,6 @@ +The ``open`` and ``short`` parameters of ``KeysightE4980ACorrection`` have been replaced by +methods of the same name. Both were command-only parameters, so calling them without an +argument, e.g. ``meter.correction.open()`` as in the example notebook, only returned the cached +value and did not execute the OPEN or SHORT correction. Calling them like this now sends the +command. Code that passed a value to them, e.g. ``meter.correction.open(None)``, must drop the +argument, and they are no longer part of the instrument's parameters or snapshot. diff --git a/docs/changes/newsfragments/8695.improved_driver b/docs/changes/newsfragments/8695.improved_driver new file mode 100644 index 000000000000..ef5430be69b7 --- /dev/null +++ b/docs/changes/newsfragments/8695.improved_driver @@ -0,0 +1,15 @@ +The enum parameters of the ``KeysightB1517A`` and ``KeysightB1520A`` modules of the Keysight +B1500, such as ``sweep_mode``, ``measurement_mode`` and ``impedance_model``, are now annotated +with their enum type, e.g. ``Parameter[constants.SweepMode, Self]``, and +``KeysightB1517A.time_axis`` as a numpy array. The parameters of the Keysight B220x and the +``phase`` of the ``AgilentE8257D`` and ``AgilentE8267C`` (``float``, in degrees) are annotated +as well. The ``post_sweep_voltage_val`` argument of ``KeysightB1517A.setup_staircase_sweep`` is +now typed as ``constants.WM.Post``, the enum used by the B1517A sweep, instead of +``constants.WMDCV.Post``, which belongs to the B1520A CV sweep. Both have the same values, so +existing calls keep working. + +Parameters of the Keysight 344xxA DMMs and 33XXX waveform generators that can also be set to +SCPI keywords such as ``"MIN"``, ``"MAX"``, ``"DEF"`` or ``"INF"``, e.g. +``Keysight344xxASample.timer``, are annotated as ``Parameter[float | str, Self]`` (or +``int | str`` for counts). Getting them always returns a number, but the cached value is the +string that was set until the parameter is read again, as now explained in their docstrings. diff --git a/docs/changes/newsfragments/8695.improved_driver.1 b/docs/changes/newsfragments/8695.improved_driver.1 new file mode 100644 index 000000000000..fb0b111897c0 --- /dev/null +++ b/docs/changes/newsfragments/8695.improved_driver.1 @@ -0,0 +1,2 @@ +Setting ``resolution`` of the Keysight 344xxA DMMs to ``"MIN"``, ``"MAX"`` or ``"DEF"`` raised a +``ValueError`` instead of sending the setting to the instrument. diff --git a/src/qcodes/instrument/sims/Agilent_E8257D.yaml b/src/qcodes/instrument/sims/Agilent_E8257D.yaml index af4816d6bbbc..c7e645089acd 100644 --- a/src/qcodes/instrument/sims/Agilent_E8257D.yaml +++ b/src/qcodes/instrument/sims/Agilent_E8257D.yaml @@ -25,6 +25,16 @@ devices: setter: q: "OUTP {}" + phase: + # *RST value according to the E8257D/67D SCPI command reference; + # the query returns radians + default: "+0.00000000E+000" + getter: + q: "PHASE?" + r: "{}" + setter: + q: "PHASE {}" + resources: GPIB::1::INSTR: device: device 1 diff --git a/src/qcodes/instrument/sims/Agilent_E8267C.yaml b/src/qcodes/instrument/sims/Agilent_E8267C.yaml index c6f82f5887d6..d00b23abca21 100644 --- a/src/qcodes/instrument/sims/Agilent_E8267C.yaml +++ b/src/qcodes/instrument/sims/Agilent_E8267C.yaml @@ -36,6 +36,16 @@ devices: setter: q: "PULM:INT:PWID {}" + phase: + # *RST value according to the E8257D/67D SCPI command reference; + # the query returns radians + default: "+0.00000000E+000" + getter: + q: "PHAS?" + r: "{}" + setter: + q: "PHAS {}" + resources: GPIB::1::INSTR: device: device 1 diff --git a/src/qcodes/instrument/sims/Keysight_34465A.yaml b/src/qcodes/instrument/sims/Keysight_34465A.yaml index 8caf02abe68d..75a3493eb287 100644 --- a/src/qcodes/instrument/sims/Keysight_34465A.yaml +++ b/src/qcodes/instrument/sims/Keysight_34465A.yaml @@ -127,7 +127,8 @@ devices: q: "SENSe:VOLTage:DC:RESolution?" r: "{}" setter: - q: "SENSe:VOLTage:DC:RESolution {}" + # the driver sets the resolution with the short form + q: "VOLT:DC:RES {}" current_ac_autorange: default: 0 getter: diff --git a/src/qcodes/instrument/sims/Keysight_E4980A.yaml b/src/qcodes/instrument/sims/Keysight_E4980A.yaml index b218c9e7a343..64e02e9133ed 100644 --- a/src/qcodes/instrument/sims/Keysight_E4980A.yaml +++ b/src/qcodes/instrument/sims/Keysight_E4980A.yaml @@ -49,6 +49,15 @@ devices: setter: q: ":FUNCtion:IMPedance:RANGe:AUTO {}" + # command-only, no reply + correction_open: + setter: + q: ":CORRection:OPEN" + + correction_short: + setter: + q: ":CORRection:SHORt" + resources: GPIB::1::INSTR: device: device 1 diff --git a/src/qcodes/instrument_drivers/Keysight/KeysightAgilent_33XXX.py b/src/qcodes/instrument_drivers/Keysight/KeysightAgilent_33XXX.py index 60ff7edc82d2..b9a0c4b84b8e 100644 --- a/src/qcodes/instrument_drivers/Keysight/KeysightAgilent_33XXX.py +++ b/src/qcodes/instrument_drivers/Keysight/KeysightAgilent_33XXX.py @@ -180,7 +180,7 @@ def val_parser(parser: type, inputstring: str) -> float | int: ) """Parameter pulse_width""" - self.edges: Parameter = self.add_parameter( + self.edges: Parameter[float | str, Self] = self.add_parameter( "edges", label=f"Channel {channum} edges width", set_cmd=f"SOURce{channum}:FUNCtion:PULSe:TRANsition {{}}", @@ -188,8 +188,22 @@ def val_parser(parser: type, inputstring: str) -> float | int: get_parser=float, unit="s", vals=vals.MultiType(vals.Numbers(), vals.Enum("MIN", "MAX")), + docstring=( + "Sets the transition times for the leading and trailing edges of " + "the pulse. Besides a number, this can be set to 'MIN' or 'MAX', " + "which the instrument converts to the corresponding number. " + "Getting the parameter always returns a number, but until it is " + "read again, the cached value is the string that was set. The " + "value is therefore annotated as ``float | str``." + ), ) - """Sets the transition times for the leading and trailing edges of the pulse.""" + """ + Sets the transition times for the leading and trailing edges of the pulse. + Besides a number, this can be set to "MIN" or "MAX", which the instrument + converts to the corresponding number. Getting the parameter always returns + a number, but until it is read again, the cached value is the string that + was set. The value is therefore annotated as ``float | str``. + """ # TRIGGER MENU self.trigger_source: Parameter[str, Self] = self.add_parameter( @@ -214,15 +228,24 @@ def val_parser(parser: type, inputstring: str) -> float | int: # Older models do not have all the fancy trigger options if self._parent.model[2] in ["5", "6"]: - self.trigger_count: Parameter = self.add_parameter( + self.trigger_count: Parameter[float, Self] = self.add_parameter( "trigger_count", label=f"Channel {channum} trigger count", set_cmd=f"TRIGger{channum}:COUNt {{}}", get_cmd=f"TRIGger{channum}:COUNt?", vals=vals.Ints(1, 1000000), get_parser=partial(val_parser, int), + docstring=( + "Number of triggers. Reads back as ``float('inf')`` if the " + "instrument reports an infinite count (9.9e37), so the value " + "is annotated as ``float``." + ), ) - """Parameter trigger_count""" + """ + Number of triggers. Reads back as ``float("inf")`` if the instrument + reports an infinite count (9.9e37), so the value is annotated as + ``float``. + """ self.trigger_delay: Parameter[float, Self] = self.add_parameter( "trigger_delay", @@ -279,15 +302,29 @@ def val_parser(parser: type, inputstring: str) -> float | int: ) """Parameter burst_mode""" - self.burst_ncycles: Parameter = self.add_parameter( + self.burst_ncycles: Parameter[float | str, Self] = self.add_parameter( "burst_ncycles", label=f"Channel {channum} burst no. of cycles", set_cmd=f"SOURce{channum}:BURSt:NCYCles {{}}", get_cmd=f"SOURce{channum}:BURSt:NCYCLes?", get_parser=partial(val_parser, int), vals=vals.MultiType(vals.Ints(1), vals.Enum("MIN", "MAX", "INF")), + docstring=( + "Number of cycles in a burst. Besides a number, this can be set " + "to 'MIN', 'MAX' or 'INF', which the instrument converts to the " + "corresponding number. Getting the parameter always returns a " + "number, ``float('inf')`` for an infinite count, but until it is " + "read again, the cached value is the string that was set. The " + "value is therefore annotated as ``float | str``." + ), ) - """Parameter burst_ncycles""" + """ + Number of cycles in a burst. Besides a number, this can be set to "MIN", + "MAX" or "INF", which the instrument converts to the corresponding number. + Getting the parameter always returns a number, ``float("inf")`` for an + infinite count, but until it is read again, the cached value is the string + that was set. The value is therefore annotated as ``float | str``. + """ self.burst_phase: Parameter[float, Self] = self.add_parameter( "burst_phase", @@ -325,7 +362,7 @@ def val_parser(parser: type, inputstring: str) -> float | int: ) """The burst period is the time between the starts of consecutive bursts when trigger is immediate.""" - self.output_load: Parameter = self.add_parameter( + self.output_load: Parameter[float | str, Self] = self.add_parameter( "output_load", label=f"Channel {channum} output load", set_cmd=f"OUTPut{channum}:LOAD {{}}", @@ -333,8 +370,26 @@ def val_parser(parser: type, inputstring: str) -> float | int: get_parser=partial(val_parser, float), unit="ohms", vals=vals.MultiType(vals.Numbers(1, 10000), vals.Enum("INF", "MIN", "MAX")), + docstring=( + "Sets expected output termination. Should equal the load " + "impedance attached to the output. Besides a number, this can be " + "set to 'INF' (high impedance), 'MIN' or 'MAX', which the " + "instrument converts to the corresponding number. Getting the " + "parameter always returns a number, ``float('inf')`` for high " + "impedance, but until it is read again, the cached value is the " + "string that was set. The value is therefore annotated as " + "``float | str``." + ), ) - """Sets expected output termination. Should equal the load impedance attached to the output.""" + """ + Sets expected output termination. Should equal the load impedance attached + to the output. Besides a number, this can be set to "INF" (high + impedance), "MIN" or "MAX", which the instrument converts to the + corresponding number. Getting the parameter always returns a number, + ``float("inf")`` for high impedance, but until it is read again, the + cached value is the string that was set. The value is therefore annotated + as ``float | str``. + """ self.auto_range: Parameter[str, Self] = self.add_parameter( "auto_range", @@ -352,7 +407,7 @@ def val_parser(parser: type, inputstring: str) -> float | int: "6", ]: # Older models do not support all arbitrary options max_srate = self._parent._max_srate[self.model] - self.srate: Parameter = self.add_parameter( + self.srate: Parameter[float | str, Self] = self.add_parameter( "srate", label=f"Channel {channum} sample rate", set_cmd=f"SOURce{channum}:FUNCtion:ARBitrary:SRATe {{}}", @@ -362,8 +417,22 @@ def val_parser(parser: type, inputstring: str) -> float | int: vals=vals.MultiType( vals.Numbers(1e-6, max_srate), vals.Enum("MIN", "MAX", "DEF") ), + docstring=( + "Sets the sample rate for the arbitrary waveform. Besides a " + "number, this can be set to 'MIN', 'MAX' or 'DEF', which the " + "instrument converts to the corresponding number. Getting the " + "parameter always returns a number, but until it is read " + "again, the cached value is the string that was set. The " + "value is therefore annotated as ``float | str``." + ), ) - """Sets the sample rate for the arbitrary waveform.""" + """ + Sets the sample rate for the arbitrary waveform. Besides a number, this + can be set to "MIN", "MAX" or "DEF", which the instrument converts to + the corresponding number. Getting the parameter always returns a + number, but until it is read again, the cached value is the string + that was set. The value is therefore annotated as ``float | str``. + """ self.add_function( "load_arb", diff --git a/src/qcodes/instrument_drivers/Keysight/keysight_b220x.py b/src/qcodes/instrument_drivers/Keysight/keysight_b220x.py index a235bafd5c9c..e1fc69375e88 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysight_b220x.py +++ b/src/qcodes/instrument_drivers/Keysight/keysight_b220x.py @@ -147,7 +147,7 @@ def __init__( 'mbb' (make before break) """ - self.bias_input_port: Parameter = self.add_parameter( + self.bias_input_port: Parameter[int, Self] = self.add_parameter( name="bias_input_port", get_cmd=f":BIAS:PORT? {self._card}", set_cmd=f":BIAS:PORT {self._card},{{}}", @@ -175,7 +175,7 @@ def __init__( ) """Param: True for ON, False for OFF""" - self.gnd_input_port: Parameter = self.add_parameter( + self.gnd_input_port: Parameter[int, Self] = self.add_parameter( name="gnd_input_port", get_cmd=f":AGND:PORT? {self._card}", set_cmd=f":AGND:PORT {self._card},{{}}", @@ -201,7 +201,7 @@ def __init__( ) """Parameter gnd_mode""" - self.unused_inputs: Parameter = self.add_parameter( + self.unused_inputs: Parameter[list[int], Self] = self.add_parameter( name="unused_inputs", get_cmd=f":AGND:UNUSED? {self._card}", set_cmd=f":AGND:UNUSED {self._card},'{{}}'", @@ -213,7 +213,7 @@ def __init__( ) """Parameter unused_inputs""" - self.couple_ports: Parameter = self.add_parameter( + self.couple_ports: Parameter[list[int], Self] = self.add_parameter( name="couple_ports", get_cmd=f":COUP:PORT? {self._card}", set_cmd=f":COUP:PORT {self._card},'{{}}'", diff --git a/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py b/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py index 19793ca568a6..8c6eb8cc2b16 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py +++ b/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py @@ -153,13 +153,6 @@ def __init__( ) -> None: super().__init__(parent, name, **kwargs) - self.open: Parameter = self.add_parameter( - "open", - set_cmd=":CORRection:OPEN", - docstring="Executes OPEN correction based on all frequency points.", - ) - """Executes OPEN correction based on all frequency points.""" - self.open_state: Parameter[bool, Self] = self.add_parameter( "open_state", get_cmd=":CORRection:OPEN:STATe?", @@ -169,13 +162,6 @@ def __init__( ) """Enables or disable OPEN correction""" - self.short: Parameter = self.add_parameter( - "short", - set_cmd=":CORRection:SHORt", - docstring="Executes SHORT correction based on all frequency points.", - ) - """Executes SHORT correction based on all frequency points.""" - self.short_state: Parameter[bool, Self] = self.add_parameter( "short_state", get_cmd=":CORRection:SHORt:STATe?", @@ -185,6 +171,18 @@ def __init__( ) """Enables or disable SHORT correction.""" + def open(self) -> None: + """ + Executes OPEN correction based on all frequency points. + """ + self.write(":CORRection:OPEN") + + def short(self) -> None: + """ + Executes SHORT correction based on all frequency points. + """ + self.write(":CORRection:SHORt") + class KeysightE4980A(VisaInstrument): """ diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py index 22d414b525ff..f02fdb2e0602 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py @@ -79,7 +79,7 @@ def __init__( "power_compliance": None, } - self.sweep_auto_abort: Parameter = self.add_parameter( + self.sweep_auto_abort: Parameter[constants.Abort, Self] = self.add_parameter( name="sweep_auto_abort", set_cmd=self._set_sweep_auto_abort, get_cmd=self._get_sweep_auto_abort, @@ -127,20 +127,22 @@ def __init__( sweep source forces the pulse base value after sweep. """ - self.post_sweep_voltage_condition: Parameter = self.add_parameter( - name="post_sweep_voltage_condition", - set_cmd=self._set_post_sweep_voltage_condition, - get_cmd=self._get_post_sweep_voltage_condition, - set_parser=constants.WM.Post, - get_parser=constants.WM.Post, - vals=vals.Enum(*list(constants.WM.Post)), - initial_cache_value=constants.WM.Post.START, - docstring=textwrap.dedent( - """ + self.post_sweep_voltage_condition: Parameter[constants.WM.Post, Self] = ( + self.add_parameter( + name="post_sweep_voltage_condition", + set_cmd=self._set_post_sweep_voltage_condition, + get_cmd=self._get_post_sweep_voltage_condition, + set_parser=constants.WM.Post, + get_parser=constants.WM.Post, + vals=vals.Enum(*list(constants.WM.Post)), + initial_cache_value=constants.WM.Post.START, + docstring=textwrap.dedent( + """ Source output value after the measurement is normally completed. If this parameter is not set, the sweep sources force the start value. """ - ), + ), + ) ) """ Source output value after the measurement is normally completed. If @@ -294,7 +296,7 @@ def __init__( get_parser=self._get_sweep_delays_parser, ) - self.sweep_mode: Parameter = self.add_parameter( + self.sweep_mode: Parameter[constants.SweepMode, Self] = self.add_parameter( name="sweep_mode", set_cmd=self._set_sweep_mode, get_cmd=self._get_sweep_mode, @@ -323,7 +325,7 @@ def __init__( 4: Log sweep (double stair, start to stop to start.) """ - self.sweep_range: Parameter = self.add_parameter( + self.sweep_range: Parameter[constants.VOutputRange, Self] = self.add_parameter( name="sweep_range", set_cmd=self._set_sweep_range, get_cmd=self._get_sweep_range, @@ -955,7 +957,7 @@ def __init__( IOutputRange.MIN_100mA, ] - self.measurement_mode: Parameter = self.add_parameter( + self.measurement_mode: Parameter[MM.Mode, Self] = self.add_parameter( name="measurement_mode", get_cmd=None, set_cmd=self._set_measurement_mode, @@ -1022,7 +1024,7 @@ def __init__( ) """Parameter current""" - self.time_axis: Parameter = self.add_parameter( + self.time_axis: Parameter[npt.NDArray, Self] = self.add_parameter( name="time_axis", get_cmd=self._get_time_axis, vals=vals.Arrays(shape=(self._get_number_of_samples,)), @@ -1358,7 +1360,7 @@ def setup_staircase_sweep( v_start: float, v_end: float, n_steps: int, - post_sweep_voltage_val: constants.WMDCV.Post | int = constants.WMDCV.Post.STOP, + post_sweep_voltage_val: constants.WM.Post | int = constants.WM.Post.STOP, av_coef: int = -1, enable_filter: bool = True, v_src_range: constants.OutputRange = constants.VOutputRange.AUTO, @@ -1420,10 +1422,12 @@ def setup_staircase_sweep( self.iv_sweep.delay(delay) self.iv_sweep.step_delay(step_delay) self.iv_sweep.measure_delay(measure_delay) - self.iv_sweep.sweep_auto_abort(abort_enabled) - self.iv_sweep.post_sweep_voltage_condition(post_sweep_voltage_val) - self.iv_sweep.sweep_mode(sweep_mode) - self.iv_sweep.sweep_range(v_src_range) + self.iv_sweep.sweep_auto_abort(constants.Abort(abort_enabled)) + self.iv_sweep.post_sweep_voltage_condition( + constants.WM.Post(post_sweep_voltage_val) + ) + self.iv_sweep.sweep_mode(constants.SweepMode(sweep_mode)) + self.iv_sweep.sweep_range(constants.VOutputRange(v_src_range)) self.iv_sweep.sweep_start(v_start) self.iv_sweep.sweep_end(v_end) self.iv_sweep.sweep_steps(n_steps) diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py index 073dc5b3afdf..5207962eaebc 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py @@ -53,7 +53,7 @@ def __init__( ): super().__init__(parent, name, **kwargs) - self.sweep_auto_abort: Parameter = self.add_parameter( + self.sweep_auto_abort: Parameter[constants.Abort, Self] = self.add_parameter( name="sweep_auto_abort", set_cmd=self._set_sweep_auto_abort, get_cmd=self._get_sweep_auto_abort, @@ -86,16 +86,17 @@ def __init__( - Overflow on the AD converter """ - self.post_sweep_voltage_condition: Parameter = self.add_parameter( - name="post_sweep_voltage_condition", - set_cmd=self._set_post_sweep_voltage_condition, - get_cmd=self._get_post_sweep_voltage_condition, - set_parser=constants.WMDCV.Post, - get_parser=constants.WMDCV.Post, - vals=vals.Enum(*list(constants.WMDCV.Post)), - initial_cache_value=constants.WMDCV.Post.START, - docstring=textwrap.dedent( - """ + self.post_sweep_voltage_condition: Parameter[constants.WMDCV.Post, Self] = ( + self.add_parameter( + name="post_sweep_voltage_condition", + set_cmd=self._set_post_sweep_voltage_condition, + get_cmd=self._get_post_sweep_voltage_condition, + set_parser=constants.WMDCV.Post, + get_parser=constants.WMDCV.Post, + vals=vals.Enum(*list(constants.WMDCV.Post)), + initial_cache_value=constants.WMDCV.Post.START, + docstring=textwrap.dedent( + """ This command also sets the post measurement condition of the MFCMU. After the measurement is normally completed, the DC bias sweep source @@ -107,7 +108,8 @@ def __init__( the start value, and the pulsed bias sweep source forces the pulse base value after sweep. """ - ), + ), + ) ) """ This command also sets the post measurement @@ -524,13 +526,14 @@ def __init__( ) """Instrument module correction""" - self.phase_compensation_mode: Parameter = self.add_parameter( - name="phase_compensation_mode", - set_cmd=self._set_phase_compensation_mode, - get_cmd=None, - set_parser=constants.ADJ.Mode, - docstring=textwrap.dedent( - """ + self.phase_compensation_mode: Parameter[constants.ADJ.Mode, Self] = ( + self.add_parameter( + name="phase_compensation_mode", + set_cmd=self._set_phase_compensation_mode, + get_cmd=None, + set_parser=constants.ADJ.Mode, + docstring=textwrap.dedent( + """ This parameter selects the MFCMU phase compensation mode. This command initializes the MFCMU. The available modes are captured in :class:`constants.ADJ.Mode`: @@ -546,7 +549,8 @@ def __init__( For mode=2, the KeysightB1500 performs the phase compensation before every measurement. It is useful when there are wide load fluctuations by changing the bias and so on.""" - ), + ), + ) ) """ This parameter selects the MFCMU phase compensation mode. This @@ -642,7 +646,7 @@ def __init__( get_parser=self._get_adc_mode_parser, ) - self.ranging_mode: Parameter = self.add_parameter( + self.ranging_mode: Parameter[constants.RangingMode, Self] = self.add_parameter( name="ranging_mode", set_cmd=self._set_ranging_mode, vals=vals.Enum(*list(constants.RangingMode)), @@ -691,7 +695,7 @@ def __init__( the FC command. """ - self.measurement_mode: Parameter = self.add_parameter( + self.measurement_mode: Parameter[MM.Mode, Self] = self.add_parameter( name="measurement_mode", get_cmd=None, set_cmd=self._set_measurement_mode, @@ -719,19 +723,21 @@ def __init__( guide for more information. """ - self.impedance_model: Parameter = self.add_parameter( - name="impedance_model", - set_cmd=self._set_impedance_model, - get_cmd=None, - vals=vals.Enum(*list(constants.IMP.MeasurementMode)), - set_parser=constants.IMP.MeasurementMode, - initial_value=constants.IMP.MeasurementMode.Cp_D, - docstring=textwrap.dedent( - """ + self.impedance_model: Parameter[constants.IMP.MeasurementMode, Self] = ( + self.add_parameter( + name="impedance_model", + set_cmd=self._set_impedance_model, + get_cmd=None, + vals=vals.Enum(*list(constants.IMP.MeasurementMode)), + set_parser=constants.IMP.MeasurementMode, + initial_value=constants.IMP.MeasurementMode.Cp_D, + docstring=textwrap.dedent( + """ The IMP command specifies the parameter measured by the MFCMU. Look at the ``constants.IMP.MeasurementMode`` for all the modes. """ - ), + ), + ) ) """ The IMP command specifies the parameter measured by the MFCMU. @@ -1124,8 +1130,10 @@ def setup_staircase_cv( self.adc_coef(adc_coef) self.frequency(freq) self.voltage_ac(ac_rms) - self.cv_sweep.sweep_auto_abort(abort_enabled) - self.cv_sweep.post_sweep_voltage_condition(post_sweep_voltage_condition) + self.cv_sweep.sweep_auto_abort(constants.Abort(abort_enabled)) + self.cv_sweep.post_sweep_voltage_condition( + constants.WMDCV.Post(post_sweep_voltage_condition) + ) self.cv_sweep.hold_time(hold_delay) self.cv_sweep.delay(delay) self.cv_sweep.step_delay(step_delay) @@ -1136,9 +1144,9 @@ def setup_staircase_cv( self.cv_sweep.sweep_end(v_end) self.cv_sweep.sweep_steps(n_steps) self.measurement_mode(constants.MM.Mode.CV_DC_SWEEP) - self.impedance_model(imp_model) + self.impedance_model(constants.IMP.MeasurementMode(imp_model)) self.ac_dc_volt_monitor(volt_monitor) - self.ranging_mode(ranging_mode) + self.ranging_mode(constants.RangingMode(ranging_mode)) self.measurement_range_for_non_auto(fixed_range_val) self.setup_fnc_already_run = True diff --git a/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py b/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py index 94f7deb654fc..9eae886d94ba 100644 --- a/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py +++ b/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py @@ -2,7 +2,7 @@ from bisect import bisect_left from contextlib import ExitStack from functools import partial -from typing import TYPE_CHECKING, Any, Literal, Self, get_args +from typing import TYPE_CHECKING, Any, Literal, Self import numpy as np import numpy.typing as npt @@ -46,7 +46,7 @@ def __init__( else: _max_trigger_count = 1e6 - self.count: Parameter = self.add_parameter( + self.count: Parameter[float | str, Self] = self.add_parameter( "count", label="Trigger Count", set_cmd="TRIGger:COUNt {}", @@ -67,7 +67,13 @@ def __init__( A variable trigger count is not available from the front panel. However, when you return to remote control of the instrument, - the trigger count returns to the previous value you selected.""" + the trigger count returns to the previous value you selected. + + Besides a number, the count can be set to "MIN", "MAX", "DEF" or + "INF" (infinite, read back as 9.9e37), which the instrument converts + to the corresponding number. Getting the parameter always returns a + number, but until it is read again, the cached value is the string + that was set. The value is therefore annotated as ``float | str``.""" ), ) """ @@ -81,9 +87,15 @@ def __init__( A variable trigger count is not available from the front panel. However, when you return to remote control of the instrument, the trigger count returns to the previous value you selected. + + Besides a number, the count can be set to "MIN", "MAX", "DEF" or + "INF" (infinite, read back as 9.9e37), which the instrument converts + to the corresponding number. Getting the parameter always returns a + number, but until it is read again, the cached value is the string + that was set. The value is therefore annotated as ``float | str``. """ - self.delay: Parameter = self.add_parameter( + self.delay: Parameter[float | str, Self] = self.add_parameter( "delay", label="Trigger Delay", unit="s", @@ -102,7 +114,13 @@ def __init__( measurements, step size depends on AC bandwidth. Selecting a specific trigger delay disables the automatic - trigger delay.""" + trigger delay. + + Besides a number, the delay can be set to "MIN", "MAX" or "DEF", + which the instrument converts to the corresponding number. Getting + the parameter always returns a number, but until it is read again, + the cached value is the string that was set. The value is therefore + annotated as ``float | str``.""" ), ) """ @@ -116,6 +134,12 @@ def __init__( Selecting a specific trigger delay disables the automatic trigger delay. + + Besides a number, the delay can be set to "MIN", "MAX" or "DEF", + which the instrument converts to the corresponding number. Getting + the parameter always returns a number, but until it is read again, + the cached value is the string that was set. The value is therefore + annotated as ``float | str``. """ self.auto_delay_enabled: Parameter[bool, Self] = self.add_parameter( @@ -154,7 +178,7 @@ def __init__( """Parameter slope""" if self.parent.has_DIG or (self.parent.model == "34411A"): - self.level: Parameter = self.add_parameter( + self.level: Parameter[float | str, Self] = self.add_parameter( "level", label="Trigger Level", unit="V", @@ -171,7 +195,14 @@ def __init__( Note that for 100 mV to 100 V ranges and autorange is off, the trigger level can only be set within ±120% of the - range.""" + range. + + Besides a number, the level can be set to "MIN", "MAX" or + "DEF", which the instrument converts to the corresponding + number. Getting the parameter always returns a number, but + until it is read again, the cached value is the string that + was set. The value is therefore annotated as + ``float | str``.""" ), ) """ @@ -181,6 +212,13 @@ def __init__( Note that for 100 mV to 100 V ranges and autorange is off, the trigger level can only be set within ±120% of the range. + + Besides a number, the level can be set to "MIN", "MAX" or + "DEF", which the instrument converts to the corresponding + number. Getting the parameter always returns a number, but + until it is read again, the cached value is the string that + was set. The value is therefore annotated as + ``float | str``. """ _trigger_source_docstring = textwrap.dedent("""\ @@ -245,7 +283,7 @@ def __init__( else: _max_sample_count = int(1e6) - self.count: Parameter = self.add_parameter( + self.count: Parameter[int | str, Self] = self.add_parameter( "count", label="Sample Count", set_cmd="SAMPle:COUNt {}", @@ -265,7 +303,13 @@ def __init__( MEM option). For the model 34410A the maximum is 50,000 readings, and for the model 34411A the maximum is 1,000,000 readings. The latter does - not depend on the pretrigger count.""" + not depend on the pretrigger count. + + Besides a number, the count can be set to "MIN", "MAX" or "DEF", + which the instrument converts to the corresponding number. Getting + the parameter always returns a number, but until it is read again, + the cached value is the string that was set. The value is therefore + annotated as ``int | str``.""" ), ) """Specifies the number of measurements (samples) the instrument @@ -277,7 +321,13 @@ def __init__( MEM option). For the model 34410A the maximum is 50,000 readings, and for the model 34411A the maximum is 1,000,000 readings. The latter does -not depend on the pretrigger count.""" +not depend on the pretrigger count. + +Besides a number, the count can be set to "MIN", "MAX" or "DEF", +which the instrument converts to the corresponding number. Getting +the parameter always returns a number, but until it is read again, +the cached value is the string that was set. The value is therefore +annotated as ``int | str``.""" if self.parent.has_DIG or (self.parent.model == "34411A"): if self.parent.model == "34411A": @@ -287,7 +337,7 @@ def __init__( else: _max_pretrig_count = int(5e4) - 1 - self.pretrigger_count: Parameter = self.add_parameter( + self.pretrigger_count: Parameter[int | str, Self] = self.add_parameter( "pretrigger_count", label="Sample Pretrigger Count", set_cmd="SAMPle:COUNt:PRETrigger {}", @@ -306,7 +356,13 @@ def __init__( by the current number of sample counts as specified via the ``sample.count`` parameter. Refer to the doc of the ``sample.count`` parameter for information on the maximum - number of sample counts.""" + number of sample counts. + + Besides a number, the count can be set to "MIN", "MAX" or + "DEF", which the instrument converts to the corresponding + number. Getting the parameter always returns a number, but + until it is read again, the cached value is the string that + was set. The value is therefore annotated as ``int | str``.""" ), ) """Allows collection of the data being digitized the trigger. @@ -317,7 +373,13 @@ def __init__( by the current number of sample counts as specified via the ``sample.count`` parameter. Refer to the doc of the ``sample.count`` parameter for information on the maximum -number of sample counts.""" +number of sample counts. + +Besides a number, the count can be set to "MIN", "MAX" or +"DEF", which the instrument converts to the corresponding +number. Getting the parameter always returns a number, but +until it is read again, the cached value is the string that +was set. The value is therefore annotated as ``int | str``.""" if self.parent.is_34465A_34470A or self.parent.is_34410A_34411A: self.source: Parameter[str, Self] = self.add_parameter( @@ -332,7 +394,7 @@ def __init__( ) """Determines sampling time, immediate or using `sample.timer`""" - self.timer: Parameter = self.add_parameter( + self.timer: Parameter[float | str, Self] = self.add_parameter( "timer", label="Sample Timer", set_cmd="SAMPle:TIMer {}", @@ -358,7 +420,12 @@ def __init__( may generate a timing violation error when making measurements. Applying a value less than the absolute minimum will generate an - error.""" + error. + + Getting the parameter always returns a number, but until it is + read again after setting one of the special values, the cached + value is the string that was set. The value is therefore annotated + as ``float | str``.""" ), ) """The value is rounded by the instrument to the nearest step. For DC @@ -377,7 +444,12 @@ def __init__( may generate a timing violation error when making measurements. Applying a value less than the absolute minimum will generate an -error.""" +error. + +Getting the parameter always returns a number, but until it is +read again after setting one of the special values, the cached +value is the string that was set. The value is therefore annotated +as ``float | str``.""" self.timer_minimum: Parameter[float, Self] = self.add_parameter( "timer_minimum", @@ -784,7 +856,7 @@ def __init__( ) """Parameter range""" - self.resolution: Parameter = self.add_parameter( + self.resolution: Parameter[float | str, Self] = self.add_parameter( "resolution", get_cmd="SENSe:VOLTage:DC:RESolution?", get_parser=float, @@ -798,8 +870,8 @@ def __init__( measurements. The resolution is specified in the same units as the selected measurement function, not in number of digits. - You can also specify MIN (best resolution) or MAX (worst - resolution). + You can also specify MIN (best resolution), MAX (worst + resolution) or DEF (default resolution). To achieve normal mode (line frequency noise) rejection, use a resolution that corresponds to an integration time that is @@ -807,15 +879,20 @@ def __init__( Refer to "Resolution Table" or "Range, Resolution and NPLC" sections of the instrument's manual for the available ranges for - the resolution values.""" + the resolution values. + + Getting the parameter always returns a number, but until it is + read again after setting MIN, MAX or DEF, the cached value is the + string that was set. The value is therefore annotated as + ``float | str``.""" ), ) """Selects the measurement resolution for DC voltage and ratio measurements. The resolution is specified in the same units as the selected measurement function, not in number of digits. -You can also specify MIN (best resolution) or MAX (worst -resolution). +You can also specify MIN (best resolution), MAX (worst +resolution) or DEF (default resolution). To achieve normal mode (line frequency noise) rejection, use a resolution that corresponds to an integration time that is @@ -823,7 +900,12 @@ def __init__( Refer to "Resolution Table" or "Range, Resolution and NPLC" sections of the instrument's manual for the available ranges for -the resolution values.""" +the resolution values. + +Getting the parameter always returns a number, but until it is +read again after setting MIN, MAX or DEF, the cached value is the +string that was set. The value is therefore annotated as +``float | str``.""" self.autorange: Parameter[str, Self] = self.add_parameter( "autorange", @@ -941,7 +1023,7 @@ def __init__( mode is disabled (default), the integration time is set in PLC (power-line cycles).""" - self.aperture_time: Parameter = self.add_parameter( + self.aperture_time: Parameter[float | str, Self] = self.add_parameter( "aperture_time", label="Aperture time", set_cmd=self._set_apt_time, @@ -960,7 +1042,14 @@ def __init__( rejection characteristics (NPLC > 1). Setting the aperture time automatically enables the aperture - mode.""" + mode. + + Besides a number, the aperture time can be set to "MIN", + "MAX" or "DEF", which the instrument converts to the + corresponding number. Getting the parameter always returns a + number, but until it is read again, the cached value is the + string that was set. The value is therefore annotated as + ``float | str``.""" ), ) """Specifies the integration time in seconds (called aperture @@ -971,7 +1060,14 @@ def __init__( rejection characteristics (NPLC > 1). Setting the aperture time automatically enables the aperture -mode.""" +mode. + +Besides a number, the aperture time can be set to "MIN", +"MAX" or "DEF", which the instrument converts to the +corresponding number. Getting the parameter always returns a +number, but until it is read again, the cached value is the +string that was set. The value is therefore annotated as +``float | str``.""" #################################### # Submodules @@ -1273,8 +1369,11 @@ def _set_resolution(self, value: float | NumericScpiMnemonic) -> None: # convert both value*range and the resolution factors # to strings with few digits, so we avoid floating point # rounding errors. - if value in get_args(NumericScpiMnemonic): - str_value = str(value) # already str, but mypy does not know this + str_value: str + if isinstance(value, str): + # one of the NumericScpiMnemonic strings, as enforced by the + # validator + str_value = value else: res_fac_strs = [f"{(v * rang):.1e}" for v in self._resolution_factors] str_value = f"{value:.1e}" diff --git a/src/qcodes/instrument_drivers/agilent/Agilent_E8257D.py b/src/qcodes/instrument_drivers/agilent/Agilent_E8257D.py index 2dfbdf89f12c..87abb7a0451e 100644 --- a/src/qcodes/instrument_drivers/agilent/Agilent_E8257D.py +++ b/src/qcodes/instrument_drivers/agilent/Agilent_E8257D.py @@ -104,7 +104,7 @@ def __init__( ) """Parameter frequency""" - self.phase: Parameter = self.add_parameter( + self.phase: Parameter[float, Self] = self.add_parameter( name="phase", label="Phase", unit="deg", diff --git a/src/qcodes/instrument_drivers/agilent/Agilent_E8267C.py b/src/qcodes/instrument_drivers/agilent/Agilent_E8267C.py index f2f9d0a3e237..cbe552f1273a 100644 --- a/src/qcodes/instrument_drivers/agilent/Agilent_E8267C.py +++ b/src/qcodes/instrument_drivers/agilent/Agilent_E8267C.py @@ -64,7 +64,7 @@ def __init__( vals=Numbers(min_value=10e-9, max_value=20e-9), ) """Parameter pulse_width""" - self.phase: Parameter = self.add_parameter( + self.phase: Parameter[float, Self] = self.add_parameter( name="phase", label="Phase", unit="deg", diff --git a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py index d2f36cae9694..95ed7aa66b55 100644 --- a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py +++ b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py @@ -334,7 +334,7 @@ def test_measurement_mode_to_enum_value(smu: KeysightB1517A) -> None: def test_measurement_mode_to_int_value(smu: KeysightB1517A) -> None: mainframe = smu.parent assert isinstance(mainframe, MagicMock) - smu.measurement_mode(10) + smu.measurement_mode(10) # pyright: ignore[reportArgumentType] mainframe.write.assert_called_once_with("MM 10,1") new_mode = smu.measurement_mode() @@ -561,7 +561,7 @@ def test_set_post_sweep_voltage_cond(smu: KeysightB1517A) -> None: mainframe = smu.parent assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = "WM2,2;WT1.0,0.0,0.0,0.0,0.0" - smu.iv_sweep.post_sweep_voltage_condition(constants.WMDCV.Post.STOP) + smu.iv_sweep.post_sweep_voltage_condition(constants.WM.Post.STOP) mainframe.write.assert_called_once_with("WM 2,2") diff --git a/tests/drivers/test_agilent_e8257d.py b/tests/drivers/test_agilent_e8257d.py index 42de831800d6..c4d8c0cb6f63 100644 --- a/tests/drivers/test_agilent_e8257d.py +++ b/tests/drivers/test_agilent_e8257d.py @@ -143,3 +143,19 @@ def test_limits_from_options(e8257d: AgilentE8257D) -> None: e8257d.power.validate(-135) with pytest.raises(ValueError): e8257d.power.validate(11) + + +def test_phase_is_float_in_degrees( + e8257d: AgilentE8257D, mocker: "MockerFixture" +) -> None: + assert e8257d.phase.unit == "deg" + + phase = e8257d.phase() + assert isinstance(phase, float) + assert phase == 0.0 + + # the instrument takes and returns the phase in radians + write_raw = mocker.spy(e8257d, "write_raw") + e8257d.phase(90) + write_raw.assert_called_once_with("PHASE 1.57079633") + assert e8257d.phase() == pytest.approx(90, abs=1e-6) diff --git a/tests/drivers/test_agilent_e8267c.py b/tests/drivers/test_agilent_e8267c.py index e374d5865e3f..efb9aac22858 100644 --- a/tests/drivers/test_agilent_e8267c.py +++ b/tests/drivers/test_agilent_e8267c.py @@ -28,3 +28,18 @@ def test_pulse_width_is_float_in_seconds(e8267c: AgilentE8267C) -> None: e8267c.pulse_width(15e-9) assert e8267c.pulse_width() == pytest.approx(15e-9) + + +def test_phase_is_float_in_degrees(e8267c: AgilentE8267C) -> None: + assert e8267c.phase.unit == "deg" + + phase = e8267c.phase() + assert isinstance(phase, float) + assert phase == 0.0 + + # the instrument takes and returns the phase in radians + e8267c.phase(-90) + assert e8267c.phase() == pytest.approx(-90) + + with pytest.raises(ValueError): + e8267c.phase(180) diff --git a/tests/drivers/test_keysight_34465a.py b/tests/drivers/test_keysight_34465a.py index bcd5f175a6e6..9df99528ebef 100644 --- a/tests/drivers/test_keysight_34465a.py +++ b/tests/drivers/test_keysight_34465a.py @@ -10,6 +10,8 @@ if TYPE_CHECKING: from collections.abc import Generator + from pytest_mock import MockerFixture + class _AskCallable(Protocol): def __call__(self, cmd: str) -> str: ... @@ -167,3 +169,25 @@ def test_display_text(driver: Keysight34465A) -> None: driver.display.text(original_text) restored_text = driver.display.text() assert restored_text == original_text + + +@pytest.mark.parametrize("value", ["MIN", "MAX", "DEF"]) +def test_set_resolution_to_mnemonic( + driver: Keysight34465A, mocker: "MockerFixture", value: str +) -> None: + write_raw = mocker.spy(driver, "write_raw") + + driver.resolution(value) + + write_raw.assert_any_call(f"VOLT:DC:RES {value}") + + +def test_set_resolution_to_number( + driver: Keysight34465A, mocker: "MockerFixture" +) -> None: + write_raw = mocker.spy(driver, "write_raw") + driver.range(1) + + driver.resolution(3e-5) + + write_raw.assert_any_call("VOLT:DC:RES 3.0e-05") diff --git a/tests/drivers/test_keysight_e4980a.py b/tests/drivers/test_keysight_e4980a.py index 844a5f98ae5f..2489917d1788 100644 --- a/tests/drivers/test_keysight_e4980a.py +++ b/tests/drivers/test_keysight_e4980a.py @@ -8,6 +8,8 @@ if TYPE_CHECKING: from collections.abc import Generator + from pytest_mock import MockerFixture + @pytest.fixture(name="driver") def _make_driver() -> "Generator[E4980A.KeysightE4980A, None, None]": @@ -74,3 +76,23 @@ def test_range_is_float(driver: E4980A.KeysightE4980A) -> None: driver.range(1000) assert driver.range() == 1000.0 + + +@pytest.mark.parametrize( + ("action", "command"), + [("open", ":CORRection:OPEN"), ("short", ":CORRection:SHORt")], +) +def test_correction_methods_send_command( + driver: E4980A.KeysightE4980A, mocker: "MockerFixture", action: str, command: str +) -> None: + # these used to be parameters, so calling them without an argument only + # returned the cached value and did not send anything + write_raw = mocker.spy(driver, "write_raw") + if action == "open": + driver.correction.open() + else: + driver.correction.short() + + write_raw.assert_called_once_with(command) + # the instrument does not reply, so the next query is unaffected + assert driver.IDN()["model"] == "E4980A"