From c4d6831458ea5ff001133d80fe94c6116ac53d56 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Mon, 7 Oct 2024 15:10:51 +0200 Subject: [PATCH 01/29] To be tested! Handling attribute errors when removing validators --- src/qumada/measurement/device_object.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index e62f77b..c6a1389 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -596,8 +596,12 @@ def _set_limits(self): as no valid instrument parameter was assigned to it!" ) else: - if self._limit_validator in param.validators: - param.remove_validator() + try: + if self._limit_validator in param.validators: + param.remove_validator() + except AttributeError as e: + logger.warning(e) + pass self._limit_validator = Numbers(min_value=min(self.limits), max_value=max(self.limits)) param.add_validator(self._limit_validator) From 7937bfd2796e84e1463475322c98101e68b12d7a Mon Sep 17 00:00:00 2001 From: THuckemann Date: Mon, 7 Oct 2024 15:11:53 +0200 Subject: [PATCH 02/29] Using default buffer settings from device for measurements if no settings are explicitely specified. --- src/qumada/measurement/device_object.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index c6a1389..96d4eb5 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -47,6 +47,7 @@ def __init__( self.instrument_parameters = {} self.make_terminals_global = make_terminals_global self.station = station + self.buffer_settings = {} self.buffer_script_setup = {} self.states = {} self.ramp: bool = True @@ -252,6 +253,8 @@ def timetrace( station = self.station if not isinstance(station, Station): raise TypeError("No valid station assigned!") + if buffer_settings == {}: + buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered is True: logger.warning("Temporarily modifying buffer settings to match function arguments.") @@ -320,6 +323,8 @@ def sweep_2D( fast_param.setpoints = np.linspace( fast_param.value - fast_param_range / 2.0, fast_param.value + fast_param_range / 2.0, fast_num_points ) + if buffer_settings == {}: + buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered is True: if "num_points" in temp_buffer_settings.keys(): @@ -647,10 +652,13 @@ def measured_ramp( if buffered is False: self.setpoints = [*np.linspace(start, value, num_points), *np.linspace(value, start, num_points)] else: + logger.warning("Cannot do backsweep for buffered measurements") self.setpoints = np.linspace(start, value, num_points) else: self.setpoints = np.linspace(start, value, num_points) + if buffer_settings == {}: + buffer_settings = self._parent_device.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered: if "num_points" in temp_buffer_settings.keys(): From e39d1f7d89afc32d8ab0d924c98f676150be7ec3 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Mon, 7 Oct 2024 15:15:30 +0200 Subject: [PATCH 03/29] Added backsweeps to buffered measured_ramps. --- src/qumada/measurement/device_object.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 96d4eb5..3f1edf0 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -652,8 +652,6 @@ def measured_ramp( if buffered is False: self.setpoints = [*np.linspace(start, value, num_points), *np.linspace(value, start, num_points)] else: - - logger.warning("Cannot do backsweep for buffered measurements") self.setpoints = np.linspace(start, value, num_points) else: self.setpoints = np.linspace(start, value, num_points) @@ -671,13 +669,17 @@ def measured_ramp( "Num_points not specified in buffer settings! fast_num_points value is \ ignored and buffer settings are used to specify measurement!" ) - script = Generic_1D_Sweep_buffered() + if backsweep is True: + script = Generic_1D_Hysteresis_buffered() + else: + script = Generic_1D_Sweep_buffered() else: script = Generic_1D_Sweep() script.setup( self._parent_device.save_to_dict(priorize_stored_value=priorize_stored_value), metadata=metadata, name=name, + iterations = 1, buffer_settings=temp_buffer_settings, **self._parent_device.buffer_script_setup, ) From 6baf1c95367690de0968a1d60dda1e3c9ac017fe Mon Sep 17 00:00:00 2001 From: THuckemann Date: Mon, 7 Oct 2024 15:41:07 +0200 Subject: [PATCH 04/29] Danke Pascal! --- src/qumada/measurement/device_object.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 3f1edf0..d367168 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -245,7 +245,7 @@ def timetrace( metadata=None, station=None, buffered=False, - buffer_settings: dict = {}, + buffer_settings: dict|None = None, priorize_stored_value=False, ): """ """ @@ -253,7 +253,7 @@ def timetrace( station = self.station if not isinstance(station, Station): raise TypeError("No valid station assigned!") - if buffer_settings == {}: + if buffer_settings is None: buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered is True: @@ -298,7 +298,7 @@ def sweep_2D( metadata=None, station=None, buffered=False, - buffer_settings: dict = {}, + buffer_settings: dict|None = None, priorize_stored_value=False, restore_state=True, ): @@ -323,7 +323,7 @@ def sweep_2D( fast_param.setpoints = np.linspace( fast_param.value - fast_param_range / 2.0, fast_param.value + fast_param_range / 2.0, fast_num_points ) - if buffer_settings == {}: + if buffer_settings is None: buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered is True: @@ -631,7 +631,7 @@ def measured_ramp( metadata=None, backsweep=False, buffered=False, - buffer_settings={}, + buffer_settings: dict|None = None, priorize_stored_value=False, ): if station is None: @@ -655,7 +655,7 @@ def measured_ramp( self.setpoints = np.linspace(start, value, num_points) else: self.setpoints = np.linspace(start, value, num_points) - if buffer_settings == {}: + if buffer_settings is None: buffer_settings = self._parent_device.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered: From 5173e9fc7716bbfffb09490e31b0b2660763d0ca Mon Sep 17 00:00:00 2001 From: THuckemann Date: Mon, 7 Oct 2024 17:36:05 +0200 Subject: [PATCH 05/29] Cleaning up script.setup() --- src/qumada/measurement/measurement.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/qumada/measurement/measurement.py b/src/qumada/measurement/measurement.py index 558ccda..d2df4b2 100644 --- a/src/qumada/measurement/measurement.py +++ b/src/qumada/measurement/measurement.py @@ -179,7 +179,7 @@ def setup( *, add_script_to_metadata: bool = True, add_parameters_to_metadata: bool = True, - buffer_settings: dict = {}, + buffer_settings: dict|None = None, measurement_name: str | None = None, **settings: dict, ) -> None: @@ -209,14 +209,16 @@ def setup( self.measurement_name = measurement_name cls = type(self) try: - self.buffer_settings.update(buffer_settings) - except Exception: - self.buffer_settings = buffer_settings + self.buffer_settings.update(buffer_settings or {}) + except Exception as e: + logger.warning(f"Ex={e}") + self.buffer_settings = buffer_settings or {} self._set_buffered_num_points() try: self.settings.update(settings) - except Exception: + except Exception as e: + logger.warning(f"Ex={e}") self.settings = settings # Add script and parameters to metadata @@ -224,15 +226,15 @@ def setup( try: metadata.add_script_to_metadata(inspect.getsource(cls), language="python", name=cls.__name__) except OSError as err: - print(f"Source of MeasurementScript could not be acquired: {err}") + logger.warning(f"Source of MeasurementScript could not be acquired: {err}") except Exception as ex: - print(f"Script could not be added to metadata: {ex}") + logger.warning(f"Script could not be added to metadata: {ex}") if add_parameters_to_metadata: try: metadata.add_parameters_to_metadata(json.dumps(parameters), name=f"{cls.__name__}Settings") except Exception as ex: - print(f"Parameters could not be added to metadata: {ex}") + logger.warning(f"Parameters could not be added to metadata: {ex}") # Add gate parameters for gate, vals in parameters.items(): From b822a9a09a6e1717f73caae5cd0be2eb8bac77d0 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Tue, 8 Oct 2024 13:18:57 +0200 Subject: [PATCH 06/29] Device measurement set unused dynamic params to static gettable instead of static --- src/qumada/measurement/device_object.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index d367168..38401b6 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -17,6 +17,7 @@ from qumada.measurement.scripts import ( Generic_1D_Sweep, Generic_1D_Sweep_buffered, + Generic_1D_Hysteresis_buffered, Generic_2D_Sweep_buffered, Generic_nD_Sweep, Timetrace, @@ -312,7 +313,7 @@ def sweep_2D( for terminal in self.terminals.values(): for parameter in terminal.terminal_parameters.values(): if parameter.type == "dynamic": - parameter.type = "static" + parameter.type = "static gettable" slow_param.type = "dynamic" slow_param.setpoints = np.linspace( slow_param.value - slow_param_range / 2.0, slow_param.value + slow_param_range / 2.0, slow_num_points @@ -644,7 +645,7 @@ def measured_ramp( for terminal_name, terminal in self._parent_device.terminals.items(): for param_name, param in terminal.terminal_parameters.items(): if param.type == "dynamic": - param.type = "static" + param.type = "static gettable" self.type = "dynamic" if start is None: start = self() From 55ad3d8bef7b2b581ef8691fd648736f57239e32 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Tue, 8 Oct 2024 13:19:37 +0200 Subject: [PATCH 07/29] Fixing typo --- src/qumada/utils/ramp_parameter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qumada/utils/ramp_parameter.py b/src/qumada/utils/ramp_parameter.py index 834b4f6..584e4e7 100644 --- a/src/qumada/utils/ramp_parameter.py +++ b/src/qumada/utils/ramp_parameter.py @@ -112,7 +112,7 @@ def ramp_parameter( num_points = int(abs(current_value - float(target)) / (ramp_rate * setpoint_intervall)) + 2 if ramp_time is not None and ramp_time < abs(current_value - float(target)) / ramp_rate: print( - f"Ramp rate of {param} is to low to reach target value in specified" + f"Ramp rate of {parameter} is to low to reach target value in specified" "max ramp time. Adapting ramp rate to match ramp time" ) return ramp_parameter( From 36ce06035578d4e7d2aa6f79f6ae31e4cd620448 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Tue, 15 Oct 2024 17:42:13 +0200 Subject: [PATCH 08/29] Changes at sionludi lab pc --- pyproject.toml | 2 +- src/qumada/instrument/buffers/mfli_buffer.py | 2 +- .../instrument/custom_drivers/QDevil/QDAC2.py | 2441 +++++++++++++++++ .../instrument/custom_drivers/ZI/MFLI.py | 15 + src/qumada/instrument/mapping/QDevil/qdac2.py | 30 +- src/qumada/instrument/mapping/base.py | 1 + src/qumada/measurement/device_object.py | 129 +- src/qumada/measurement/measurement.py | 18 +- src/qumada/measurement/shuttling_scripts.py | 233 ++ src/{tests => qumada/utils}/plotting_2D.py | 0 10 files changed, 2856 insertions(+), 15 deletions(-) create mode 100644 src/qumada/instrument/custom_drivers/QDevil/QDAC2.py create mode 100644 src/qumada/measurement/shuttling_scripts.py rename src/{tests => qumada/utils}/plotting_2D.py (100%) diff --git a/pyproject.toml b/pyproject.toml index 5e88436..b61e5cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dynamic = ["version"] requires-python = ">=3.9" dependencies = [ - "qcodes >= 0.40.0", + "qcodes >= 0.48.0", "qcodes_contrib_drivers >= 0.18.0", "matplotlib", "jsonschema", diff --git a/src/qumada/instrument/buffers/mfli_buffer.py b/src/qumada/instrument/buffers/mfli_buffer.py index f1b1d5b..a2ed64f 100644 --- a/src/qumada/instrument/buffers/mfli_buffer.py +++ b/src/qumada/instrument/buffers/mfli_buffer.py @@ -136,7 +136,7 @@ def num_points(self) -> int | None: @num_points.setter def num_points(self, num_points) -> None: - if num_points > 8388608: + if num_points > 8_388_608: raise BufferException( "Buffer is to small for this measurement. \ Please reduce the number of data points" diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py new file mode 100644 index 0000000..dc2ad65 --- /dev/null +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -0,0 +1,2441 @@ +import numpy as np +import itertools +import uuid +from time import sleep as sleep_s +from qcodes.instrument.channel import InstrumentChannel, ChannelList +from qcodes.instrument.visa import VisaInstrument +from pyvisa.errors import VisaIOError +from qcodes.utils import validators +from typing import NewType, Tuple, Sequence, List, Dict, Optional +from packaging.version import Version, parse +import abc + +# Version 1.2.0 +# +# Guiding principles for this driver for QDevil QDAC-II +# ----------------------------------------------------- +# +# 1. Each command should be self-contained, so +# +# qdac.ch02.dc_constant_V(0.1) +# +# should make sure that channel 2 is in the right mode for outputting +# a constant voltage. +# +# 2. Numeric values should be in ISO units and/or their unit should be an +# explicitly part of the function name, like above. If the numeric is +# a unit-less number, then prefixed by n_ like +# +# qdac.n_channels() +# +# 3. Allocation of resources should be automated as much as possible, preferably +# by python context managers that automatically clean up on exit. Such +# context managers have a name with a '_Context' suffix. +# +# 4. Any generator should by default be set to start on the BUS trigger +# (*TRG) so that it is possible to synchronise several generators without +# further setup; which also eliminates the need for special cases for the +# BUS trigger. + + +# +# Future improvements +# ------------------- +# +# - Detect and handle mixing of internal and external triggers (_trigger). +# + +error_ambiguous_wave = 'Only one of frequency_Hz or period_s can be ' \ + 'specified for a wave form' + + +def ints_to_comma_separated_list(array: Sequence[int]) -> str: + return ','.join([str(x) for x in array]) + + +def floats_to_comma_separated_list(array: Sequence[float]) -> str: + rounded = [format(x, 'g') for x in array] + return ','.join(rounded) + + +def comma_sequence_to_list(sequence: str) -> Sequence[str]: + if not sequence: + return [] + return [x.strip() for x in sequence.split(',')] + + +def comma_sequence_to_list_of_floats(sequence: str) -> Sequence[float]: + if not sequence: + return [] + return [float(x.strip()) for x in sequence.split(',')] + + +def diff_matrix(initial: Sequence[float], + measurements: Sequence[Sequence[float]]) -> np.ndarray: + """Subtract an array of measurements by an initial measurement + """ + matrix = np.asarray(measurements) + return matrix - np.asarray(list(itertools.repeat(initial, matrix.shape[1]))) + + +def split_version_string_into_components(version: str) -> List[str]: + return version.split('-') + + +"""External input trigger + +There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4). +""" +ExternalInput = NewType('ExternalInput', int) + + +class QDac2Trigger_Context: + """Internal Triggers with automatic deallocation + + This context manager wraps an already-allocated internal trigger number so + that the trigger can be automatically reclaimed when the context exits. + """ + + def __init__(self, parent: 'QDac2', value: int): + self._parent = parent + self._value = value + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._parent.free_trigger(self) + # Propagate exceptions + return False + + @property + def value(self) -> int: + """internal SCPI trigger number""" + return self._value + + +def _trigger_context_to_value(trigger: QDac2Trigger_Context) -> int: + return trigger.value + + +class QDac2ExternalTrigger(InstrumentChannel): + """External output trigger + + There are three 5V isolated triggers on the front (1, 2, 3) and two + non-isolated 3V3 on the back (4, 5). + """ + + def __init__(self, parent: 'QDac2', name: str, external: int): + super().__init__(parent, name) + self.add_function( + name='source_from_bus', + call_cmd=f'outp:trig{external}:sour bus' + ) + self.add_parameter( + name='source_from_input', + # Route external input to external output + set_cmd='outp:trig{0}:sour ext{1}'.format(external, '{}'), + get_parser=int + ) + self.add_parameter( + name='source_from_trigger', + # Route internal trigger to external output + set_parser=_trigger_context_to_value, + set_cmd='outp:trig{0}:sour int{1}'.format(external, '{}'), + get_parser=int + ) + self.add_parameter( + name='width_s', + label='width', + unit='s', + set_cmd='outp:trig{0}:widt {1}'.format(external, '{}'), + get_cmd=f'outp:trig{external}:widt?', + get_parser=float + ) + self.add_parameter( + name='polarity', + label='polarity', + set_cmd='outp:trig{0}:pol {1}'.format(external, '{}'), + get_cmd=f'outp:trig{external}:pol?', + get_parser=str, + vals=validators.Enum('inv', 'norm') + ) + self.add_parameter( + name='delay_s', + label='delay', + unit='s', + set_cmd='outp:trig{0}:del {1}'.format(external, '{}'), + get_cmd=f'outp:trig{external}:del?', + get_parser=float + ) + self.add_function( + name='signal', + call_cmd=f'outp:trig{external}:sign' + ) + self.add_parameter( + name='set_trigger', + # Route internal trigger to external output + #set_parser=_trigger_context_to_value, + set_cmd='outp:trig{0}:sour {1}'.format(external, "{}"), + ) + + +class _Channel_Context(metaclass=abc.ABCMeta): + + def __init__(self, channel: 'QDac2Channel'): + self._channel = channel + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Propagate exceptions + return False + + def allocate_trigger(self) -> QDac2Trigger_Context: + """Allocate internal trigger + + Returns: + QDac2Trigger_Context: Context that wraps the trigger + """ + return self._channel._parent.allocate_trigger() + + @abc.abstractmethod + def start_on(self, trigger: QDac2Trigger_Context) -> None: + pass + + @abc.abstractmethod + def start_on_external(self, trigger: ExternalInput) -> None: + pass + + @abc.abstractmethod + def abort(self) -> None: + pass + + def _write_channel(self, cmd: str) -> None: + self._channel.write_channel(cmd) + + def _write_channel_floats(self, cmd: str, values: Sequence[float]) -> None: + self._channel.write_channel_floats(cmd, values) + + def _ask_channel(self, cmd: str) -> str: + return self._channel.ask_channel(cmd) + + def _channel_message(self, template: str) -> None: + return self._channel._channel_message(template) + + +class _Dc_Context(_Channel_Context): + + def __init__(self, channel: 'QDac2Channel'): + super().__init__(channel) + self._write_channel('sour{0}:dc:trig:sour hold') + self._trigger: Optional[QDac2Trigger_Context] = None + self._marker_start: Optional[QDac2Trigger_Context] = None + self._marker_end: Optional[QDac2Trigger_Context] = None + self._marker_step_start: Optional[QDac2Trigger_Context] = None + self._marker_step_end: Optional[QDac2Trigger_Context] = None + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to DC generator + + Args: + trigger (QDac2Trigger_Context): trigger that will start DC + """ + self._trigger = trigger + internal = _trigger_context_to_value(trigger) + self._write_channel(f'sour{"{0}"}:dc:trig:sour int{internal}') + self._make_ready_to_start() + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to DC generator + + Args: + trigger (ExternalInput): trigger that will start DC generator + """ + self._trigger = None + self._write_channel(f'sour{"{0}"}:dc:trig:sour ext{trigger}') + self._make_ready_to_start() + + def abort(self) -> None: + """Abort any DC running generator on the channel + """ + self._write_channel('sour{0}:dc:abor') + + def end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of the DC generator + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end + """ + if not self._marker_end: + self._marker_end = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:dc:mark:end {self._marker_end.value}') + return self._marker_end + + def start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of the DC generator + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning + """ + if not self._marker_start: + self._marker_start = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:dc:mark:star {self._marker_start.value}') + return self._marker_start + + def step_end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of each step + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each step + """ + if not self._marker_step_end: + self._marker_step_end = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:dc:mark:send {self._marker_step_end.value}') + return self._marker_step_end + + def step_start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of each step + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each step + """ + if not self._marker_step_start: + self._marker_step_start = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:dc:mark:sst {self._marker_step_start.value}') + return self._marker_step_start + + def _set_delay(self, delay_s: float) -> None: + self._write_channel(f'sour{"{0}"}:dc:del {delay_s}') + + def _set_triggering(self) -> None: + self._write_channel('sour{0}:dc:trig:sour bus') + self._make_ready_to_start() + + def _start(self, description: str) -> None: + if self._trigger: + self._make_ready_to_start() + return self._write_channel(f'tint {self._trigger.value}') + self._switch_to_immediate_trigger() + self._write_channel('sour{0}:dc:init') + + def _make_ready_to_start(self) -> None: + self._write_channel('sour{0}:dc:init:cont on') + self._write_channel('sour{0}:dc:init') + + def _switch_to_immediate_trigger(self) -> None: + self._write_channel('sour{0}:dc:init:cont off') + self._write_channel('sour{0}:dc:trig:sour imm') + + +class Sweep_Context(_Dc_Context): + + def __init__(self, channel: 'QDac2Channel', start_V: float, stop_V: float, + points: int, repetitions: int, dwell_s: float, delay_s: float, + backwards: bool, stepped: bool): + self._repetitions = repetitions + super().__init__(channel) + channel.write_channel('sour{0}:volt:mode swe') + self._set_voltages(start_V, stop_V) + channel.write_channel(f'sour{"{0}"}:swe:poin {points}') + self._set_trigger_mode(stepped) + channel.write_channel(f'sour{"{0}"}:swe:dwel {dwell_s}') + super()._set_delay(delay_s) + self._set_direction(backwards) + self._set_repetitions() + self._set_triggering() + + def _set_voltages(self, start_V: float, stop_V: float): + self._write_channel(f'sour{"{0}"}:swe:star {start_V}') + self._write_channel(f'sour{"{0}"}:swe:stop {stop_V}') + + def _set_trigger_mode(self, stepped: bool) -> None: + if stepped: + return self._write_channel('sour{0}:swe:gen step') + self._write_channel('sour{0}:swe:gen auto') + + def _set_direction(self, backwards: bool) -> None: + if backwards: + return self._write_channel('sour{0}:swe:dir down') + self._write_channel('sour{0}:swe:dir up') + + def _set_repetitions(self) -> None: + self._write_channel(f'sour{"{0}"}:swe:coun {self._repetitions}') + + def _perpetual(self) -> bool: + return self._repetitions < 0 + + def start(self) -> None: + """Start the DC sweep + """ + self._start('DC sweep') + + def points(self) -> int: + """ + Returns: + int: Number of steps in the DC sweep + """ + return int(self._ask_channel('sour{0}:swe:poin?')) + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the DC sweep + """ + return int(self._ask_channel('sour{0}:swe:ncl?')) + + def time_s(self) -> float: + """ + Returns: + float: Seconds that it will take to do the sweep + """ + return float(self._ask_channel('sour{0}:swe:time?')) + + def start_V(self) -> float: + """ + Returns: + float: Starting voltage + """ + return float(self._ask_channel('sour{0}:swe:star?')) + + def stop_V(self) -> float: + """ + Returns: + float: Ending voltage + """ + return float(self._ask_channel('sour{0}:swe:stop?')) + + def values_V(self) -> Sequence[float]: + """ + Returns: + Sequence[float]: List of voltages + """ + return list(np.linspace(self.start_V(), self.stop_V(), self.points())) + + +class List_Context(_Dc_Context): + + def __init__(self, channel: 'QDac2Channel', voltages: Sequence[float], + repetitions: int, dwell_s: float, delay_s: float, + backwards: bool, stepped: bool): + super().__init__(channel) + self._repetitions = repetitions + self._write_channel('sour{0}:volt:mode list') + self._set_voltages(voltages) + self._set_trigger_mode(stepped) + self._write_channel(f'sour{"{0}"}:list:dwel {dwell_s}') + super()._set_delay(delay_s) + self._set_direction(backwards) + self._set_repetitions() + self._set_triggering() + + def _set_voltages(self, voltages: Sequence[float]) -> None: + self._write_channel_floats('sour{0}:list:volt ', voltages) + + def _set_trigger_mode(self, stepped: bool) -> None: + if stepped: + return self._write_channel('sour{0}:list:tmod step') + self._write_channel('sour{0}:list:tmod auto') + + def _set_direction(self, backwards: bool) -> None: + if backwards: + return self._write_channel('sour{0}:list:dir down') + self._write_channel('sour{0}:list:dir up') + + def _set_repetitions(self) -> None: + self._write_channel(f'sour{"{0}"}:list:coun {self._repetitions}') + + def _perpetual(self) -> bool: + return self._repetitions < 0 + + def start(self) -> None: + """Start the DC list generator + """ + self._start('DC list') + + def append(self, voltages: Sequence[float]) -> None: + """Append voltages to the existing list + + Arguments: + voltages (Sequence[float]): Sequence of voltages + """ + self._write_channel_floats('sour{0}:list:volt:app ', voltages) + self._make_ready_to_start() + + def points(self) -> int: + """ + Returns: + int: Number of steps in the DC list + """ + return int(self._ask_channel('sour{0}:list:poin?')) + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the DC list + """ + return int(self._ask_channel('sour{0}:list:ncl?')) + + def values_V(self) -> Sequence[float]: + """ + Returns: + Sequence[float]: List of voltages + """ + # return comma_sequence_to_list_of_floats( + # self._ask_channel('sour{0}:list:volt?')) + return comma_sequence_to_list_of_floats( + self._ask_channel('sour{0}:list:volt?')) + + +class _Waveform_Context(_Channel_Context): + + def __init__(self, channel: 'QDac2Channel'): + super().__init__(channel) + self._trigger: Optional[QDac2Trigger_Context] = None + self._marker_start: Optional[QDac2Trigger_Context] = None + self._marker_end: Optional[QDac2Trigger_Context] = None + self._marker_period_start: Optional[QDac2Trigger_Context] = None + self._marker_period_end: Optional[QDac2Trigger_Context] = None + + def _start(self, wave_kind: str, description: str) -> None: + if self._trigger: + self._make_ready_to_start(wave_kind) + return self._write_channel(f'tint {self._trigger.value}') + self._switch_to_immediate_trigger(wave_kind) + self._write_channel(f'sour{"{0}"}:{wave_kind}:init') + + def _start_on(self, trigger: QDac2Trigger_Context, wave_kind: str) -> None: + self._trigger = trigger + internal = _trigger_context_to_value(trigger) + self._write_channel(f'sour{"{0}"}:{wave_kind}:trig:sour int{internal}') + self._make_ready_to_start(wave_kind) + + def _start_on_external(self, trigger: ExternalInput, wave_kind: str) -> None: + self._trigger = None + self._write_channel(f'sour{"{0}"}:{wave_kind}:trig:sour ext{trigger}') + self._make_ready_to_start(wave_kind) + + def _end_marker(self, wave_kind: str) -> QDac2Trigger_Context: + if not self._marker_end: + self._marker_end = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:{wave_kind}:mark:end {self._marker_end.value}') + return self._marker_end + + def _start_marker(self, wave_kind: str) -> QDac2Trigger_Context: + if not self._marker_start: + self._marker_start = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:{wave_kind}:mark:star {self._marker_start.value}') + return self._marker_start + + def _period_end_marker(self, wave_kind: str) -> QDac2Trigger_Context: + if not self._marker_period_end: + self._marker_period_end = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:{wave_kind}:mark:pend {self._marker_period_end.value}') + return self._marker_period_end + + def _period_start_marker(self, wave_kind: str) -> QDac2Trigger_Context: + if not self._marker_period_start: + self._marker_period_start = self.allocate_trigger() + self._write_channel(f'sour{"{0}"}:{wave_kind}:mark:pstart {self._marker_period_start.value}') + return self._marker_period_start + + def _make_ready_to_start(self, wave_kind: str) -> None: + self._write_channel(f'sour{"{0}"}:{wave_kind}:init:cont on') + self._write_channel(f'sour{"{0}"}:{wave_kind}:init') + + def _switch_to_immediate_trigger(self, wave_kind: str): + self._write_channel(f'sour{"{0}"}:{wave_kind}:init:cont off') + self._write_channel(f'sour{"{0}"}:{wave_kind}:trig:sour imm') + + def _set_delay(self, wave_kind: str, delay_s) -> None: + self._write_channel(f'sour{"{0}"}:{wave_kind}:del {delay_s}') + + def _set_slew(self, wave_kind: str, slew_V_s: Optional[float]) -> None: + if slew_V_s: + # Bug, see https://trello.com/c/SeeUrRNY + self._write_channel(f'sour{"{0}"}:{wave_kind}:slew {slew_V_s}') + else: + self._write_channel(f'sour{"{0}"}:{wave_kind}:slew inf') + + +class Square_Context(_Waveform_Context): + + def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], + repetitions: int, period_s: Optional[float], + duty_cycle_percent: float, kind: str, inverted: bool, + span_V: float, offset_V: float, delay_s: float, + slew_V_s: Optional[float]): + super().__init__(channel) + self._repetitions = repetitions + self._write_channel('sour{0}:squ:trig:sour hold') + self._set_frequency(frequency_Hz, period_s) + self._write_channel(f'sour{"{0}"}:squ:dcyc {duty_cycle_percent}') + self._set_type(kind) + self._set_polarity(inverted) + self._write_channel(f'sour{"{0}"}:squ:span {span_V}') + self._write_channel(f'sour{"{0}"}:squ:offs {offset_V}') + self._set_slew('squ', slew_V_s) + super()._set_delay('squ', delay_s) + self._write_channel(f'sour{"{0}"}:squ:coun {repetitions}') + self._set_triggering() + + def start(self) -> None: + """Start the square wave generator + """ + self._start('squ', 'square wave') + + def abort(self) -> None: + """Abort any running square wave generator + """ + self._write_channel('sour{0}:squ:abor') + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the square wave + """ + return int(self._ask_channel('sour{0}:squ:ncl?')) + + def _set_frequency(self, frequency_Hz: Optional[float], + period_s: Optional[float]) -> None: + if frequency_Hz: + return self._write_channel(f'sour{"{0}"}:squ:freq {frequency_Hz}') + if period_s: + self._write_channel(f'sour{"{0}"}:squ:per {period_s}') + + def _set_type(self, kind: str) -> None: + if kind == 'positive': + self._write_channel('sour{0}:squ:typ pos') + elif kind == 'negative': + self._write_channel('sour{0}:squ:typ neg') + else: + self._write_channel('sour{0}:squ:typ symm') + + def _set_polarity(self, inverted: bool) -> None: + if inverted: + self._write_channel('sour{0}:squ:pol inv') + else: + self._write_channel('sour{0}:squ:pol norm') + + def _set_triggering(self) -> None: + self._write_channel('sour{0}:squ:trig:sour bus') + self._make_ready_to_start('squ') + + def end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of the square wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end + """ + return super()._end_marker('squ') + + def start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of the square wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning + """ + return super()._start_marker('squ') + + def period_end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each period + """ + return super()._period_end_marker('squ') + + def period_start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning of each period + """ + return super()._period_start_marker('squ') + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to start the square wave generator + + Args: + trigger (QDac2Trigger_Context): trigger that will start square wave + """ + return super()._start_on(trigger, 'squ') + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to start the square wave generator + + Args: + trigger (ExternalInput): external trigger that will start square wave + """ + return super()._start_on_external(trigger, 'squ') + + +class Sine_Context(_Waveform_Context): + + def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], + repetitions: int, period_s: Optional[float], inverted: bool, + span_V: float, offset_V: float, delay_s: float, + slew_V_s: Optional[float]): + super().__init__(channel) + self._repetitions = repetitions + self._write_channel('sour{0}:sine:trig:sour hold') + self._set_frequency(frequency_Hz, period_s) + self._set_polarity(inverted) + self._write_channel(f'sour{"{0}"}:sine:span {span_V}') + self._write_channel(f'sour{"{0}"}:sine:offs {offset_V}') + self._set_slew('sine', slew_V_s) + super()._set_delay('sine', delay_s) + self._write_channel(f'sour{"{0}"}:sine:coun {repetitions}') + self._set_triggering() + + def start(self) -> None: + """Start the sine wave generator + """ + self._start('sine', 'sine wave') + + def abort(self) -> None: + """Abort any running sine wave generator + """ + self._write_channel('sour{0}:sine:abor') + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the sine wave + """ + return int(self._ask_channel('sour{0}:sine:ncl?')) + + def _set_frequency(self, frequency_Hz: Optional[float], + period_s: Optional[float]) -> None: + if frequency_Hz: + return self._write_channel(f'sour{"{0}"}:sine:freq {frequency_Hz}') + if period_s: + self._write_channel(f'sour{"{0}"}:sine:per {period_s}') + + def _set_polarity(self, inverted: bool) -> None: + if inverted: + self._write_channel('sour{0}:sine:pol inv') + else: + self._write_channel('sour{0}:sine:pol norm') + + def _set_triggering(self) -> None: + self._write_channel('sour{0}:sine:trig:sour bus') + self._make_ready_to_start('sine') + + def end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of the sine wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end + """ + return super()._end_marker('sine') + + def start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of the sine wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning + """ + return super()._start_marker('sine') + + def period_end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each period + """ + return super()._period_end_marker('sine') + + def period_start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning of each period + """ + return super()._period_start_marker('sine') + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to start the sine wave generator + + Args: + trigger (QDac2Trigger_Context): trigger that will start sine wave + """ + return super()._start_on(trigger, 'sine') + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to start the sine wave generator + + Args: + trigger (ExternalInput): external trigger that will start sine wave + """ + return super()._start_on_external(trigger, 'sine') + + +class Triangle_Context(_Waveform_Context): + + def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], + repetitions: int, period_s: Optional[float], + duty_cycle_percent: float, inverted: bool, span_V: float, + offset_V: float, delay_s: float, slew_V_s: Optional[float]): + super().__init__(channel) + self._repetitions = repetitions + self._write_channel('sour{0}:tri:trig:sour hold') + self._set_frequency(frequency_Hz, period_s) + self._write_channel(f'sour{"{0}"}:tri:dcyc {duty_cycle_percent}') + self._set_polarity(inverted) + self._write_channel(f'sour{"{0}"}:tri:span {span_V}') + self._write_channel(f'sour{"{0}"}:tri:offs {offset_V}') + self._set_slew('tri', slew_V_s) + super()._set_delay('tri', delay_s) + self._write_channel(f'sour{"{0}"}:tri:coun {repetitions}') + self._set_triggering() + + def start(self) -> None: + """Start the triangle wave generator + """ + self._start('tri', 'triangle wave') + + def abort(self) -> None: + """Abort any running triangle wave generator + """ + self._write_channel('sour{0}:tri:abor') + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the triangle wave + """ + return int(self._ask_channel('sour{0}:tri:ncl?')) + + def _set_frequency(self, frequency_Hz: Optional[float], + period_s: Optional[float]) -> None: + if frequency_Hz: + return self._write_channel(f'sour{"{0}"}:tri:freq {frequency_Hz}') + if period_s: + self._write_channel(f'sour{"{0}"}:tri:per {period_s}') + + def _set_type(self, kind: bool) -> None: + if kind == 'positive': + self._write_channel('sour{0}:tri:typ pos') + elif kind == 'negative': + self._write_channel('sour{0}:tri:typ neg') + else: + self._write_channel('sour{0}:tri:typ symm') + + def _set_polarity(self, inverted: bool) -> None: + if inverted: + self._write_channel('sour{0}:tri:pol inv') + else: + self._write_channel('sour{0}:tri:pol norm') + + def _set_triggering(self) -> None: + self._write_channel('sour{0}:tri:trig:sour bus') + self._make_ready_to_start('tri') + + def end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of the triangle wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end + """ + return super()._end_marker('tri') + + def start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of the triangle wave + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning + """ + return super()._start_marker('tri') + + def period_end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each period + """ + return super()._period_end_marker('tri') + + def period_start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning of each period + """ + return super()._period_start_marker('tri') + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to start the triangle wave generator + + Args: + trigger (QDac2Trigger_Context): trigger that will start triangle + """ + return super()._start_on(trigger, 'tri') + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to start the triangle wave generator + + Args: + trigger (ExternalInput): external trigger that will start triangle + """ + return super()._start_on_external(trigger, 'tri') + + +class Awg_Context(_Waveform_Context): + + def __init__(self, channel: 'QDac2Channel', trace_name: str, + repetitions: int, scale: float, offset_V: float, + slew_V_s: Optional[float]): + super().__init__(channel) + self._repetitions = repetitions + self._write_channel('sour{0}:awg:trig:sour hold') + self._write_channel(f'sour{"{0}"}:awg:def "{trace_name}"') + self._write_channel(f'sour{"{0}"}:awg:scal {scale}') + self._write_channel(f'sour{"{0}"}:awg:offs {offset_V}') + self._set_slew('awg', slew_V_s) + self._write_channel(f'sour{"{0}"}:awg:coun {repetitions}') + self._set_triggering() + + def start(self) -> None: + """Start the AWG + """ + self._start('awg', 'AWG') + + def abort(self) -> None: + """Abort any running AWG + """ + self._write_channel('sour{0}:awg:abor') + + def cycles_remaining(self) -> int: + """ + Returns: + int: Number of cycles remaining in the AWG + """ + return int(self._ask_channel('sour{0}:awg:ncl?')) + + def _set_triggering(self) -> None: + self._write_channel('sour{0}:awg:trig:sour bus') + self._make_ready_to_start('awg') + + def end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of the AWG + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end + """ + return super()._end_marker('awg') + + def start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of the AWG + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning + """ + return super()._start_marker('awg') + + def period_end_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the end of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the end of each period + """ + return super()._period_end_marker('awg') + + def period_start_marker(self) -> QDac2Trigger_Context: + """Internal trigger that will mark the beginning of each period + + A new internal trigger is allocated if necessary. + + Returns: + QDac2Trigger_Context: trigger that will mark the beginning of each period + """ + return super()._period_start_marker('awg') + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to start the AWG + + Args: + trigger (QDac2Trigger_Context): trigger that will start AWG + """ + return super()._start_on(trigger, 'awg') + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to start the AWG + + Args: + trigger (ExternalInput): external trigger that will start AWG + """ + return super()._start_on_external(trigger, 'awg') + + +class Measurement_Context(_Channel_Context): + + def __init__(self, channel: 'QDac2Channel', delay_s: float, + repetitions: int, current_range: str, + aperture_s: Optional[float], nplc: Optional[int]): + super().__init__(channel) + self._trigger: Optional[QDac2Trigger_Context] = None + self._write_channel(f'sens{"{0}"}:del {delay_s}') + self._write_channel(f'sens{"{0}"}:rang {current_range}') + self._set_aperture(aperture_s, nplc) + self._write_channel(f'sens{"{0}"}:coun {repetitions}') + self._set_triggering() + + def start(self) -> None: + """Start a current measurement + """ + if self._trigger: + return self._write_channel(f'tint {self._trigger.value}') + self._switch_to_immediate_trigger() + self._write_channel('sens{0}:init') + + def _switch_to_immediate_trigger(self) -> None: + self._write_channel('sens{0}:init:cont off') + self._write_channel('sens{0}:trig:sour imm') + + def start_on(self, trigger: QDac2Trigger_Context) -> None: + """Attach internal trigger to start the current measurement + + Args: + trigger (QDac2Trigger_Context): trigger that will start measurement + """ + self._trigger = trigger + internal = _trigger_context_to_value(trigger) + self._write_channel(f'sens{"{0}"}:trig:sour int{internal}') + self._write_channel(f'sens{"{0}"}:init:cont on') + self._write_channel(f'sens{"{0}"}:init') + + def start_on_external(self, trigger: ExternalInput) -> None: + """Attach external trigger to start the current measurement + + Args: + trigger (ExternalInput): trigger that will start measurement + """ + self._write_channel(f'sens{"{0}"}:trig:sour ext{trigger}') + self._write_channel(f'sens{"{0}"}:init:cont on') + self._write_channel(f'sens{"{0}"}:init') + + def abort(self) -> None: + """Abort current measurement + """ + self._write_channel('sens{0}:abor') + + def n_cycles_remaining(self) -> int: + """ + Returns: + int: Number of measurements remaining + """ + return int(self._ask_channel('sens{0}:ncl?')) + + def n_available(self) -> int: + """ + Returns: + int: Number of measurements available + """ + return int(self._ask_channel('sens{0}:data:poin?')) + + def available_A(self) -> Sequence[float]: + """Retrieve current measurements + + The available measurements will be removed from measurement queue. + + Returns: + Sequence[float]: list of available current measurements + """ + # Bug circumvention + if self.n_available() == 0: + return list() + return comma_sequence_to_list_of_floats( + self._ask_channel('sens{0}:data:rem?')) + + def peek_A(self) -> float: + """Peek at the first available current measurement + + Returns: + float: current in Amperes + """ + return float(self._ask_channel('sens{0}:data:last?')) + + def _set_aperture(self, aperture_s: Optional[float], nplc: Optional[int] + ) -> None: + if aperture_s: + return self._write_channel(f'sens{"{0}"}:aper {aperture_s}') + self._write_channel(f'sens{"{0}"}:nplc {nplc}') + + def _set_triggering(self) -> None: + self._write_channel('sens{0}:trig:sour bus') + self._write_channel('sens{0}:init') + + +class QDac2Channel(InstrumentChannel): + + def __init__(self, parent: 'QDac2', name: str, channum: int): + super().__init__(parent, name) + self._channum = channum + self.add_parameter( + name='measurement_range', + label='range', + set_cmd='sens{1}:rang {0}'.format('{}', channum), + get_cmd=f'sens{channum}:rang?', + vals=validators.Enum('low', 'high') + ) + self.add_parameter( + name='measurement_aperture_s', + label='aperture', + unit='s', + set_cmd='sens{1}:aper {0}'.format('{}', channum), + get_cmd=f'sens{channum}:aper?', + get_parser=float + ) + self.add_parameter( + name='measurement_nplc', + label='PLC', + set_cmd='sens{1}:nplc {0}'.format('{}', channum), + get_cmd=f'sens{channum}:nplc?', + get_parser=int + ) + self.add_parameter( + name='measurement_delay_s', + label=f'delay', + unit='s', + set_cmd='sens{1}:del {0}'.format('{}', channum), + get_cmd=f'sens{channum}:del?', + get_parser=float + ) + self.add_function( + name='measurement_abort', + call_cmd=f'sens{channum}:abor' + ) + self.add_parameter( + name='measurement_count', + label='count', + set_cmd='sens{1}:coun {0}'.format('{}', channum), + get_cmd=f'sens{channum}:coun?', + get_parser=int + ) + self.add_parameter( + name='n_masurements_remaining', + label='remaning', + get_cmd=f'sens{channum}:ncl?', + get_parser=int + ) + self.add_parameter( + name='current_last_A', + label='last', + unit='A', + get_cmd=f'sens{channum}:data:last?', + get_parser=float + ) + self.add_parameter( + name='n_measurements_available', + label='available', + get_cmd=f'sens{channum}:data:poin?', + get_parser=int + ) + self.add_parameter( + name='current_start_on', + # Channel {channum} current measurement on internal trigger + set_parser=_trigger_context_to_value, + set_cmd='sens{1}:trig:sour int{0}'.format('{}', channum), + ) + self.add_parameter( + name='measurement_start_on_external', + # Channel {channum} current measurement on external input + set_cmd='sens{1}:trig:sour ext{0}'.format('{}', channum), + ) + self.add_parameter( + name='output_range', + label='range', + set_cmd='sour{1}:rang {0}'.format('{}', channum), + get_cmd=f'sour{channum}:rang?', + vals=validators.Enum('low', 'high') + ) + self.add_parameter( + name='output_low_range_minimum_V', + label='low range min', + unit='V', + get_cmd=f'sour{channum}:rang:low:min?', + get_parser=float + ) + self.add_parameter( + name='output_low_range_maximum_V', + label='low voltage max', + unit='V', + get_cmd=f'sour{channum}:rang:low:max?', + get_parser=float + ) + self.add_parameter( + name='output_high_range_minimum_V', + label='high voltage min', + unit='V', + get_cmd=f'sour{channum}:rang:high:min?', + get_parser=float + ) + self.add_parameter( + name='output_high_range_maximum_V', + label='high voltage max', + unit='V', + get_cmd=f'sour{channum}:rang:high:max?', + get_parser=float + ) + self.add_parameter( + name='output_filter', + label=f'low-pass cut-off', + unit='Hz', + set_cmd='sour{1}:filt {0}'.format('{}', channum), + get_cmd=f'sour{channum}:filt?', + get_parser=str, + vals=validators.Enum('dc', 'med', 'high') + ) + self.add_parameter( + name='dc_constant_V', + label=f'ch{channum}', + unit='V', + set_cmd=self._set_fixed_voltage_immediately, + get_cmd=f'sour{channum}:volt?', + get_parser=float, + vals=validators.Numbers(-10.0, 10.0) + ) + self.add_parameter( + name='dc_last_V', + label=f'ch{channum}', + unit='V', + get_cmd=f'sour{channum}:volt:last?', + get_parser=float + ) + self.add_parameter( + name='dc_next_V', + label=f'ch{channum}', + unit='V', + set_cmd='sour{1}:volt:trig {0}'.format('{}', channum), + get_cmd=f'sour{channum}:volt:trig?', + get_parser=float + ) + self.add_parameter( + name='dc_slew_rate_V_per_s', + label=f'ch{channum}', + unit='V/s', + set_cmd='sour{1}:volt:slew {0}'.format('{}', channum), + get_cmd=f'sour{channum}:volt:slew?', + get_parser=float + ) + self.add_parameter( + name='read_current_A', + # Perform immediate current measurement on channel + label=f'ch{channum}', + unit='A', + get_cmd=f'read{channum}?', + get_parser=float + ) + self.add_parameter( + name='fetch_current_A', + # Retrieve all available current measurements on channel + label=f'ch{channum}', + unit='A', + get_cmd=f'fetc{channum}?', + get_parser=comma_sequence_to_list_of_floats + ) + self.add_parameter( + name='dc_mode', + label=f'DC mode', + set_cmd='sour{1}:volt:mode {0}'.format('{}', channum), + get_cmd=f'sour{channum}:volt:mode?', + vals=validators.Enum('fixed', 'list', 'sweep') + ) + self.add_function( + name='dc_initiate', + call_cmd=f'sour{channum}:dc:init' + ) + self.add_function( + name='dc_abort', + call_cmd=f'sour{channum}:dc:abor' + ) + self.add_function( + name='abort', + call_cmd=f'sour{channum}:all:abor' + ) + + @property + def number(self) -> int: + """Channel number""" + return self._channum + + def clear_measurements(self) -> Sequence[float]: + """Retrieve current measurements + + The available measurements will be removed from measurement queue. + + Returns: + Sequence[float]: list of available current measurements + """ + # Bug circumvention + if int(self.ask_channel('sens{0}:data:poin?')) == 0: + return list() + return comma_sequence_to_list_of_floats( + self.ask_channel('sens{0}:data:rem?')) + + def measurement(self, delay_s: float = 0.0, repetitions: int = 1, + current_range: str = 'high', + aperture_s: Optional[float] = None, + nplc: Optional[int] = None + ) -> Measurement_Context: + """Set up a sequence of current measurements + + Args: + delay_s (float, optional): Seconds to delay the actual measurement after trigger (default 0) + repetitions (int, optional): Number of consecutive measurements (default 1) + current_range (str, optional): high (10mA, default) or low (200nA) + nplc (None, optional): Integration time in power-line cycles (default 1) + aperture_s (None, optional): Seconds of integration time instead of NPLC + + Returns: + Measurement_Context: context manager + + Raises: + ValueError: configuration error + """ + if aperture_s and nplc: + raise ValueError('Only one of nplc or aperture_s can be ' + 'specified for a current measurement') + if not aperture_s and not nplc: + nplc = 1 + return Measurement_Context(self, delay_s, repetitions, current_range, + aperture_s, nplc) + + def output_mode(self, range: str = 'high', filter: str = 'high') -> None: + """Set the output voltage + + Args: + range (str, optional): Low or high (default) current range + filter (str, optional): DC (10Hz), medium (10kHz) or high (300kHz, default) voltage filter + """ + self.output_range(range) + self.output_filter(filter) + + def dc_list(self, voltages: Sequence[float], repetitions: int = 1, + dwell_s: float = 1e-03, delay_s: float = 0, + backwards: bool = False, stepped: bool = False + ) -> List_Context: + """Set up a DC-list generator + + Args: + voltages (Sequence[float]): Voltages in list + repetitions (int, optional): Number of repetitions of the list (default 1) + dwell_s (float, optional): Seconds between each voltage (default 1ms) + delay_s (float, optional): Seconds of delay after receiving a trigger (default 0) + backwards (bool, optional): Use list in reverse (default is forward) + stepped (bool, optional): True means that each step needs to be triggered (default False) + + Returns: + List_Context: context manager + """ + return List_Context(self, voltages, repetitions, dwell_s, delay_s, + backwards, stepped) + + def dc_sweep(self, start_V: float, stop_V: float, points: int, + repetitions: int = 1, dwell_s: float = 1e-03, + delay_s: float = 0, backwards=False, stepped=True + ) -> Sweep_Context: + """Set up a DC sweep + + Args: + start_V (float): Start voltage + stop_V (float): Send voltage + points (int): Number of steps + repetitions (int, optional): Number of repetition (default 1) + dwell_s (float, optional): Seconds between each voltage (default 1ms) + delay_s (float, optional): Seconds of delay after receiving a trigger (default 0) + backwards (bool, optional): Sweep in reverse (default is forward) + stepped (bool, optional): True means that each step needs to be triggered (default False) + + Returns: + Sweep_Context: context manager + """ + return Sweep_Context(self, start_V, stop_V, points, repetitions, + dwell_s, delay_s, backwards, stepped) + + def square_wave(self, frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, repetitions: int = -1, + duty_cycle_percent: float = 50.0, kind: str = 'symmetric', + inverted: bool = False, span_V: float = 0.2, + offset_V: float = 0.0, delay_s: float = 0, + slew_V_s: Optional[float] = None + ) -> Square_Context: + """Set up a square-wave generator + + Args: + frequency_Hz (float, optional): Frequency + period_s (float, optional): Period length (instead of frequency) + repetitions (int, optional): Number of repetition (default infinite) + duty_cycle_percent (float, optional): Percentage on-time (default 50%) + kind (str, optional): Positive, negative or symmetric (default) around the offset + inverted (bool, optional): True means flipped (default False) + span_V (float, optional): Voltage span (default 200mV) + offset_V (float, optional): Offset (default 0V) + delay_s (float, optional): Seconds of delay after receiving a trigger (default 0) + slew_V_s (float, optional): Max slew rate in V/s (default None) + + Returns: + Square_Context: context manager + + Raises: + ValueError: configuration error + """ + if frequency_Hz and period_s: + raise ValueError(error_ambiguous_wave) + if not frequency_Hz and not period_s: + frequency_Hz = 1000 + return Square_Context(self, frequency_Hz, repetitions, period_s, + duty_cycle_percent, kind, inverted, span_V, + offset_V, delay_s, slew_V_s) + + def sine_wave(self, frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, repetitions: int = -1, + inverted: bool = False, span_V: float = 0.2, + offset_V: float = 0.0, delay_s: float = 0, + slew_V_s: Optional[float] = None + ) -> Sine_Context: + """Set up a sine-wave generator + + Args: + frequency_Hz (float, optional): Frequency + period_s (float, optional): Period length (instead of frequency) + repetitions (int, optional): Number of repetition (default infinite) + inverted (bool, optional): True means flipped (default False) + span_V (float, optional): Voltage span (default 200mV) + offset_V (float, optional): Offset (default 0V) + delay_s (float, optional): Seconds of delay after receiving a trigger (default 0) + slew_V_s (None, optional): Max slew rate in V/s (default None) + + Returns: + Sine_Context: context manager + + Raises: + ValueError: configuration error + """ + if frequency_Hz and period_s: + raise ValueError(error_ambiguous_wave) + if not frequency_Hz and not period_s: + frequency_Hz = 1000 + return Sine_Context(self, frequency_Hz, repetitions, period_s, + inverted, span_V, offset_V, delay_s, slew_V_s) + + def triangle_wave(self, frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, repetitions: int = -1, + duty_cycle_percent: float = 50.0, inverted: bool = False, + span_V: float = 0.2, offset_V: float = 0.0, + delay_s: float = 0, slew_V_s: Optional[float] = None + ) -> Triangle_Context: + """Set up a triangle-wave generator + + Args: + frequency_Hz (float, optional): Frequency + period_s (float, optional): Period length (instead of frequency) + repetitions (int, optional): Number of repetition (default infinite) + duty_cycle_percent (float, optional): Percentage on-time (default 50%) + inverted (bool, optional): True means flipped (default False) + span_V (float, optional): Voltage span (default 200mV) + offset_V (float, optional): Offset (default 0V) + delay_s (float, optional): Seconds of delay after receiving a trigger (default 0) + slew_V_s (float, optional): Max slew rate in V/s (default None) + + Returns: + Triangle_Context: context manager + + Raises: + ValueError: configuration error + """ + if frequency_Hz and period_s: + raise ValueError(error_ambiguous_wave) + if not frequency_Hz and not period_s: + frequency_Hz = 1000 + return Triangle_Context(self, frequency_Hz, repetitions, period_s, + duty_cycle_percent, inverted, span_V, + offset_V, delay_s, slew_V_s) + + def arbitrary_wave(self, trace_name: str, repetitions: int = 1, + scale: float = 1.0, offset_V: float = 0.0, + slew_V_s: Optional[float] = None + ) -> Awg_Context: + """Set up an arbitrary-wave generator + + Args: + trace_name (str): Use data from this named trace + repetitions (int, optional): Number of repetition (default 1) + scale (float, optional): Scaling factor of voltages (default 1) + offset_V (float, optional): Offset (default 0V) + slew_V_s (None, optional): Max slew rate in V/s (default None) + + Returns: + Awg_Context: context manager + """ + return Awg_Context(self, trace_name, repetitions, scale, offset_V, + slew_V_s) + + def _set_fixed_voltage_immediately(self, v) -> None: + self.write(f'sour{self._channum}:volt:mode fix') + self.write(f'sour{self._channum}:volt {v}') + + def ask_channel(self, cmd: str) -> str: + """Inject channel number into SCPI query + + Arguments: + cmd (str): Must contain a '{0}' placeholder for the channel number + + Returns: + str: SCPI answer + """ + return self.ask(self._channel_message(cmd)) + + def write_channel(self, cmd: str) -> None: + """Inject channel number into SCPI command + + Arguments: + cmd (str): Must contain a '{0}' placeholder for the channel number + """ + self.write(self._channel_message(cmd)) + + def write_channel_floats(self, cmd: str, values: Sequence[float]) -> None: + """Inject channel number and a list of values into SCPI command + + The values are appended to the end of the command. + + Arguments: + cmd (str): Must contain a '{0}' placeholder for channel number + values (Sequence[float]): Sequence of numbers + """ + self._parent.write_floats(self._channel_message(cmd), values) + + def write(self, cmd: str) -> None: + """Send a SCPI command + + Args: + cmd (str): SCPI command + """ + self._parent.write(cmd) + + def _channel_message(self, template: str): + return template.format(self._channum) + + +class Trace_Context: + + def __init__(self, parent, name: str, size: int): + self._parent = parent + self._size = size + self._name = name + self._parent.write(f'trac:def "{name}",{size}') + + def __len__(self): + return self.size + + @property + def size(self) -> int: + """Number of values in trace""" + return self._size + + @property + def name(self) -> str: + """Name of trace""" + return self._name + + def waveform(self, values: Sequence[float]) -> None: + """Fill values into trace + + Args: + values (Sequence[float]): Sequence of values + + Raises: + ValueError: size mismatch + """ + if len(values) != self.size: + raise ValueError(f'trace length {len(values)} does not match ' + f'allocated length {self.size}') + self._parent.write_floats(f'trac:data "{self.name}",', values) + + +class Virtual_Sweep_Context: + + def __init__(self, arrangement: 'Arrangement_Context', sweep: np.ndarray, + start_trigger: Optional[str], step_time_s: float, + step_trigger: Optional[str], repetitions: Optional[int]): + self._arrangement = arrangement + self._sweep = sweep + self._step_trigger = step_trigger + self._step_time_s = step_time_s + self._repetitions = repetitions + self._allocate_triggers(start_trigger) + self._qdac_ready = False + + def __enter__(self): + self._ensure_qdac_setup() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Let Arrangement take care of freeing triggers + return False + + def actual_values_V(self, contact: str) -> np.ndarray: + """The corrected values that would actually be sent to the contact + + Args: + contact (str): Name of contact + + Returns: + np.ndarray: Corrected voltages + """ + index = self._arrangement._contact_index(contact) + return self._sweep[:, index] + + def start(self) -> None: + """Start the 2D sweep + """ + self._ensure_qdac_setup() + trigger = self._arrangement.get_trigger_by_name(self._start_trigger_name) + self._arrangement._qdac.trigger(trigger) + + def _allocate_triggers(self, start_sweep: Optional[str]) -> None: + if not start_sweep: + # Use a random, unique name + start_sweep = uuid.uuid4().hex + self._arrangement._allocate_internal_triggers([start_sweep]) + self._start_trigger_name = start_sweep + + def _ensure_qdac_setup(self) -> None: + if self._qdac_ready: + return self._make_ready_to_start() + self._route_inner_trigger() + self._send_lists_to_qdac() + self._qdac_ready = True + + def _route_inner_trigger(self) -> None: + if not self._step_trigger: + return + trigger = self._arrangement.get_trigger_by_name(self._step_trigger) + # All channels change in sync, so just use the first channel to make the + # external trigger. + channel = self._get_channel(0) + channel.write_channel(f'sour{"{0}"}:dc:mark:sst ' + f'{_trigger_context_to_value(trigger)}') + + def _get_channel(self, contact_index: int) -> 'QDac2Channel': + channel_number = self._arrangement._channels[contact_index] + qdac = self._arrangement._qdac + return qdac.channel(channel_number) + + def _send_lists_to_qdac(self) -> None: + for contact_index in range(self._arrangement.shape): + self._send_list_to_qdac(contact_index, self._sweep[:, contact_index]) + + def _send_list_to_qdac(self, contact_index, voltages): + channel = self._get_channel(contact_index) + dc_list = channel.dc_list(voltages=voltages, dwell_s=self._step_time_s, + repetitions=self._repetitions) + trigger = self._arrangement.get_trigger_by_name(self._start_trigger_name) + dc_list.start_on(trigger) + + def _make_ready_to_start(self): # Bug circumvention + for contact_index in range(self._arrangement.shape): + channel = self._get_channel(contact_index) + channel.write_channel('sour{0}:dc:init') + + +class Arrangement_Context: + def __init__(self, qdac: 'QDac2', contacts: Dict[str, int], + output_triggers: Optional[Dict[str, int]], + internal_triggers: Optional[Sequence[str]]): + self._qdac = qdac + self._fix_contact_order(contacts) + self._allocate_triggers(internal_triggers, output_triggers) + self._correction = np.identity(self.shape) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._free_triggers() + return False + + @property + def shape(self) -> int: + """Number of contacts in the arrangement""" + return len(self._contacts) + + @property + def correction_matrix(self) -> np.ndarray: + """Correction matrix""" + return self._correction + + @property + def contact_names(self) -> Sequence[str]: + """ + Returns: + Sequence[str]: Contact names in the same order as channel_numbers + """ + return self._contact_names + + def _allocate_internal_triggers(self, + internal_triggers: Optional[Sequence[str]] + ) -> None: + if not internal_triggers: + return + for name in internal_triggers: + self._internal_triggers[name] = self._qdac.allocate_trigger() + + def initiate_correction(self, contact: str, factors: Sequence[float]) -> None: + """Override how much a particular contact influences the other contacts + + Args: + contact (str): Name of contact + factors (Sequence[float]): factors between -1.0 and 1.0 + """ + index = self._contact_index(contact) + self._correction[index] = factors + + def set_virtual_voltage(self, contact: str, voltage: float) -> None: + """Set virtual voltage on specific contact + + The actual voltage that the contact will receive depends on the + correction matrix. + + Args: + contact (str): Name of contact + voltage (float): Voltage corresponding to no correction + """ + try: + index = self._contact_index(contact) + except KeyError: + raise ValueError(f'No contact named "{contact}"') + self._effectuate_virtual_voltage(index, voltage) + + def set_virtual_voltages(self, contacts_to_voltages: Dict[str, float]) -> None: + """Set virtual voltages on specific contacts in one go + + The actual voltage that each contact will receive depends on the + correction matrix. + + Args: + contact_to_voltages (Dict[str,float]): contact to voltage map + """ + for contact, voltage in contacts_to_voltages.items(): + try: + index = self._contact_index(contact) + except KeyError: + raise ValueError(f'No contact named "{contact}"') + self._virtual_voltages[index] = voltage + self._effectuate_virtual_voltages() + + def _effectuate_virtual_voltage(self, index: int, voltage: float) -> None: + self._virtual_voltages[index] = voltage + self._effectuate_virtual_voltages() + + def _effectuate_virtual_voltages(self) -> None: + for index, channel_number in enumerate(self._channels): + actual_V = self.actual_voltages()[index] + self._qdac.channel(channel_number).dc_constant_V(actual_V) + + def add_correction(self, contact: str, factors: Sequence[float]) -> None: + """Update how much a particular contact influences the other contacts + + This is mostly useful in arrangements where each contact has significant + effect only on nearby contacts, and thus can be added incrementally. + + The factors are extended by the identity matrix and multiplied to the + correction matrix. + + Args: + contact (str): Name of contact + factors (Sequence[float]): factors usually between -1.0 and 1.0 + """ + index = self._contact_index(contact) + multiplier = np.identity(self.shape) + multiplier[index] = factors + self._correction = np.matmul(multiplier, self._correction) + + def _fix_contact_order(self, contacts: Dict[str, int]) -> None: + self._contact_names = list() + self._contacts = dict() + self._channels = list() + index = 0 + for contact, channel in contacts.items(): + self._contact_names.append(contact) + self._contacts[contact] = index + index += 1 + self._channels.append(channel) + self._virtual_voltages = np.zeros(self.shape) + + @property + def channel_numbers(self) -> Sequence[int]: + """ + Returns: + Sequence[int]: Channels numbers in the same order as contact_names + """ + return self._channels + + def channel(self, name: str) -> QDac2Channel: + return self._qdac.channel(self._channels[self._contacts[name]]) + + def virtual_voltage(self, contact: str) -> float: + """ + Args: + contact (str): Name of contact + + Returns: + float: Voltage before correction + """ + index = self._contact_index(contact) + return self._virtual_voltages[index] + + def actual_voltages(self) -> Sequence[float]: + """ + Returns: + Sequence[float]: Corrected voltages for all contacts + """ + vs = np.matmul(self._correction, self._virtual_voltages) + if self._qdac._round_off: + vs = np.round(vs, self._qdac._round_off) + return list(vs) + + def get_trigger_by_name(self, name: str) -> QDac2Trigger_Context: + """ + Args: + name (str): Name of trigger + + Returns: + QDac2Trigger_Context: Trigger context manager + """ + try: + return self._internal_triggers[name] + except KeyError: + print(f'Internal triggers: {list(self._internal_triggers.keys())}') + raise + + def _all_channels_as_suffix(self) -> str: + channels_str = ints_to_comma_separated_list(self.channel_numbers) + return f'(@{channels_str})' + + def currents_A(self, nplc: int = 1, current_range: str = "low") -> Sequence[float]: + """Measure currents on all contacts + + Args: + nplc (int, optional): Number of powerline cycles to average over + current_range (str, optional): Current range (default low) + """ + channels_suffix = self._all_channels_as_suffix() + self._qdac.write(f'sens:rang {current_range},{channels_suffix}') + self._qdac.write(f'sens:nplc {nplc},{channels_suffix}') + # Discard first reading because of possible output-capacitor effects, etc + slowest_line_freq_Hz = 50 + sleep_s(1 / slowest_line_freq_Hz) + self._qdac.ask(f'read? {channels_suffix}') + # Then make a proper reading + sleep_s((nplc + 1) / slowest_line_freq_Hz) + currents = self._qdac.ask(f'read? {channels_suffix}') + return comma_sequence_to_list_of_floats(currents) + + def virtual_sweep(self, contact: str, voltages: Sequence[float], + start_sweep_trigger: Optional[str] = None, + step_time_s: float = 1e-5, + step_trigger: Optional[str] = None, + repetitions: int = 1) -> Virtual_Sweep_Context: + """Sweep a contact to create a 1D sweep + + Args: + contact (str): Name of sweeping contact + voltages (Sequence[float]): Virtual sweep voltages + outer_contact (str): Name of slow-changing (outer) contact + start_sweep_trigger (None, optional): Trigger that starts sweep + step_time_s (float, optional): Delay between voltage changes + step_trigger (None, optional): Trigger that marks each step + repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite + + Returns: + Virtual_Sweep_Context: context manager + """ + sweep = self._calculate_1d_values(contact, voltages) + return Virtual_Sweep_Context(self, sweep, start_sweep_trigger, + step_time_s, step_trigger, repetitions) + + def _calculate_1d_values(self, contact: str, voltages: Sequence[float] + ) -> np.ndarray: + original_voltage = self.virtual_voltage(contact) + index = self._contact_index(contact) + sweep = list() + for v in voltages: + self._virtual_voltages[index] = v + sweep.append(self.actual_voltages()) + self._virtual_voltages[index] = original_voltage + return np.array(sweep) + + def virtual_sweep2d(self, inner_contact: str, inner_voltages: Sequence[float], + outer_contact: str, outer_voltages: Sequence[float], + start_sweep_trigger: Optional[str] = None, + inner_step_time_s: float = 1e-5, + inner_step_trigger: Optional[str] = None, + repetitions: int = 1) -> Virtual_Sweep_Context: + """Sweep two contacts to create a 2D sweep + + Args: + inner_contact (str): Name of fast-changing (inner) contact + inner_voltages (Sequence[float]): Inner contact virtual voltages + outer_contact (str): Name of slow-changing (outer) contact + outer_voltages (Sequence[float]): Outer contact virtual voltages + start_sweep_trigger (None, optional): Trigger that starts sweep + inner_step_time_s (float, optional): Delay between voltage changes + inner_step_trigger (None, optional): Trigger that marks each step + repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite + + Returns: + Virtual_Sweep_Context: context manager + """ + sweep = self._calculate_2d_values(inner_contact, inner_voltages, + outer_contact, outer_voltages) + return Virtual_Sweep_Context(self, sweep, start_sweep_trigger, + inner_step_time_s, inner_step_trigger, repetitions) + + def _calculate_2d_values(self, inner_contact: str, + inner_voltages: Sequence[float], + outer_contact: str, + outer_voltages: Sequence[float]) -> np.ndarray: + original_fast_voltage = self.virtual_voltage(inner_contact) + original_slow_voltage = self.virtual_voltage(outer_contact) + outer_index = self._contact_index(outer_contact) + inner_index = self._contact_index(inner_contact) + sweep = list() + for slow_V in outer_voltages: + self._virtual_voltages[outer_index] = slow_V + for fast_V in inner_voltages: + self._virtual_voltages[inner_index] = fast_V + sweep.append(self.actual_voltages()) + self._virtual_voltages[inner_index] = original_fast_voltage + self._virtual_voltages[outer_index] = original_slow_voltage + return np.array(sweep) + + def virtual_detune(self, contacts: Sequence[str], start_V: Sequence[float], + end_V: Sequence[float], steps: int, + start_trigger: Optional[str] = None, + step_time_s: float = 1e-5, + step_trigger: Optional[str] = None, + repetitions: int = 1) -> Virtual_Sweep_Context: + """Sweep any number of contacts linearly from one set of values to another set of values + + Args: + contacts (Sequence[str]): contacts involved in sweep + start_V (Sequence[float]): First-extreme values + end_V (Sequence[float]): Second-extreme values + steps (int): Number of steps between extremes + start_trigger (None, optional): Trigger that starts sweep + step_time_s (float, Optional): Seconds between each step + step_trigger (None, optional): Trigger that marks each step + repetitions (int, Optional): Number of back-and-forth sweeps, or -1 for infinite + """ + self._check_same_lengths(contacts, start_V, end_V) + sweep = self._calculate_detune_values(contacts, start_V, end_V, steps) + return Virtual_Sweep_Context(self, sweep, start_trigger, step_time_s, + step_trigger, repetitions) + + @staticmethod + def _check_same_lengths(contacts, start_V, end_V) -> None: + n_contacts = len(contacts) + if n_contacts != len(start_V): + raise ValueError(f'There must be exactly one voltage per contact: {start_V}') + if n_contacts != len(end_V): + raise ValueError(f'There must be exactly one voltage per contact: {end_V}') + + def _calculate_detune_values(self, contacts: Sequence[str], start_V: Sequence[float], + end_V: Sequence[float], steps: int): + original_voltages = [self.virtual_voltage(contact) for contact in contacts] + indices = [self._contact_index(contact) for contact in contacts] + sweep = list() + forward_V = [forward_and_back(start_V[i], end_V[i], steps) for i in range(len(contacts))] + for voltages in zip(*forward_V): + for index, voltage in zip(indices, voltages): + self._virtual_voltages[index] = voltage + sweep.append(self.actual_voltages()) + for index, voltage in zip(indices, original_voltages): + self._virtual_voltages[index] = voltage + return np.array(sweep) + + def leakage(self, modulation_V: float, nplc: int = 2) -> np.ndarray: + """Run a simple leakage test between the contacts + + Each contact is changed in turn and the resulting change in current from + steady-state is recorded. The resulting resistance matrix is calculated + as modulation_voltage divided by current_change. + + Args: + modulation_V (float): Virtual voltage added to each contact + nplc (int, Optional): Powerline cycles to wait for each measurement + + Returns: + ndarray: contact-to-contact resistance in Ohms + """ + steady_state_A, currents_matrix = self._leakage_currents(modulation_V, nplc, 'low') + with np.errstate(divide='ignore'): + return np.abs(modulation_V / diff_matrix(steady_state_A, currents_matrix)) + + def _leakage_currents(self, modulation_V: float, nplc: int, + current_range: str + ) -> Tuple[Sequence[float], Sequence[Sequence[float]]]: + steady_state_A = self.currents_A(nplc, 'low') + currents_matrix = list() + for index, channel_nr in enumerate(self.channel_numbers): + original_V = self._virtual_voltages[index] + self._effectuate_virtual_voltage(index, original_V + modulation_V) + currents = self.currents_A(nplc, current_range) + self._effectuate_virtual_voltage(index, original_V) + currents_matrix.append(currents) + return steady_state_A, currents_matrix + + def _contact_index(self, contact: str) -> int: + return self._contacts[contact] + + def _allocate_triggers(self, internal_triggers: Optional[Sequence[str]], + output_triggers: Optional[Dict[str, int]] + ) -> None: + self._internal_triggers: Dict[str, QDac2Trigger_Context] = dict() + self._allocate_internal_triggers(internal_triggers) + self._allocate_external_triggers(output_triggers) + + def _allocate_external_triggers(self, output_triggers: + Optional[Dict[str, int]] + ) -> None: + self._external_triggers = dict() + if not output_triggers: + return + for name, port in output_triggers.items(): + self._external_triggers[name] = port + trigger = self._qdac.allocate_trigger() + self._qdac.connect_external_trigger(port, trigger) + self._internal_triggers[name] = trigger + + def _free_triggers(self) -> None: + for trigger in self._internal_triggers.values(): + self._qdac.free_trigger(trigger) + + +def forward_and_back(start: float, end: float, steps: int): + forward = np.linspace(start, end, steps) + backward = np.flip(forward)[1:][:-1] + back_and_forth = itertools.chain(forward, backward) + return back_and_forth + + +class QDac2(VisaInstrument): + + def __init__(self, name: str, address: str, **kwargs) -> None: + """Connect to a QDAC-II + + Args: + name (str): Name for instrument + address (str): Visa identification string + **kwargs: additional argument to the Visa driver + """ + self._check_instrument_name(name) + super().__init__(name, address, terminator='\n', **kwargs) + self._set_up_serial() + self._set_up_debug_settings() + self._set_up_channels() + self._set_up_external_triggers() + self._set_up_internal_triggers() + self._set_up_simple_functions() + self.connect_message() + self._check_for_wrong_model() + self._check_for_incompatiable_firmware() + self._set_up_manual_triggers() + + def n_channels(self) -> int: + """ + Returns: + int: Number of channels + """ + return len(self.submodules['channels']) + + def channel(self, ch: int) -> QDac2Channel: + """ + Args: + ch (int): Channel number + + Returns: + QDac2Channel: Visa representation of the channel + """ + return getattr(self, f'ch{ch:02}') + + @staticmethod + def n_triggers() -> int: + """ + Returns: + int: Number of internal triggers + """ + return 14 + + @staticmethod + def n_external_inputs() -> int: + """ + Returns: + int: Number of external input triggers + """ + return 4 + + def n_external_outputs(self) -> int: + """ + Returns: + int: Number of external output triggers + """ + return len(self.submodules['external_triggers']) + + def allocate_trigger(self) -> QDac2Trigger_Context: + """Allocate an internal trigger + + Does not have any effect on the instrument, only the driver. + + Returns: + QDac2Trigger_Context: Context manager + + Raises: + ValueError: no free triggers + """ + try: + number = self._internal_triggers.pop() + except KeyError: + raise ValueError('no free internal triggers') + return QDac2Trigger_Context(self, number) + + def free_trigger(self, trigger: QDac2Trigger_Context) -> None: + """Free an internal trigger + + Does not have any effect on the instrument, only the driver. + + Args: + trigger (QDac2Trigger_Context): trigger to free + """ + internal = _trigger_context_to_value(trigger) + self._internal_triggers.add(internal) + + def free_all_triggers(self) -> None: + """Free all an internal triggers + + Does not have any effect on the instrument, only the driver. + """ + self._set_up_internal_triggers() + + def connect_external_trigger(self, port: int, trigger: QDac2Trigger_Context, + width_s: float = 1e-6 + ) -> None: + """Route internal trigger to external trigger + + Args: + port (int): External output trigger number + trigger (QDac2Trigger_Context): Internal trigger + width_s (float, optional): Output trigger width in seconds (default 1ms) + """ + internal = _trigger_context_to_value(trigger) + self.write(f'outp:trig{port}:sour int{internal}') + self.write(f'outp:trig{port}:widt {width_s}') + + def reset(self) -> None: + self.write('*rst') + sleep_s(5) + + def errors(self) -> str: + """Retrieve and clear all previous errors + + Returns: + str: Comma separated list of errors or '0, "No error"' + """ + return self.ask('syst:err:all?') + + def error(self) -> str: + """Retrieve next error + + Returns: + str: The next error or '0, "No error"' + """ + return self.ask('syst:err?') + + def n_errors(self) -> int: + """Peek at number of previous errors + + Returns: + int: Number of errors + """ + return int(self.ask('syst:err:coun?')) + + def start_all(self) -> None: + """Trigger the global SCPI bus (``*TRG``) + + All generators, that have not been explicitly set to trigger on an + internal or external trigger, will be started. + """ + self.write('*trg') + + def remove_traces(self) -> None: + """Delete all trace definitions from the instrument + + This means that all AWGs loose their data. + """ + self.write('trac:rem:all') + + def traces(self) -> Sequence[str]: + """List all defined traces + + Returns: + Sequence[str]: trace names + """ + return comma_sequence_to_list(self.ask('trac:cat?')) + + def allocate_trace(self, name: str, size: int) -> Trace_Context: + """Reserve memory for a new trace + + Args: + name (str): Name of new trace + size (int): Number of voltage values in the trace + + Returns: + Trace_Context: context manager + """ + return Trace_Context(self, name, size) + + def mac(self) -> str: + """ + Returns: + str: Media Access Control (MAC) address of the instrument + """ + mac = self.ask('syst:comm:lan:mac?') + return f'{mac[1:3]}-{mac[3:5]}-{mac[5:7]}-{mac[7:9]}-{mac[9:11]}' \ + f'-{mac[11:13]}' + + def arrange(self, contacts: Dict[str, int], + output_triggers: Optional[Dict[str, int]] = None, + internal_triggers: Optional[Sequence[str]] = None + ) -> Arrangement_Context: + """An arrangement of contacts and triggers for virtual gates + + Each contact corresponds to a particular output channel. Each + output_trigger corresponds to a particular external output trigger. + Each internal_trigger will be allocated from the pool of internal + triggers, and can later be used for synchronisation. After + initialisation of the arrangement, contacts and triggers can only be + referred to by name. + + The voltages that will appear on each contact depends not only on the + specified virtual voltage, but also on a correction matrix. Initially, + the contacts are assumed to not influence each other, which means that + the correction matrix is the identity matrix, ie. the row for + each contact has a value of [0, ..., 0, 1, 0, ..., 0]. + + Args: + contacts (Dict[str, int]): Name/channel pairs + output_triggers (Sequence[Tuple[str,int]], optional): Name/number pairs of output triggers + internal_triggers (Sequence[str], optional): List of names of internal triggers to allocate + + Returns: + Arrangement_Context: context manager + """ + return Arrangement_Context(self, contacts, output_triggers, + internal_triggers) + + # ----------------------------------------------------------------------- + # Instrument-wide functions + # ----------------------------------------------------------------------- + + # ----------------------------------------------------------------------- + # Debugging and testing + + def start_recording_scpi(self) -> None: + """Record all SCPI commands sent to the instrument + + Any previous recordings are removed. To inspect the SCPI commands sent + to the instrument, call get_recorded_scpi_commands(). + """ + self._scpi_sent: List[str] = list() + self._record_commands = True + + def get_recorded_scpi_commands(self) -> List[str]: + """ + Returns: + Sequence[str]: SCPI commands sent to the instrument + """ + commands = self._scpi_sent + self._scpi_sent = list() + return commands + + def clear(self) -> None: + """Reset the VISA message queue of the instrument + """ + self.visa_handle.clear() + + def clear_read_queue(self) -> Sequence[str]: + """Flush the VISA message queue of the instrument + + Takes at least _message_flush_timeout_ms to carry out. + + Returns: + Sequence[str]: Messages lingering in queue + """ + lingering = list() + original_timeout = self.visa_handle.timeout + self.visa_handle.timeout = self._message_flush_timeout_ms + while True: + try: + message = self.visa_handle.read() + except VisaIOError: + break + else: + lingering.append(message) + self.visa_handle.timeout = original_timeout + return lingering + + # ----------------------------------------------------------------------- + # Override communication methods to make it possible to record the + # communication with the instrument. + + def write(self, cmd: str) -> None: + """Send SCPI command to instrument + + Args: + cmd (str): SCPI command + """ + if self._record_commands: + self._scpi_sent.append(cmd) + super().write(cmd) + + def ask(self, cmd: str) -> str: + """Send SCPI query to instrument + + Args: + cmd (str): SCPI query + + Returns: + str: SCPI answer + """ + if self._record_commands: + self._scpi_sent.append(cmd) + answer = super().ask(cmd) + return answer + + def write_floats(self, cmd: str, values: Sequence[float]) -> None: + """Append a list of values to a SCPI command + + By default, the values are IEEE binary encoded. + + Remember to include separating space in command if needed. + """ + if self._no_binary_values: + compiled = f'{cmd}{floats_to_comma_separated_list(values)}' + if self._record_commands: + self._scpi_sent.append(compiled) + return super().write(compiled) + if self._record_commands: + self._scpi_sent.append(f'{cmd}{floats_to_comma_separated_list(values)}') + self.visa_handle.write_binary_values(cmd, values) + + # ----------------------------------------------------------------------- + + def _set_up_debug_settings(self) -> None: + self._record_commands = False + self._scpi_sent = list() + self._message_flush_timeout_ms = 1 + self._round_off = None + self._no_binary_values = False + + def _set_up_serial(self) -> None: + # No harm in setting the speed even if the connection is not serial. + self.visa_handle.baud_rate = 921600 # type: ignore + + def _check_for_wrong_model(self) -> None: + model = self.IDN()['model'] + if model != 'QDAC-II': + raise ValueError(f'Unknown model {model}. Are you using the right' + ' driver for your instrument?') + + def _check_for_incompatiable_firmware(self) -> None: + # Only compare the firmware, not the FPGA version + firmware = split_version_string_into_components(self.IDN()['firmware'])[1] + least_compatible_fw = '0.17.5' + if parse(firmware) < parse(least_compatible_fw): + raise ValueError(f'Incompatible firmware {firmware}. You need at ' + f'least {least_compatible_fw}') + + def _set_up_channels(self) -> None: + channels = ChannelList(self, 'Channels', QDac2Channel, + snapshotable=False) + for i in range(1, 24 + 1): + name = f'ch{i:02}' + channel = QDac2Channel(self, name, i) + self.add_submodule(name, channel) + channels.append(channel) + channels.lock() + self.add_submodule('channels', channels) + + def _set_up_external_triggers(self) -> None: + triggers = ChannelList(self, 'Channels', QDac2ExternalTrigger, + snapshotable=False) + for i in range(1, 5 + 1): + name = f'ext{i}' + trigger = QDac2ExternalTrigger(self, name, i) + self.add_submodule(name, trigger) + triggers.append(trigger) + triggers.lock() + self.add_submodule('external_triggers', triggers) + + def _set_up_internal_triggers(self) -> None: + # A set of the available internal triggers + self._internal_triggers = set(range(1, self.n_triggers() + 1)) + + def _set_up_manual_triggers(self) -> None: + self.add_parameter( + name='trigger', + # Manually trigger event + set_parser=_trigger_context_to_value, + set_cmd='tint {}', + ) + + def _set_up_simple_functions(self) -> None: + self.add_function('abort', call_cmd='abor') + + def _check_instrument_name(self, name: str) -> None: + if name.isidentifier(): + return + raise ValueError( + f'Instrument name "{name}" is incompatible with QCoDeS parameter ' + 'generation (no spaces, punctuation, prepended numbers, etc)') diff --git a/src/qumada/instrument/custom_drivers/ZI/MFLI.py b/src/qumada/instrument/custom_drivers/ZI/MFLI.py index afb371f..ea5b515 100644 --- a/src/qumada/instrument/custom_drivers/ZI/MFLI.py +++ b/src/qumada/instrument/custom_drivers/ZI/MFLI.py @@ -182,6 +182,14 @@ def __init__( set_cmd=lambda x: self.instr.sigouts[0].range(x), docstring="Range of the output", ) + + self.add_parameter( + name = "output_enabled", + label = "Output Enabled", + get_cmd = lambda: self.instr.sigouts[0].on(), + set_cmd = lambda x: self.instr.sigouts[0].on(x), + docstring = "Turns Output1 on or off" + ) self.add_parameter( name="demod0_time_constant", @@ -227,3 +235,10 @@ def __init__( set_cmd=None, docstring="Gets/Sets the order of the demod 0 filter.", ) + + def get_idn(self): + #TODO: Implement this function! + # /dev..../features/devtype + # /dev..../features/serial + # /dev..../features/options + return {} \ No newline at end of file diff --git a/src/qumada/instrument/mapping/QDevil/qdac2.py b/src/qumada/instrument/mapping/QDevil/qdac2.py index 5c70834..4dfc8e8 100644 --- a/src/qumada/instrument/mapping/QDevil/qdac2.py +++ b/src/qumada/instrument/mapping/QDevil/qdac2.py @@ -22,7 +22,7 @@ import logging from qcodes.parameters import Parameter -from qcodes_contrib_drivers.drivers.QDevil.QDAC2 import QDac2 +from qumada.instrument.custom_drivers.QDevil.QDAC2 import QDac2 from qumada.instrument.mapping import QDAC2_MAPPING from qumada.instrument.mapping.base import InstrumentMapping @@ -156,15 +156,16 @@ def pulse( if delay < 1e-6: raise Exception("Delay for QDacII pulse is to small (<1 us)") channels = [param._instrument for param in parameters] - for channel, points in zip(channels, setpoints): - dc_list = channel.dc_list( + self.dc_lists = [ + channel.dc_list( voltages=points, dwell_s=delay, - ) + ) for channel, points in zip(channels, setpoints) + ] if sync_trigger is not None: if sync_trigger in range(1, 6): - trigger = dc_list.start_marker() + trigger = self.dc_lists[0].start_marker() qdac.external_triggers[sync_trigger - 1].width_s(trigger_width) qdac.external_triggers[sync_trigger - 1].polarity(trigger_polarity) qdac.external_triggers[sync_trigger - 1].source_from_trigger(trigger) @@ -180,3 +181,22 @@ def setup_trigger_in(): "QDac2 does not have a trigger input \ not yet supported!" ) + + def clean_generators(self): + for dc_list in self.dc_lists: + dc_list.abort() + self.dc_lists = [] + + @staticmethod + def query_instrument(parameters: list[Parameter]): + """ Check if all parameters are from the same instrument """ + instruments = {parameter.root_instrument for parameter in parameters} + if len(instruments) > 1: + raise Exception( + "Parameters are from more than one instrument. \ + This would lead to non synchronized ramps." + ) + qdac: QDac2 = instruments.pop() + assert isinstance(qdac, QDac2) + return qdac + \ No newline at end of file diff --git a/src/qumada/instrument/mapping/base.py b/src/qumada/instrument/mapping/base.py index 1eb0216..6d299fc 100644 --- a/src/qumada/instrument/mapping/base.py +++ b/src/qumada/instrument/mapping/base.py @@ -183,6 +183,7 @@ def add_mapping_to_instrument( except TypeError: pass instrument._is_triggerable = mapping._is_triggerable + mapping._instrument = instrument instrument._qumada_mapping = mapping try: instrument._qumada_trigger = mapping.trigger diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 38401b6..43b8f2d 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -17,11 +17,14 @@ from qumada.measurement.scripts import ( Generic_1D_Sweep, Generic_1D_Sweep_buffered, + Generic_1D_parallel_asymm_Sweep, Generic_1D_Hysteresis_buffered, Generic_2D_Sweep_buffered, Generic_nD_Sweep, Timetrace, Timetrace_buffered, + Generic_Pulsed_Measurement, + Generic_Pulsed_Repeated_Measurement, ) from qumada.utils.ramp_parameter import ramp_or_set_parameter @@ -202,6 +205,8 @@ def save_to_dict(self, priorize_stored_value=False): "break_conditions", "limits", "group", + "leverarms", + "compensated_gates", ]: if hasattr(param, attr_name): return_dict[terminal.name][param.name][attr_name] = getattr(param, attr_name) @@ -365,7 +370,119 @@ def sweep_2D( del self.states["_temp_2D"] return data + def sweep_parallel(self, + params: list[Parameter], + setpoints: list[list[float]]|None = None, + target_values: list[float]|None = None, + num_points: int = 100, + name = None, + metadata = None, + station = None, + priorize_stored_value = False, + **kwargs + ): + """ + Sweep multiple parameters in parallel. + Provide either setpoints or target_values. Setpoints have to have the same length for all parameters. + If no setpoints are provided, the target_values will be used to create the setpoints. Ramps will start from + the current value of the parameters then. + Gettable parameters and break conditions will be set according to their state in the device object. + You can pass backsweep_after_break as a kwarg. If set to True, the sweep will continue in the opposite direction after + a break condition is reached. + """ + if station is None: + station = self.station + if not isinstance(station, Station): + raise TypeError("No valid station assigned!") + if setpoints is None and target_values is None: + raise(Exception("Either setpoints or target_values have to be provided!")) + if target_values is not None and setpoints is not None: + raise(Exception("Either setpoints or target_values have to be provided, not both!")) + if setpoints is None: + assert len(params) == len(target_values) + setpoints = [np.linspace(param(), target, num_points) for param, target in zip(params, target_values)] + assert len(params) == len(setpoints) + assert all([len(setpoint) == len(setpoints[0]) for setpoint in setpoints]) + + + for terminal in self.terminals.values(): + for parameter in terminal.terminal_parameters.values(): + if parameter not in params and parameter.type == "dynamic": + parameter.type = "static gettable" + if parameter in params: + parameter.type = "dynamic" + parameter.setpoints = setpoints[params.index(parameter)] + script = Generic_1D_parallel_asymm_Sweep() + script.setup( + self.save_to_dict(priorize_stored_value=priorize_stored_value), + metadata=metadata, + name=name, + **kwargs + ) + mapping = self.instrument_parameters + map_terminals_gui(station.components, script.gate_parameters, mapping) + data = script.run() + return data + + def pulsed_measurement(self, + params: list[Parameter], + setpoints: list[list[float]], + repetitions: int = 1, + name = None, + metadata = None, + station = None, + buffer_settings: dict|None = None, + priorize_stored_value = False, + **kwargs, + ): + if station is None: + station = self.station + if not isinstance(station, Station): + raise TypeError("No valid station assigned!") + assert len(params) == len(setpoints) + assert all([len(setpoint) == len(setpoints[0]) for setpoint in setpoints]) + assert repetitions >= 1 + if buffer_settings is None: + buffer_settings = self.buffer_settings + temp_buffer_settings = deepcopy(buffer_settings) + if "num_points" in temp_buffer_settings.keys(): + temp_buffer_settings["num_points"] = len(setpoints[0]) + logger.warning( + f"Temporarily changed buffer settings to match the \ + number of points specified in the setpoints" + ) + else: + raise Exception( + "For this kind of measurement, you have to specify the number of points in the buffer settings!" + ) + for terminal in self.terminals.values(): + for parameter in terminal.terminal_parameters.values(): + if parameter not in params and parameter.type == "dynamic": + parameter.type = "static gettable" + if parameter in params: + parameter.type = "dynamic" + parameter.setpoints = setpoints[params.index(parameter)] + if repetitions == 1: + script = Generic_Pulsed_Measurement() + elif repetitions > 1: + script = Generic_Pulsed_Repeated_Measurement() + script.setup( + self.save_to_dict(priorize_stored_value=priorize_stored_value), + metadata=metadata, + measurement_name=name, # achtung geändert! + repetitions=repetitions, + buffer_settings=temp_buffer_settings, + **self.buffer_script_setup, + **kwargs, + ) + mapping = self.instrument_parameters + map_terminals_gui(station.components, script.gate_parameters, mapping) + map_triggers(station.components, script.properties, script.gate_parameters) + data = script.run() + return data + + def create_hook(func, hook): """ Decorator to hook a function onto an existing function. @@ -499,6 +616,7 @@ def __init__(self, name: str, Terminal: Terminal, properties: dict = {}) -> None self._value = None self.name = name self._limits = self.properties.get("limits", None) + self.leverarms = self.properties.get("leverarms", None) self.rampable = False self.ramp_rate = self.properties.get("ramp_rate", 0.1) self.group = self.properties.get("group", None) @@ -521,6 +639,7 @@ def _apply_properties(self): self.delay = self.properties.get("delay", self.delay) self.ramp_rate = self.properties.get("ramp_rate", self.ramp_rate) self.group = self.properties.get("group", self.group) + self.leverarms = self.properties.get("leverarms", self.leverarms) @property def value(self): @@ -656,9 +775,8 @@ def measured_ramp( self.setpoints = np.linspace(start, value, num_points) else: self.setpoints = np.linspace(start, value, num_points) - if buffer_settings is None: - buffer_settings = self._parent_device.buffer_settings - temp_buffer_settings = deepcopy(buffer_settings) + temp_buffer_settings = deepcopy(self._parent_device.buffer_settings) + temp_buffer_settings.update(buffer_settings or {}) if buffered: if "num_points" in temp_buffer_settings.keys(): temp_buffer_settings["num_points"] = num_points @@ -735,7 +853,10 @@ def __call__(self, value=None, ramp=None): if value is None: return self.value else: - self.value = value + if ramp is True: + self.ramp(value) + else: + self.value = value # class Virtual_Terminal_Parameter(Terminal_Parameter): diff --git a/src/qumada/measurement/measurement.py b/src/qumada/measurement/measurement.py index d2df4b2..3158823 100644 --- a/src/qumada/measurement/measurement.py +++ b/src/qumada/measurement/measurement.py @@ -113,9 +113,9 @@ class MeasurementScript(ABC): def __init__(self): # Create function hooks for metadata # reverse order, so insert metadata is run second - self.run = create_hook(self.run, self._insert_metadata_into_db) - self.run = create_hook(self.run, self._add_data_to_metadata) - self.run = create_hook(self.run, self._add_current_datetime_to_metadata) + # self.run = create_hook(self.run, self._insert_metadata_into_db) + # self.run = create_hook(self.run, self._add_data_to_metadata) + # self.run = create_hook(self.run, self._add_current_datetime_to_metadata) self.properties: dict[Any, Any] = {} self.gate_parameters: dict[Any, dict[Any, Parameter | None] | Parameter | None] = {} @@ -630,6 +630,11 @@ def initialize(self, dyn_ramp_to_val=False, inactive_dyn_channels: list | None = # This iterates over all compensating parameters if self.properties[gate][parameter]["type"].find("comp") >= 0: try: + for i in range(len(self.compensating_parameters)): + if self.compensating_parameters[i]["gate"] == gate and self.compensating_parameters[i][ + "parameter" + ] == parameter: + break i = self.compensating_parameters.index({"gate": gate, "parameter": parameter}) leverarms = self.compensating_leverarms[i] comped_params = copy.deepcopy( @@ -646,7 +651,12 @@ def initialize(self, dyn_ramp_to_val=False, inactive_dyn_channels: list | None = else: # Get only the relevant list entries for the current parameter try: - comped_index = self.dynamic_parameters.index(comped_param) + for i in range(len(self.dynamic_parameters)): + if self.dynamic_parameters[i]["gate"] == comped_param["gate"] and self.dynamic_parameters[i][ + "parameter"] == comped_param["parameter"]: + comped_index = i + break + # comped_index = self.dynamic_parameters.index(comped_param) except ValueError as e: logger.exception( "Watch out, there is an Exception incoming!" diff --git a/src/qumada/measurement/shuttling_scripts.py b/src/qumada/measurement/shuttling_scripts.py new file mode 100644 index 0000000..e6a3db2 --- /dev/null +++ b/src/qumada/measurement/shuttling_scripts.py @@ -0,0 +1,233 @@ +import numpy as np + + +def shuttle_setpoints(offsets, amplitudes, num_periods, phases, sampling_rate, barrier_voltages, duration, reverse = False): + pulses=[] + def shuttling_sin(x, offset, amplitude, phase, reverse = False): + if reverse: + sign = -1 + else: + sign = 1 + return amplitude*np.sin(sign*x+phase)+offset + for i in range(0, 4): + x_datapoints = np.linspace(0, 2*np.pi*num_periods, int(sampling_rate*duration)) + pulses.append( + shuttling_sin(x_datapoints, offsets[i], amplitudes[i], phases[i], reverse = reverse) + ) + for i in range(5, 7): + pulses.append([barrier_voltages[i-5] for _ in range(int(sampling_rate*duration))]) + return pulses + +def ramping_setpoints(starts, stops, duration, sampling_rate): + setpoints = [] + for start, stop in zip(starts, stops): + setpoints.append(np.linspace(start, stop, int(duration*sampling_rate))) + return setpoints + +def concatenate_setpoints(setpoint_arrays): + + num_gates = len(setpoint_arrays[0]) + conc_array = [[] for _ in range(num_gates)] + for i in range(len(setpoint_arrays)): + for j in range(len(setpoint_arrays[0])): + for val in setpoint_arrays[i][j]: + conc_array[j].append(val) + + return conc_array + + +def Generic_Pulsed_Measurement_w_parameter(device, add_parameter, add_parameter_setpoints, settings): + """ + Measurement script for buffered measurements with abritary setpoints. + Trigger Types: + "software": Sends a software command to each buffer and dynamic parameters + in order to start data acquisition and ramping. Timing + might be off slightly + "hardware": Expects a trigger command for each setpoint. Can be used + with a preconfigured hardware trigger (Todo), a method, + that starts a manually adjusted hardware trigger + (has to be passed as trigger_start() method to + measurement script) or a manual trigger. + "manual" : The trigger setup is done by the user. The measurent script will + just start the first ramp. Usefull for synchronized trigger outputs + as in the QDac. + trigger_start: A callable that triggers the trigger (called to start the measurement) + or the keyword "manual" when triggering is done by user. Defauls is manual. + trigger_reset (optional): Callable to reset the trigger. Default is NONE. + include_gate_name (optional): Appends name of ramped gates to measurement name. Default is TRUE. + reset_time: Time for ramping fast param back to the start value. + TODO: Add Time! + """ + meas + with meas.run() as datasaver: + for setpoint in add_parameter_setpoints: + + script = Generic_Pulsed_Measurement() + script.setup() + + + def run(self): + self.buffered = True + TRIGGER_TYPES = ["software", "hardware", "manual"] + trigger_start = self.settings.get("trigger_start", "manual") # TODO: this should be set elsewhere + trigger_reset = self.settings.get("trigger_reset", None) + trigger_type = _validate_mapping( + self.settings.get("trigger_type"), + TRIGGER_TYPES, + default="software", + default_key_error="software", + ) + self.repetitions = self.settings.get("repetitions", 1) + self.add_parameter = self.settings.get("add_parameter") + self.add_parameter_setpoints = self.settings.get("add_parameter_setpoints") + include_gate_name = self.settings.get("include_gate_name", True) + sync_trigger = self.settings.get("sync_trigger", None) + datasets = [] + timer = ElapsedTimeParameter("time") + self.generate_lists() + self.measurement_name = naming_helper(self, default_name="nD Sweep") + if include_gate_name: + gate_names = [gate["gate"] for gate in self.dynamic_parameters] + self.measurement_name += f" {gate_names}" + + meas = Measurement(name=self.measurement_name) + meas.register_parameter(self.add_parameter) + meas.register_parameter(timer) + for parameter in self.dynamic_parameters: + self.properties[parameter["gate"]][parameter["parameter"]]["_is_triggered"] = True + for dynamic_param in self.dynamic_channels: + meas.register_parameter( + dynamic_param, + setpoints=[ + timer, + ], + ) + + # ------------------- + static_gettables = [] + del_channels = [] + del_params = [] + for parameter, channel in zip(self.gettable_parameters, self.gettable_channels): + if is_bufferable(channel): + meas.register_parameter( + channel, + setpoints=[ + timer, + ], + ) + elif channel in self.static_channels: + del_channels.append(channel) + del_params.append(parameter) + meas.register_parameter( + channel, + setpoints=[ + timer, + ], + ) + parameter_value = self.properties[parameter["gate"]][parameter["parameter"]]["value"] + static_gettables.append((channel, [parameter_value for _ in range(self.buffered_num_points)])) + for channel in del_channels: + self.gettable_channels.remove(channel) + for param in del_params: + self.gettable_parameters.remove(param) + # -------------------------- + + self.initialize() + for c_param in self.active_compensating_channels: + meas.register_parameter( + c_param, + setpoints=[ + timer, + ], + ) + + instruments = {param.root_instrument for param in self.dynamic_channels} + time_setpoints = np.linspace(0, self._burst_duration, int(self.buffered_num_points)) + setpoints = [sweep.get_setpoints() for sweep in self.dynamic_sweeps] + compensating_setpoints = [] + for i in range(len(self.active_compensating_channels)): + index = self.compensating_channels.index(self.active_compensating_channels[i]) + active_setpoints = sum([sweep.get_setpoints() for sweep in self.compensating_sweeps[i]]) + active_setpoints += float(self.compensating_parameters_values[index]) + compensating_setpoints.append(active_setpoints) + if min(active_setpoints) < min(self.compensating_limits[index]) or max(active_setpoints) > max( + self.compensating_limits[index] + ): + raise Exception(f"Setpoints of compensating gate {self.compensating_parameters[index]} exceed limits!") + results = [] + with meas.run() as datasaver: + for k in range(self.repetitions): + self.initialize() + try: + trigger_reset() + except TypeError: + logger.info("No method to reset the trigger defined.") + self.ready_buffers() + for instr in instruments: + try: + instr._qumada_pulse( + parameters=[*self.dynamic_channels, *self.active_compensating_channels], + setpoints=[*setpoints, *compensating_setpoints], + delay=self._burst_duration / self.buffered_num_points, + sync_trigger=sync_trigger, + ) + except AttributeError as ex: + logger.error( + f"Exception: {instr} probably does not have a \ + a qumada_pulse method. Buffered measurements without \ + ramp method are no longer supported. \ + Use the unbuffered script!" + ) + raise ex + + if trigger_type == "manual": + logger.warning( + "You are using manual triggering. If you want to pulse parameters on multiple" + "instruments this can lead to delays and bad timing!" + ) + + if trigger_type == "hardware": + try: + trigger_start() + except NameError as ex: + print("Please set a trigger or define a trigger_start method") + raise ex + + elif trigger_type == "software": + for buffer in self.buffers: + buffer.force_trigger() + logger.warning( + "You are using software trigger, which \ + can lead to significant delays between \ + measurement instruments! Only recommended\ + for debugging." + ) + + while not all(buffer.is_finished() for buffer in list(self.buffers)): + sleep(0.1) + try: + trigger_reset() + except TypeError: + logger.info("No method to reset the trigger defined.") + + results.append(self.readout_buffers()) + average_results = [] + for i in range(len(results[0])): + helper_array = np.zeros(len(results[0][0][1])) + for meas_results in results: + helper_array += meas_results[i][1] + helper_array /= self.repetitions + average_results.append((meas_results[i][0], helper_array)) + + datasaver.add_result( + (timer, time_setpoints), + *(zip(self.dynamic_channels, setpoints)), + *(zip(self.active_compensating_channels, compensating_setpoints)), + *average_results, + *static_gettables, + ) + datasets.append(datasaver.dataset) + self.clean_up() + return datasets + + diff --git a/src/tests/plotting_2D.py b/src/qumada/utils/plotting_2D.py similarity index 100% rename from src/tests/plotting_2D.py rename to src/qumada/utils/plotting_2D.py From 05309d861207aa439acb44f62052fc844d8e1384 Mon Sep 17 00:00:00 2001 From: "till.huckemann@rwth-aachen.de" Date: Fri, 25 Oct 2024 14:10:25 +0200 Subject: [PATCH 09/29] Compensated gates are now added as attribute to device if generated from a dictionary. --- src/qumada/measurement/device_object.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 43b8f2d..676ec05 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -617,6 +617,7 @@ def __init__(self, name: str, Terminal: Terminal, properties: dict = {}) -> None self.name = name self._limits = self.properties.get("limits", None) self.leverarms = self.properties.get("leverarms", None) + self.compensated_gates = self.properties.get("compensated_gates") self.rampable = False self.ramp_rate = self.properties.get("ramp_rate", 0.1) self.group = self.properties.get("group", None) @@ -640,6 +641,7 @@ def _apply_properties(self): self.ramp_rate = self.properties.get("ramp_rate", self.ramp_rate) self.group = self.properties.get("group", self.group) self.leverarms = self.properties.get("leverarms", self.leverarms) + self.compensated_gates = self.properties.get("compensated_gates") @property def value(self): From 3f95cf83a431aedcde3ca0974af6c861499f94e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:22:28 +0000 Subject: [PATCH 10/29] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/asottile/pyupgrade: v3.17.0 → v3.19.0](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.19.0) - [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d15910..737ab0c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-docstring-first - id: check-yaml @@ -15,7 +15,7 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.19.0 hooks: - id: pyupgrade args: [--py39-plus] @@ -25,7 +25,7 @@ repos: - id: isort args: ["--profile", "black"] - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black args: ["--line-length", "120"] From cf9284cb26f3d177c31f27196981e600e8b5a7c2 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 16:10:22 +0100 Subject: [PATCH 11/29] It's possible to save/load trigger mappings now --- src/qumada/instrument/buffers/buffer.py | 92 ++++++++++++++++++++----- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/src/qumada/instrument/buffers/buffer.py b/src/qumada/instrument/buffers/buffer.py index 072480d..928c5df 100644 --- a/src/qumada/instrument/buffers/buffer.py +++ b/src/qumada/instrument/buffers/buffer.py @@ -28,6 +28,11 @@ from qcodes.metadatable import Metadatable from qcodes.parameters import Parameter +import logging +logger = logging.getLogger(__name__) + +import json + def is_bufferable(object: Instrument | Parameter): """Checks if the instrument or parameter is bufferable using the qumada Buffer definition.""" @@ -84,15 +89,8 @@ def map_buffers( print("Available trigger inputs:") print("[0]: None") for idx, trigger in enumerate(buffer.AVAILABLE_TRIGGERS, 1): - print(f"[{idx}]: {trigger}") - # TODO: Just a workaround, fix this! - if overwrite_trigger is not None: - try: - chosen = int(overwrite_trigger) - except Exception: - chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) - else: - chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) + print(f"[{idx}]: {trigger}") + chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) if chosen == 0: trigger = None else: @@ -125,14 +123,7 @@ def _map_triggers( print("[0]: None") for idx, trigger in enumerate(instrument._qumada_mapping.AVAILABLE_TRIGGERS, 1): print(f"[{idx}]: {trigger}") - # TODO: Just a workaround, fix this! - if overwrite_trigger is not None: - try: - chosen = int(overwrite_trigger) - except Exception: - chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) - else: - chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) + chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) if chosen == 0: trigger = None else: @@ -147,7 +138,38 @@ def map_triggers( gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter], overwrite_trigger=None, skip_mapped=True, + path: None|str=None, ) -> None: + """ + Maps the triggers of triggerable or bufferable components. + Ignores already mapped triggers by default. + + Parameters + ---------- + components : Mapping[Any, Metadatable] + Components of QCoDeS station (containing instruments to be mapped). + properties : dict + Properties of measurement script/device. Currently only required for + buffered instruments. + TODO: Remove! + gate_parameters : Mapping[Any, Mapping[Any, Parameter] | Parameter] + Parameters of measurement script/device. Currently only required for + buffered instruments. + TODO: Remove! + skip_mapped : Bool, optional + If true already mapped parameters are skipped + Set to false if you want to remap something. The default is True. + path : None|str, optional + Provide path to a json file with trigger mapping. If not all instruments + are covered in the file, you will be asked to map those. Works only if + names in file match instrument.full_name of your current instruments. + The default is None. + """ + if path is not None: + try: + load_trigger_mapping(components, path) + except Exception as e: + logger.warning(f"Exception when loadig trigger mapping from file: {e}") map_buffers( components, properties, @@ -162,8 +184,40 @@ def map_triggers( overwrite_trigger, skip_mapped, ) - - + +def save_trigger_mapping(components: Mapping[Any, Metadatable], path: str): + """ + Saves mapped triggers from components to json file. + Components should be station.components, path is the path to the file. + """ + trigger_dict = {} + triggered_instruments = filter( + lambda x: any((is_triggerable(x), is_bufferable(x))), components.values()) + for instrument in triggered_instruments: + try: + trigger_dict[instrument.full_name] = instrument._qumada_mapping.trigger_in + except AttributeError: + trigger_dict[instrument.full_name] = instrument._qumada_buffer.trigger + with open(path, mode="w") as file: + json.dump(trigger_dict, file) + +def load_trigger_mapping(components: Mapping[Any, Metadatable], path: str): + """ + Loads json file with trigger mappings and tries to apply them to instruments + in the components. Works only if the instruments have the same full_name + as in the saved file! + """ + with open(path) as file: + trigger_dict: Mapping[str, Mapping[str, str] | str] = json.load(file) + for instrument_name, trigger in trigger_dict.items(): + for instrument in components.values(): + if instrument.full_name == instrument_name: + if is_bufferable(instrument) is True: + instrument._qumada_buffer.trigger = trigger + elif is_triggerable(instrument) is True: + instrument._qumada_mapping.trigger_in = trigger + + class Buffer(ABC): """Base class for a general buffer interface for an instrument.""" From 74db138d94942ee905ba553bde70fca0136321c1 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 16:23:29 +0100 Subject: [PATCH 12/29] Updated documentation for trigger_mapping. --- docs/examples.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 29c487a..ec4af2f 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,4 +1,5 @@ Examples +Examples ============== Measurement Scripts @@ -376,7 +377,7 @@ to the used instruments. This requires a few changes to the way the measurement station.add_component(dac) mfli = MFLI("mfli", "DEV4121", "169.254.40.160") - add_mapping_to_instrument(mfli, path = MFLI_MAPPING) + add_mapping_to_instrument(mfli, mappint = MFLI_MAPPING) station.add_component(mfli) (This tutorial expects you to do the basic qcodes and QuMADA imports on your own) @@ -386,13 +387,10 @@ The QuMADA buffer has methods to setup the buffer and triggers as well as to sta to the QuMADA ones. Currently, QuMADA supports the MFLI and the SR830 (more to come), how to add additional instruments by yourself will be covered in a different section. The DecaDac's is required to do a smooth ramp, which requires usage of the built in ramp method. As this cannot be mapped by using the normal QuMADA mapping.json file, we use the DecadacMapping class and pass it as the mapping-kwarg -(instead of "path") to "add_mapping_to_instrument". This does not only add the normal mapping but includes the _qumada_ramp() method which is used in QuMADA' buffered measurement scripts for ramping channels. This method makes use of the +to "add_mapping_to_instrument". This does not only add the normal mapping but includes the _qumada_ramp() method which is used in QuMADA' buffered measurement scripts for ramping channels. This method makes use of the built-in ramp method, but standardizes the input parameters so that different instruments can be used with the same measurement script. Note that instruments without built-in ramps can be used for the buffered measurements as well, but then require communication at each setpoint, which slows down the measurement and can lead to asynchronicity. It is strongly adviced to use this feature only for debugging. -.. note:: - - In some cases it is possible to add trigger channels to the _qumada_ramp method. Those are triggered as soon as the ramp starts. However, this feature is still WIP and can lead to significat offsets due to time delays. Setting up the buffer in QuMADA is done via a settings dict (which can also be serialized into a yaml or json file). The parameters are: @@ -478,7 +476,7 @@ The measurement script is then setup in almost the same way as for normal, unbuf sync_trigger = dac.channels[19].volt) map_gates_to_instruments(station.components, script.gate_parameters) - map_buffers(station.components, script.properties, script.gate_parameters) + map_triggers(station.components, script.properties, script.gate_parameters) Instead of the Generic_1D_Sweep we are now using the buffed version. It requires the buffer_settings as input argument as well as the trigger_type. The trigger type defines, how the measurement is started, it can be either "manual", meaning the script does not care about triggers and just starts the sweep once the script.run is executed, @@ -496,7 +494,7 @@ The latter tells the measurement script how to start the measurement. You can specify a sync_trigger in the script.setup() which is then passed on to the ramp method (if supported by the instrument) and will automatically raise the trigger once the measurement is started in "manual" mode. In this example the Dac's last channel will be used to trigger the measurement. -In addition to the familiar map_gates_to_instruments, we have to execute map_buffers() as well. +In addition to the familiar map_gates_to_instruments, we have to execute map_triggers() as well. It is used to specify the trigger inputs used to trigger the available buffers. .. code-block:: @@ -514,6 +512,10 @@ It is used to specify the trigger inputs used to trigger the available buffers. If required the buffer settings are changed to allow usage of the chosen trigger input. In our example, choosing the trigger_in_1 for the MFLI will change the trigger_mode from "edge" to "digital", as the MFLI's trigger inputs require this setting and would raise an exception during the measurement. +It is also possible to save and load trigger mappings to/from json files. You can simply use "save_trigger_mapping" and "load_trigger_mapping" from qumada.instrument.buffers.buffer and provide +station.components and a file path. Alternatively, you can call map_triggers with the "path" argument and provide the path to your mapping-json. The mapping prompt will only open up, if not all instruments can be +mapped from the file. + .. code-block:: python script.run() From 1fd722ff58a47d5d7a06ee0122954339c4fe3d6f Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:06:58 +0100 Subject: [PATCH 13/29] Removed buffer subscription in mapping (it is done in initialize anyway) and removed unrequired params. --- src/qumada/instrument/buffers/buffer.py | 31 ++++--------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/qumada/instrument/buffers/buffer.py b/src/qumada/instrument/buffers/buffer.py index 928c5df..0b72fc0 100644 --- a/src/qumada/instrument/buffers/buffer.py +++ b/src/qumada/instrument/buffers/buffer.py @@ -59,26 +59,15 @@ class BufferException(Exception): def map_buffers( components: Mapping[Any, Metadatable], - properties: dict, - gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter], - overwrite_trigger=None, skip_mapped=True, + **kwargs, ) -> None: """ Maps the bufferable instruments of gate parameters. Args: components (Mapping[Any, Metadatable]): Instruments/Components in QCoDeS - gate_parameters (Mapping[Any, Union[Mapping[Any, Parameter], Parameter]]): - Gates, as defined in the measurement script """ - # subscribe to gettable parameters with buffer - for gate, parameters in gate_parameters.items(): - for parameter, channel in parameters.items(): - if properties[gate][parameter]["type"] == "gettable": - if is_bufferable(channel): - buffer: Buffer = channel.root_instrument._qumada_buffer - buffer.subscribe([channel]) buffered_instruments = filter(is_bufferable, components.values()) for instrument in buffered_instruments: @@ -101,18 +90,14 @@ def map_buffers( def _map_triggers( components: Mapping[Any, Metadatable], - properties: dict, - gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter], - overwrite_trigger=None, skip_mapped=True, + **kwargs, ) -> None: """ Maps the bufferable instruments of gate parameters. Args: components (Mapping[Any, Metadatable]): Instruments/Components in QCoDeS - gate_parameters (Mapping[Any, Union[Mapping[Any, Parameter], Parameter]]): - Gates, as defined in the measurement script """ triggered_instruments = filter(is_triggerable, components.values()) for instrument in triggered_instruments: @@ -134,11 +119,9 @@ def _map_triggers( def map_triggers( components: Mapping[Any, Metadatable], - properties: dict, - gate_parameters: Mapping[Any, Mapping[Any, Parameter] | Parameter], - overwrite_trigger=None, skip_mapped=True, path: None|str=None, + **kwargs, ) -> None: """ Maps the triggers of triggerable or bufferable components. @@ -172,17 +155,13 @@ def map_triggers( logger.warning(f"Exception when loadig trigger mapping from file: {e}") map_buffers( components, - properties, - gate_parameters, - overwrite_trigger, skip_mapped, + **kwargs, ) _map_triggers( components, - properties, - gate_parameters, - overwrite_trigger, skip_mapped, + **kwargs ) def save_trigger_mapping(components: Mapping[Any, Metadatable], path: str): From e0ddf4e59b127bed1676b852c192f4403a9f23b1 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:12:19 +0100 Subject: [PATCH 14/29] Updated documentation and example scripts accordingly. --- docs/examples.rst | 2 +- src/examples/buffered_dummy_example.py | 14 +++++++------- .../buffered_dummy_example_compensation.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index ec4af2f..c2a2628 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -476,7 +476,7 @@ The measurement script is then setup in almost the same way as for normal, unbuf sync_trigger = dac.channels[19].volt) map_gates_to_instruments(station.components, script.gate_parameters) - map_triggers(station.components, script.properties, script.gate_parameters) + map_triggers(station.components) Instead of the Generic_1D_Sweep we are now using the buffed version. It requires the buffer_settings as input argument as well as the trigger_type. The trigger type defines, how the measurement is started, it can be either "manual", meaning the script does not care about triggers and just starts the sweep once the script.run is executed, diff --git a/src/examples/buffered_dummy_example.py b/src/examples/buffered_dummy_example.py index 457c9cc..3cfc2d6 100644 --- a/src/examples/buffered_dummy_example.py +++ b/src/examples/buffered_dummy_example.py @@ -40,7 +40,7 @@ from qcodes.station import Station from qumada.instrument.buffered_instruments import BufferedDummyDMM as DummyDmm -from qumada.instrument.buffers.buffer import map_buffers +from qumada.instrument.buffers.buffer import map_triggers, save_trigger_mapping, load_trigger_mapping from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac from qumada.instrument.mapping import ( DUMMY_DMM_MAPPING, @@ -87,7 +87,7 @@ buffer_settings = { # "trigger_threshold": 0.005, # "trigger_mode": "digital", - "sampling_rate": 10, + "sampling_rate": 20, "duration": 5, "burst_duration": 5, "delay": 0, @@ -95,12 +95,12 @@ # %% Measurement Setup parameters = { - "dmm": { + "ohmic": { "voltage": {"type": "gettable"}, "current": {"type": "gettable"}, }, - "dac": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}}, - "dac2": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}}, + "gate1": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}}, + "gate2": {"voltage": {"type": "dynamic", "setpoints": np.linspace(0, np.pi, 100), "value": 0}}, } # %% script = Generic_1D_Sweep_buffered() @@ -114,7 +114,7 @@ ) map_terminals_gui(station.components, script.gate_parameters) -map_buffers(station.components, script.properties, script.gate_parameters) +map_triggers(station.components, path = r"C:\Users\till3\Documents\PythonScripts\tm.json") # %% Run measurement -script.run(insert_metadata_into_db=False) +script.run() diff --git a/src/examples/buffered_dummy_example_compensation.py b/src/examples/buffered_dummy_example_compensation.py index db4f6f6..21910dc 100644 --- a/src/examples/buffered_dummy_example_compensation.py +++ b/src/examples/buffered_dummy_example_compensation.py @@ -40,7 +40,7 @@ from qcodes.station import Station from qumada.instrument.buffered_instruments import BufferedDummyDMM as DummyDmm -from qumada.instrument.buffers.buffer import map_buffers +from qumada.instrument.buffers.buffer import map_triggers from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac from qumada.instrument.mapping import ( DUMMY_DMM_MAPPING, @@ -139,7 +139,7 @@ ) map_terminals_gui(station.components, script.gate_parameters) -map_buffers(station.components, script.properties, script.gate_parameters) +map_triggers(station.components, script.properties, script.gate_parameters) # %% Run measurement script.run(insert_metadata_into_db=False) From fc02375c970cf113fa22bed41b0142ecc10aa0cb Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:20:16 +0100 Subject: [PATCH 15/29] Changing import according to new QCoDeS version. --- src/tests/mapping_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/mapping_test.py b/src/tests/mapping_test.py index 42c2318..3a11f64 100644 --- a/src/tests/mapping_test.py +++ b/src/tests/mapping_test.py @@ -34,7 +34,7 @@ from pytest_cases import fixture_ref, parametrize from pytest_mock import MockerFixture from qcodes.station import Station -from qcodes.tests.instrument_mocks import DummyChannelInstrument, DummyInstrument +from qcodes.instrument_drivers.mock_instruments import DummyChannelInstrument, DummyInstrument import qumada.instrument.mapping as mapping from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac From 239a60a192f94c3a07c9be99539690f4e457202b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:20:25 +0000 Subject: [PATCH 16/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/tests/mapping_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tests/mapping_test.py b/src/tests/mapping_test.py index 3a11f64..b78faac 100644 --- a/src/tests/mapping_test.py +++ b/src/tests/mapping_test.py @@ -33,8 +33,11 @@ from PyQt5.QtWidgets import QApplication, QMessageBox from pytest_cases import fixture_ref, parametrize from pytest_mock import MockerFixture +from qcodes.instrument_drivers.mock_instruments import ( + DummyChannelInstrument, + DummyInstrument, +) from qcodes.station import Station -from qcodes.instrument_drivers.mock_instruments import DummyChannelInstrument, DummyInstrument import qumada.instrument.mapping as mapping from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac From 4fef8253ce0a2ca9f7c1dc52ead431223fa719ed Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:23:35 +0100 Subject: [PATCH 17/29] Changing more imports... --- src/tests/instrument_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/instrument_test.py b/src/tests/instrument_test.py index 9280960..0f70356 100644 --- a/src/tests/instrument_test.py +++ b/src/tests/instrument_test.py @@ -22,7 +22,7 @@ import pytest from qcodes.instrument import Instrument, VisaInstrument from qcodes.parameters import Parameter -from qcodes.tests.instrument_mocks import DummyInstrument +from qcodes.instrument_drivers.mock_instruments import DummyInstrument from qumada.instrument.instrument import is_instrument_class From 98a61e1cc122a4b291f348e494d8a90695bad91d Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:34:16 +0100 Subject: [PATCH 18/29] Enabling hooks for metadata again prior to push to main. --- src/qumada/measurement/measurement.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qumada/measurement/measurement.py b/src/qumada/measurement/measurement.py index 3158823..21e8c8a 100644 --- a/src/qumada/measurement/measurement.py +++ b/src/qumada/measurement/measurement.py @@ -113,9 +113,9 @@ class MeasurementScript(ABC): def __init__(self): # Create function hooks for metadata # reverse order, so insert metadata is run second - # self.run = create_hook(self.run, self._insert_metadata_into_db) - # self.run = create_hook(self.run, self._add_data_to_metadata) - # self.run = create_hook(self.run, self._add_current_datetime_to_metadata) + self.run = create_hook(self.run, self._insert_metadata_into_db) + self.run = create_hook(self.run, self._add_data_to_metadata) + self.run = create_hook(self.run, self._add_current_datetime_to_metadata) self.properties: dict[Any, Any] = {} self.gate_parameters: dict[Any, dict[Any, Parameter | None] | Parameter | None] = {} From 18de6a2aac31f09206846907aaf5d6e6bf86d43d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:37:57 +0000 Subject: [PATCH 19/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/examples/buffered_dummy_example.py | 8 +- src/qumada/instrument/buffers/buffer.py | 29 +- .../instrument/custom_drivers/QDevil/QDAC2.py | 1160 +++++++++-------- .../instrument/custom_drivers/ZI/MFLI.py | 18 +- src/qumada/instrument/mapping/QDevil/qdac2.py | 10 +- src/qumada/measurement/device_object.py | 84 +- src/qumada/measurement/measurement.py | 15 +- src/qumada/measurement/shuttling_scripts.py | 6 +- src/tests/instrument_test.py | 2 +- 9 files changed, 690 insertions(+), 642 deletions(-) diff --git a/src/examples/buffered_dummy_example.py b/src/examples/buffered_dummy_example.py index 3cfc2d6..2b561a6 100644 --- a/src/examples/buffered_dummy_example.py +++ b/src/examples/buffered_dummy_example.py @@ -40,7 +40,11 @@ from qcodes.station import Station from qumada.instrument.buffered_instruments import BufferedDummyDMM as DummyDmm -from qumada.instrument.buffers.buffer import map_triggers, save_trigger_mapping, load_trigger_mapping +from qumada.instrument.buffers.buffer import ( + load_trigger_mapping, + map_triggers, + save_trigger_mapping, +) from qumada.instrument.custom_drivers.Dummies.dummy_dac import DummyDac from qumada.instrument.mapping import ( DUMMY_DMM_MAPPING, @@ -114,7 +118,7 @@ ) map_terminals_gui(station.components, script.gate_parameters) -map_triggers(station.components, path = r"C:\Users\till3\Documents\PythonScripts\tm.json") +map_triggers(station.components, path=r"C:\Users\till3\Documents\PythonScripts\tm.json") # %% Run measurement script.run() diff --git a/src/qumada/instrument/buffers/buffer.py b/src/qumada/instrument/buffers/buffer.py index 0b72fc0..6c10583 100644 --- a/src/qumada/instrument/buffers/buffer.py +++ b/src/qumada/instrument/buffers/buffer.py @@ -20,6 +20,7 @@ from __future__ import annotations +import logging from abc import ABC, abstractmethod from collections.abc import Mapping from typing import Any @@ -28,7 +29,6 @@ from qcodes.metadatable import Metadatable from qcodes.parameters import Parameter -import logging logger = logging.getLogger(__name__) import json @@ -78,7 +78,7 @@ def map_buffers( print("Available trigger inputs:") print("[0]: None") for idx, trigger in enumerate(buffer.AVAILABLE_TRIGGERS, 1): - print(f"[{idx}]: {trigger}") + print(f"[{idx}]: {trigger}") chosen = int(input(f"Choose the trigger input for {instrument.name}: ")) if chosen == 0: trigger = None @@ -120,12 +120,12 @@ def _map_triggers( def map_triggers( components: Mapping[Any, Metadatable], skip_mapped=True, - path: None|str=None, + path: None | str = None, **kwargs, ) -> None: """ - Maps the triggers of triggerable or bufferable components. - Ignores already mapped triggers by default. + Maps the triggers of triggerable or bufferable components. + Ignores already mapped triggers by default. Parameters ---------- @@ -136,7 +136,7 @@ def map_triggers( buffered instruments. TODO: Remove! gate_parameters : Mapping[Any, Mapping[Any, Parameter] | Parameter] - Parameters of measurement script/device. Currently only required for + Parameters of measurement script/device. Currently only required for buffered instruments. TODO: Remove! skip_mapped : Bool, optional @@ -158,20 +158,16 @@ def map_triggers( skip_mapped, **kwargs, ) - _map_triggers( - components, - skip_mapped, - **kwargs - ) - + _map_triggers(components, skip_mapped, **kwargs) + + def save_trigger_mapping(components: Mapping[Any, Metadatable], path: str): """ Saves mapped triggers from components to json file. Components should be station.components, path is the path to the file. """ trigger_dict = {} - triggered_instruments = filter( - lambda x: any((is_triggerable(x), is_bufferable(x))), components.values()) + triggered_instruments = filter(lambda x: any((is_triggerable(x), is_bufferable(x))), components.values()) for instrument in triggered_instruments: try: trigger_dict[instrument.full_name] = instrument._qumada_mapping.trigger_in @@ -180,6 +176,7 @@ def save_trigger_mapping(components: Mapping[Any, Metadatable], path: str): with open(path, mode="w") as file: json.dump(trigger_dict, file) + def load_trigger_mapping(components: Mapping[Any, Metadatable], path: str): """ Loads json file with trigger mappings and tries to apply them to instruments @@ -195,8 +192,8 @@ def load_trigger_mapping(components: Mapping[Any, Metadatable], path: str): instrument._qumada_buffer.trigger = trigger elif is_triggerable(instrument) is True: instrument._qumada_mapping.trigger_in = trigger - - + + class Buffer(ABC): """Base class for a general buffer interface for an instrument.""" diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index dc2ad65..3afe53f 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -1,14 +1,16 @@ -import numpy as np +import abc import itertools import uuid +from collections.abc import Sequence from time import sleep as sleep_s -from qcodes.instrument.channel import InstrumentChannel, ChannelList -from qcodes.instrument.visa import VisaInstrument +from typing import Dict, List, NewType, Optional, Tuple + +import numpy as np +from packaging.version import Version, parse from pyvisa.errors import VisaIOError +from qcodes.instrument.channel import ChannelList, InstrumentChannel +from qcodes.instrument.visa import VisaInstrument from qcodes.utils import validators -from typing import NewType, Tuple, Sequence, List, Dict, Optional -from packaging.version import Version, parse -import abc # Version 1.2.0 # @@ -45,48 +47,45 @@ # - Detect and handle mixing of internal and external triggers (_trigger). # -error_ambiguous_wave = 'Only one of frequency_Hz or period_s can be ' \ - 'specified for a wave form' +error_ambiguous_wave = "Only one of frequency_Hz or period_s can be " "specified for a wave form" def ints_to_comma_separated_list(array: Sequence[int]) -> str: - return ','.join([str(x) for x in array]) + return ",".join([str(x) for x in array]) def floats_to_comma_separated_list(array: Sequence[float]) -> str: - rounded = [format(x, 'g') for x in array] - return ','.join(rounded) + rounded = [format(x, "g") for x in array] + return ",".join(rounded) def comma_sequence_to_list(sequence: str) -> Sequence[str]: if not sequence: return [] - return [x.strip() for x in sequence.split(',')] + return [x.strip() for x in sequence.split(",")] def comma_sequence_to_list_of_floats(sequence: str) -> Sequence[float]: if not sequence: return [] - return [float(x.strip()) for x in sequence.split(',')] + return [float(x.strip()) for x in sequence.split(",")] -def diff_matrix(initial: Sequence[float], - measurements: Sequence[Sequence[float]]) -> np.ndarray: - """Subtract an array of measurements by an initial measurement - """ +def diff_matrix(initial: Sequence[float], measurements: Sequence[Sequence[float]]) -> np.ndarray: + """Subtract an array of measurements by an initial measurement""" matrix = np.asarray(measurements) return matrix - np.asarray(list(itertools.repeat(initial, matrix.shape[1]))) -def split_version_string_into_components(version: str) -> List[str]: - return version.split('-') +def split_version_string_into_components(version: str) -> list[str]: + return version.split("-") """External input trigger There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4). """ -ExternalInput = NewType('ExternalInput', int) +ExternalInput = NewType("ExternalInput", int) class QDac2Trigger_Context: @@ -96,7 +95,7 @@ class QDac2Trigger_Context: that the trigger can be automatically reclaimed when the context exits. """ - def __init__(self, parent: 'QDac2', value: int): + def __init__(self, parent: "QDac2", value: int): self._parent = parent self._value = value @@ -125,64 +124,58 @@ class QDac2ExternalTrigger(InstrumentChannel): non-isolated 3V3 on the back (4, 5). """ - def __init__(self, parent: 'QDac2', name: str, external: int): + def __init__(self, parent: "QDac2", name: str, external: int): super().__init__(parent, name) - self.add_function( - name='source_from_bus', - call_cmd=f'outp:trig{external}:sour bus' - ) + self.add_function(name="source_from_bus", call_cmd=f"outp:trig{external}:sour bus") self.add_parameter( - name='source_from_input', + name="source_from_input", # Route external input to external output - set_cmd='outp:trig{0}:sour ext{1}'.format(external, '{}'), - get_parser=int + set_cmd="outp:trig{}:sour ext{}".format(external, "{}"), + get_parser=int, ) self.add_parameter( - name='source_from_trigger', + name="source_from_trigger", # Route internal trigger to external output set_parser=_trigger_context_to_value, - set_cmd='outp:trig{0}:sour int{1}'.format(external, '{}'), - get_parser=int + set_cmd="outp:trig{}:sour int{}".format(external, "{}"), + get_parser=int, ) self.add_parameter( - name='width_s', - label='width', - unit='s', - set_cmd='outp:trig{0}:widt {1}'.format(external, '{}'), - get_cmd=f'outp:trig{external}:widt?', - get_parser=float + name="width_s", + label="width", + unit="s", + set_cmd="outp:trig{}:widt {}".format(external, "{}"), + get_cmd=f"outp:trig{external}:widt?", + get_parser=float, ) self.add_parameter( - name='polarity', - label='polarity', - set_cmd='outp:trig{0}:pol {1}'.format(external, '{}'), - get_cmd=f'outp:trig{external}:pol?', + name="polarity", + label="polarity", + set_cmd="outp:trig{}:pol {}".format(external, "{}"), + get_cmd=f"outp:trig{external}:pol?", get_parser=str, - vals=validators.Enum('inv', 'norm') + vals=validators.Enum("inv", "norm"), ) self.add_parameter( - name='delay_s', - label='delay', - unit='s', - set_cmd='outp:trig{0}:del {1}'.format(external, '{}'), - get_cmd=f'outp:trig{external}:del?', - get_parser=float - ) - self.add_function( - name='signal', - call_cmd=f'outp:trig{external}:sign' + name="delay_s", + label="delay", + unit="s", + set_cmd="outp:trig{}:del {}".format(external, "{}"), + get_cmd=f"outp:trig{external}:del?", + get_parser=float, ) + self.add_function(name="signal", call_cmd=f"outp:trig{external}:sign") self.add_parameter( - name='set_trigger', + name="set_trigger", # Route internal trigger to external output - #set_parser=_trigger_context_to_value, - set_cmd='outp:trig{0}:sour {1}'.format(external, "{}"), + # set_parser=_trigger_context_to_value, + set_cmd="outp:trig{}:sour {}".format(external, "{}"), ) class _Channel_Context(metaclass=abc.ABCMeta): - def __init__(self, channel: 'QDac2Channel'): + def __init__(self, channel: "QDac2Channel"): self._channel = channel def __enter__(self): @@ -227,9 +220,9 @@ def _channel_message(self, template: str) -> None: class _Dc_Context(_Channel_Context): - def __init__(self, channel: 'QDac2Channel'): + def __init__(self, channel: "QDac2Channel"): super().__init__(channel) - self._write_channel('sour{0}:dc:trig:sour hold') + self._write_channel("sour{0}:dc:trig:sour hold") self._trigger: Optional[QDac2Trigger_Context] = None self._marker_start: Optional[QDac2Trigger_Context] = None self._marker_end: Optional[QDac2Trigger_Context] = None @@ -258,9 +251,8 @@ def start_on_external(self, trigger: ExternalInput) -> None: self._make_ready_to_start() def abort(self) -> None: - """Abort any DC running generator on the channel - """ - self._write_channel('sour{0}:dc:abor') + """Abort any DC running generator on the channel""" + self._write_channel("sour{0}:dc:abor") def end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of the DC generator @@ -318,33 +310,42 @@ def _set_delay(self, delay_s: float) -> None: self._write_channel(f'sour{"{0}"}:dc:del {delay_s}') def _set_triggering(self) -> None: - self._write_channel('sour{0}:dc:trig:sour bus') + self._write_channel("sour{0}:dc:trig:sour bus") self._make_ready_to_start() def _start(self, description: str) -> None: if self._trigger: self._make_ready_to_start() - return self._write_channel(f'tint {self._trigger.value}') + return self._write_channel(f"tint {self._trigger.value}") self._switch_to_immediate_trigger() - self._write_channel('sour{0}:dc:init') + self._write_channel("sour{0}:dc:init") def _make_ready_to_start(self) -> None: - self._write_channel('sour{0}:dc:init:cont on') - self._write_channel('sour{0}:dc:init') + self._write_channel("sour{0}:dc:init:cont on") + self._write_channel("sour{0}:dc:init") def _switch_to_immediate_trigger(self) -> None: - self._write_channel('sour{0}:dc:init:cont off') - self._write_channel('sour{0}:dc:trig:sour imm') + self._write_channel("sour{0}:dc:init:cont off") + self._write_channel("sour{0}:dc:trig:sour imm") class Sweep_Context(_Dc_Context): - def __init__(self, channel: 'QDac2Channel', start_V: float, stop_V: float, - points: int, repetitions: int, dwell_s: float, delay_s: float, - backwards: bool, stepped: bool): + def __init__( + self, + channel: "QDac2Channel", + start_V: float, + stop_V: float, + points: int, + repetitions: int, + dwell_s: float, + delay_s: float, + backwards: bool, + stepped: bool, + ): self._repetitions = repetitions super().__init__(channel) - channel.write_channel('sour{0}:volt:mode swe') + channel.write_channel("sour{0}:volt:mode swe") self._set_voltages(start_V, stop_V) channel.write_channel(f'sour{"{0}"}:swe:poin {points}') self._set_trigger_mode(stepped) @@ -360,13 +361,13 @@ def _set_voltages(self, start_V: float, stop_V: float): def _set_trigger_mode(self, stepped: bool) -> None: if stepped: - return self._write_channel('sour{0}:swe:gen step') - self._write_channel('sour{0}:swe:gen auto') + return self._write_channel("sour{0}:swe:gen step") + self._write_channel("sour{0}:swe:gen auto") def _set_direction(self, backwards: bool) -> None: if backwards: - return self._write_channel('sour{0}:swe:dir down') - self._write_channel('sour{0}:swe:dir up') + return self._write_channel("sour{0}:swe:dir down") + self._write_channel("sour{0}:swe:dir up") def _set_repetitions(self) -> None: self._write_channel(f'sour{"{0}"}:swe:coun {self._repetitions}') @@ -375,44 +376,43 @@ def _perpetual(self) -> bool: return self._repetitions < 0 def start(self) -> None: - """Start the DC sweep - """ - self._start('DC sweep') + """Start the DC sweep""" + self._start("DC sweep") def points(self) -> int: """ Returns: int: Number of steps in the DC sweep """ - return int(self._ask_channel('sour{0}:swe:poin?')) + return int(self._ask_channel("sour{0}:swe:poin?")) def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the DC sweep """ - return int(self._ask_channel('sour{0}:swe:ncl?')) + return int(self._ask_channel("sour{0}:swe:ncl?")) def time_s(self) -> float: """ Returns: float: Seconds that it will take to do the sweep """ - return float(self._ask_channel('sour{0}:swe:time?')) + return float(self._ask_channel("sour{0}:swe:time?")) def start_V(self) -> float: """ Returns: float: Starting voltage """ - return float(self._ask_channel('sour{0}:swe:star?')) + return float(self._ask_channel("sour{0}:swe:star?")) def stop_V(self) -> float: """ Returns: float: Ending voltage """ - return float(self._ask_channel('sour{0}:swe:stop?')) + return float(self._ask_channel("sour{0}:swe:stop?")) def values_V(self) -> Sequence[float]: """ @@ -424,12 +424,19 @@ def values_V(self) -> Sequence[float]: class List_Context(_Dc_Context): - def __init__(self, channel: 'QDac2Channel', voltages: Sequence[float], - repetitions: int, dwell_s: float, delay_s: float, - backwards: bool, stepped: bool): + def __init__( + self, + channel: "QDac2Channel", + voltages: Sequence[float], + repetitions: int, + dwell_s: float, + delay_s: float, + backwards: bool, + stepped: bool, + ): super().__init__(channel) self._repetitions = repetitions - self._write_channel('sour{0}:volt:mode list') + self._write_channel("sour{0}:volt:mode list") self._set_voltages(voltages) self._set_trigger_mode(stepped) self._write_channel(f'sour{"{0}"}:list:dwel {dwell_s}') @@ -439,17 +446,17 @@ def __init__(self, channel: 'QDac2Channel', voltages: Sequence[float], self._set_triggering() def _set_voltages(self, voltages: Sequence[float]) -> None: - self._write_channel_floats('sour{0}:list:volt ', voltages) + self._write_channel_floats("sour{0}:list:volt ", voltages) def _set_trigger_mode(self, stepped: bool) -> None: if stepped: - return self._write_channel('sour{0}:list:tmod step') - self._write_channel('sour{0}:list:tmod auto') + return self._write_channel("sour{0}:list:tmod step") + self._write_channel("sour{0}:list:tmod auto") def _set_direction(self, backwards: bool) -> None: if backwards: - return self._write_channel('sour{0}:list:dir down') - self._write_channel('sour{0}:list:dir up') + return self._write_channel("sour{0}:list:dir down") + self._write_channel("sour{0}:list:dir up") def _set_repetitions(self) -> None: self._write_channel(f'sour{"{0}"}:list:coun {self._repetitions}') @@ -458,9 +465,8 @@ def _perpetual(self) -> bool: return self._repetitions < 0 def start(self) -> None: - """Start the DC list generator - """ - self._start('DC list') + """Start the DC list generator""" + self._start("DC list") def append(self, voltages: Sequence[float]) -> None: """Append voltages to the existing list @@ -468,7 +474,7 @@ def append(self, voltages: Sequence[float]) -> None: Arguments: voltages (Sequence[float]): Sequence of voltages """ - self._write_channel_floats('sour{0}:list:volt:app ', voltages) + self._write_channel_floats("sour{0}:list:volt:app ", voltages) self._make_ready_to_start() def points(self) -> int: @@ -476,14 +482,14 @@ def points(self) -> int: Returns: int: Number of steps in the DC list """ - return int(self._ask_channel('sour{0}:list:poin?')) + return int(self._ask_channel("sour{0}:list:poin?")) def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the DC list """ - return int(self._ask_channel('sour{0}:list:ncl?')) + return int(self._ask_channel("sour{0}:list:ncl?")) def values_V(self) -> Sequence[float]: """ @@ -492,13 +498,12 @@ def values_V(self) -> Sequence[float]: """ # return comma_sequence_to_list_of_floats( # self._ask_channel('sour{0}:list:volt?')) - return comma_sequence_to_list_of_floats( - self._ask_channel('sour{0}:list:volt?')) + return comma_sequence_to_list_of_floats(self._ask_channel("sour{0}:list:volt?")) class _Waveform_Context(_Channel_Context): - def __init__(self, channel: 'QDac2Channel'): + def __init__(self, channel: "QDac2Channel"): super().__init__(channel) self._trigger: Optional[QDac2Trigger_Context] = None self._marker_start: Optional[QDac2Trigger_Context] = None @@ -509,7 +514,7 @@ def __init__(self, channel: 'QDac2Channel'): def _start(self, wave_kind: str, description: str) -> None: if self._trigger: self._make_ready_to_start(wave_kind) - return self._write_channel(f'tint {self._trigger.value}') + return self._write_channel(f"tint {self._trigger.value}") self._switch_to_immediate_trigger(wave_kind) self._write_channel(f'sour{"{0}"}:{wave_kind}:init') @@ -569,66 +574,72 @@ def _set_slew(self, wave_kind: str, slew_V_s: Optional[float]) -> None: class Square_Context(_Waveform_Context): - def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], - repetitions: int, period_s: Optional[float], - duty_cycle_percent: float, kind: str, inverted: bool, - span_V: float, offset_V: float, delay_s: float, - slew_V_s: Optional[float]): + def __init__( + self, + channel: "QDac2Channel", + frequency_Hz: Optional[float], + repetitions: int, + period_s: Optional[float], + duty_cycle_percent: float, + kind: str, + inverted: bool, + span_V: float, + offset_V: float, + delay_s: float, + slew_V_s: Optional[float], + ): super().__init__(channel) self._repetitions = repetitions - self._write_channel('sour{0}:squ:trig:sour hold') + self._write_channel("sour{0}:squ:trig:sour hold") self._set_frequency(frequency_Hz, period_s) self._write_channel(f'sour{"{0}"}:squ:dcyc {duty_cycle_percent}') self._set_type(kind) self._set_polarity(inverted) self._write_channel(f'sour{"{0}"}:squ:span {span_V}') self._write_channel(f'sour{"{0}"}:squ:offs {offset_V}') - self._set_slew('squ', slew_V_s) - super()._set_delay('squ', delay_s) + self._set_slew("squ", slew_V_s) + super()._set_delay("squ", delay_s) self._write_channel(f'sour{"{0}"}:squ:coun {repetitions}') self._set_triggering() def start(self) -> None: - """Start the square wave generator - """ - self._start('squ', 'square wave') + """Start the square wave generator""" + self._start("squ", "square wave") def abort(self) -> None: - """Abort any running square wave generator - """ - self._write_channel('sour{0}:squ:abor') + """Abort any running square wave generator""" + self._write_channel("sour{0}:squ:abor") def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the square wave """ - return int(self._ask_channel('sour{0}:squ:ncl?')) + return int(self._ask_channel("sour{0}:squ:ncl?")) - def _set_frequency(self, frequency_Hz: Optional[float], - period_s: Optional[float]) -> None: + def _set_frequency(self, frequency_Hz: Optional[float], period_s: Optional[float]) -> None: if frequency_Hz: return self._write_channel(f'sour{"{0}"}:squ:freq {frequency_Hz}') if period_s: self._write_channel(f'sour{"{0}"}:squ:per {period_s}') def _set_type(self, kind: str) -> None: - if kind == 'positive': - self._write_channel('sour{0}:squ:typ pos') - elif kind == 'negative': - self._write_channel('sour{0}:squ:typ neg') + if kind == "positive": + self._write_channel("sour{0}:squ:typ pos") + elif kind == "negative": + self._write_channel("sour{0}:squ:typ neg") else: - self._write_channel('sour{0}:squ:typ symm') + self._write_channel("sour{0}:squ:typ symm") def _set_polarity(self, inverted: bool) -> None: if inverted: - self._write_channel('sour{0}:squ:pol inv') + self._write_channel("sour{0}:squ:pol inv") else: - self._write_channel('sour{0}:squ:pol norm') + self._write_channel("sour{0}:squ:pol norm") def _set_triggering(self) -> None: - self._write_channel('sour{0}:squ:trig:sour bus') - self._make_ready_to_start('squ') + self._write_channel("sour{0}:squ:trig:sour bus") + self._make_ready_to_start("squ") def end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of the square wave @@ -638,7 +649,7 @@ def end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end """ - return super()._end_marker('squ') + return super()._end_marker("squ") def start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of the square wave @@ -648,7 +659,7 @@ def start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning """ - return super()._start_marker('squ') + return super()._start_marker("squ") def period_end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of each period @@ -658,7 +669,7 @@ def period_end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end of each period """ - return super()._period_end_marker('squ') + return super()._period_end_marker("squ") def period_start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of each period @@ -668,7 +679,7 @@ def period_start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning of each period """ - return super()._period_start_marker('squ') + return super()._period_start_marker("squ") def start_on(self, trigger: QDac2Trigger_Context) -> None: """Attach internal trigger to start the square wave generator @@ -676,7 +687,7 @@ def start_on(self, trigger: QDac2Trigger_Context) -> None: Args: trigger (QDac2Trigger_Context): trigger that will start square wave """ - return super()._start_on(trigger, 'squ') + return super()._start_on(trigger, "squ") def start_on_external(self, trigger: ExternalInput) -> None: """Attach external trigger to start the square wave generator @@ -684,46 +695,51 @@ def start_on_external(self, trigger: ExternalInput) -> None: Args: trigger (ExternalInput): external trigger that will start square wave """ - return super()._start_on_external(trigger, 'squ') + return super()._start_on_external(trigger, "squ") class Sine_Context(_Waveform_Context): - def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], - repetitions: int, period_s: Optional[float], inverted: bool, - span_V: float, offset_V: float, delay_s: float, - slew_V_s: Optional[float]): + def __init__( + self, + channel: "QDac2Channel", + frequency_Hz: Optional[float], + repetitions: int, + period_s: Optional[float], + inverted: bool, + span_V: float, + offset_V: float, + delay_s: float, + slew_V_s: Optional[float], + ): super().__init__(channel) self._repetitions = repetitions - self._write_channel('sour{0}:sine:trig:sour hold') + self._write_channel("sour{0}:sine:trig:sour hold") self._set_frequency(frequency_Hz, period_s) self._set_polarity(inverted) self._write_channel(f'sour{"{0}"}:sine:span {span_V}') self._write_channel(f'sour{"{0}"}:sine:offs {offset_V}') - self._set_slew('sine', slew_V_s) - super()._set_delay('sine', delay_s) + self._set_slew("sine", slew_V_s) + super()._set_delay("sine", delay_s) self._write_channel(f'sour{"{0}"}:sine:coun {repetitions}') self._set_triggering() def start(self) -> None: - """Start the sine wave generator - """ - self._start('sine', 'sine wave') + """Start the sine wave generator""" + self._start("sine", "sine wave") def abort(self) -> None: - """Abort any running sine wave generator - """ - self._write_channel('sour{0}:sine:abor') + """Abort any running sine wave generator""" + self._write_channel("sour{0}:sine:abor") def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the sine wave """ - return int(self._ask_channel('sour{0}:sine:ncl?')) + return int(self._ask_channel("sour{0}:sine:ncl?")) - def _set_frequency(self, frequency_Hz: Optional[float], - period_s: Optional[float]) -> None: + def _set_frequency(self, frequency_Hz: Optional[float], period_s: Optional[float]) -> None: if frequency_Hz: return self._write_channel(f'sour{"{0}"}:sine:freq {frequency_Hz}') if period_s: @@ -731,13 +747,13 @@ def _set_frequency(self, frequency_Hz: Optional[float], def _set_polarity(self, inverted: bool) -> None: if inverted: - self._write_channel('sour{0}:sine:pol inv') + self._write_channel("sour{0}:sine:pol inv") else: - self._write_channel('sour{0}:sine:pol norm') + self._write_channel("sour{0}:sine:pol norm") def _set_triggering(self) -> None: - self._write_channel('sour{0}:sine:trig:sour bus') - self._make_ready_to_start('sine') + self._write_channel("sour{0}:sine:trig:sour bus") + self._make_ready_to_start("sine") def end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of the sine wave @@ -747,7 +763,7 @@ def end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end """ - return super()._end_marker('sine') + return super()._end_marker("sine") def start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of the sine wave @@ -757,7 +773,7 @@ def start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning """ - return super()._start_marker('sine') + return super()._start_marker("sine") def period_end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of each period @@ -767,7 +783,7 @@ def period_end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end of each period """ - return super()._period_end_marker('sine') + return super()._period_end_marker("sine") def period_start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of each period @@ -777,7 +793,7 @@ def period_start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning of each period """ - return super()._period_start_marker('sine') + return super()._period_start_marker("sine") def start_on(self, trigger: QDac2Trigger_Context) -> None: """Attach internal trigger to start the sine wave generator @@ -785,7 +801,7 @@ def start_on(self, trigger: QDac2Trigger_Context) -> None: Args: trigger (QDac2Trigger_Context): trigger that will start sine wave """ - return super()._start_on(trigger, 'sine') + return super()._start_on(trigger, "sine") def start_on_external(self, trigger: ExternalInput) -> None: """Attach external trigger to start the sine wave generator @@ -793,69 +809,75 @@ def start_on_external(self, trigger: ExternalInput) -> None: Args: trigger (ExternalInput): external trigger that will start sine wave """ - return super()._start_on_external(trigger, 'sine') + return super()._start_on_external(trigger, "sine") class Triangle_Context(_Waveform_Context): - def __init__(self, channel: 'QDac2Channel', frequency_Hz: Optional[float], - repetitions: int, period_s: Optional[float], - duty_cycle_percent: float, inverted: bool, span_V: float, - offset_V: float, delay_s: float, slew_V_s: Optional[float]): + def __init__( + self, + channel: "QDac2Channel", + frequency_Hz: Optional[float], + repetitions: int, + period_s: Optional[float], + duty_cycle_percent: float, + inverted: bool, + span_V: float, + offset_V: float, + delay_s: float, + slew_V_s: Optional[float], + ): super().__init__(channel) self._repetitions = repetitions - self._write_channel('sour{0}:tri:trig:sour hold') + self._write_channel("sour{0}:tri:trig:sour hold") self._set_frequency(frequency_Hz, period_s) self._write_channel(f'sour{"{0}"}:tri:dcyc {duty_cycle_percent}') self._set_polarity(inverted) self._write_channel(f'sour{"{0}"}:tri:span {span_V}') self._write_channel(f'sour{"{0}"}:tri:offs {offset_V}') - self._set_slew('tri', slew_V_s) - super()._set_delay('tri', delay_s) + self._set_slew("tri", slew_V_s) + super()._set_delay("tri", delay_s) self._write_channel(f'sour{"{0}"}:tri:coun {repetitions}') self._set_triggering() def start(self) -> None: - """Start the triangle wave generator - """ - self._start('tri', 'triangle wave') + """Start the triangle wave generator""" + self._start("tri", "triangle wave") def abort(self) -> None: - """Abort any running triangle wave generator - """ - self._write_channel('sour{0}:tri:abor') + """Abort any running triangle wave generator""" + self._write_channel("sour{0}:tri:abor") def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the triangle wave """ - return int(self._ask_channel('sour{0}:tri:ncl?')) + return int(self._ask_channel("sour{0}:tri:ncl?")) - def _set_frequency(self, frequency_Hz: Optional[float], - period_s: Optional[float]) -> None: + def _set_frequency(self, frequency_Hz: Optional[float], period_s: Optional[float]) -> None: if frequency_Hz: return self._write_channel(f'sour{"{0}"}:tri:freq {frequency_Hz}') if period_s: self._write_channel(f'sour{"{0}"}:tri:per {period_s}') def _set_type(self, kind: bool) -> None: - if kind == 'positive': - self._write_channel('sour{0}:tri:typ pos') - elif kind == 'negative': - self._write_channel('sour{0}:tri:typ neg') + if kind == "positive": + self._write_channel("sour{0}:tri:typ pos") + elif kind == "negative": + self._write_channel("sour{0}:tri:typ neg") else: - self._write_channel('sour{0}:tri:typ symm') + self._write_channel("sour{0}:tri:typ symm") def _set_polarity(self, inverted: bool) -> None: if inverted: - self._write_channel('sour{0}:tri:pol inv') + self._write_channel("sour{0}:tri:pol inv") else: - self._write_channel('sour{0}:tri:pol norm') + self._write_channel("sour{0}:tri:pol norm") def _set_triggering(self) -> None: - self._write_channel('sour{0}:tri:trig:sour bus') - self._make_ready_to_start('tri') + self._write_channel("sour{0}:tri:trig:sour bus") + self._make_ready_to_start("tri") def end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of the triangle wave @@ -865,7 +887,7 @@ def end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end """ - return super()._end_marker('tri') + return super()._end_marker("tri") def start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of the triangle wave @@ -875,7 +897,7 @@ def start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning """ - return super()._start_marker('tri') + return super()._start_marker("tri") def period_end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of each period @@ -885,7 +907,7 @@ def period_end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end of each period """ - return super()._period_end_marker('tri') + return super()._period_end_marker("tri") def period_start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of each period @@ -895,7 +917,7 @@ def period_start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning of each period """ - return super()._period_start_marker('tri') + return super()._period_start_marker("tri") def start_on(self, trigger: QDac2Trigger_Context) -> None: """Attach internal trigger to start the triangle wave generator @@ -903,7 +925,7 @@ def start_on(self, trigger: QDac2Trigger_Context) -> None: Args: trigger (QDac2Trigger_Context): trigger that will start triangle """ - return super()._start_on(trigger, 'tri') + return super()._start_on(trigger, "tri") def start_on_external(self, trigger: ExternalInput) -> None: """Attach external trigger to start the triangle wave generator @@ -911,44 +933,48 @@ def start_on_external(self, trigger: ExternalInput) -> None: Args: trigger (ExternalInput): external trigger that will start triangle """ - return super()._start_on_external(trigger, 'tri') + return super()._start_on_external(trigger, "tri") class Awg_Context(_Waveform_Context): - def __init__(self, channel: 'QDac2Channel', trace_name: str, - repetitions: int, scale: float, offset_V: float, - slew_V_s: Optional[float]): + def __init__( + self, + channel: "QDac2Channel", + trace_name: str, + repetitions: int, + scale: float, + offset_V: float, + slew_V_s: Optional[float], + ): super().__init__(channel) self._repetitions = repetitions - self._write_channel('sour{0}:awg:trig:sour hold') + self._write_channel("sour{0}:awg:trig:sour hold") self._write_channel(f'sour{"{0}"}:awg:def "{trace_name}"') self._write_channel(f'sour{"{0}"}:awg:scal {scale}') self._write_channel(f'sour{"{0}"}:awg:offs {offset_V}') - self._set_slew('awg', slew_V_s) + self._set_slew("awg", slew_V_s) self._write_channel(f'sour{"{0}"}:awg:coun {repetitions}') self._set_triggering() def start(self) -> None: - """Start the AWG - """ - self._start('awg', 'AWG') + """Start the AWG""" + self._start("awg", "AWG") def abort(self) -> None: - """Abort any running AWG - """ - self._write_channel('sour{0}:awg:abor') + """Abort any running AWG""" + self._write_channel("sour{0}:awg:abor") def cycles_remaining(self) -> int: """ Returns: int: Number of cycles remaining in the AWG """ - return int(self._ask_channel('sour{0}:awg:ncl?')) + return int(self._ask_channel("sour{0}:awg:ncl?")) def _set_triggering(self) -> None: - self._write_channel('sour{0}:awg:trig:sour bus') - self._make_ready_to_start('awg') + self._write_channel("sour{0}:awg:trig:sour bus") + self._make_ready_to_start("awg") def end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of the AWG @@ -958,7 +984,7 @@ def end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end """ - return super()._end_marker('awg') + return super()._end_marker("awg") def start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of the AWG @@ -968,7 +994,7 @@ def start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning """ - return super()._start_marker('awg') + return super()._start_marker("awg") def period_end_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the end of each period @@ -978,7 +1004,7 @@ def period_end_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the end of each period """ - return super()._period_end_marker('awg') + return super()._period_end_marker("awg") def period_start_marker(self) -> QDac2Trigger_Context: """Internal trigger that will mark the beginning of each period @@ -988,7 +1014,7 @@ def period_start_marker(self) -> QDac2Trigger_Context: Returns: QDac2Trigger_Context: trigger that will mark the beginning of each period """ - return super()._period_start_marker('awg') + return super()._period_start_marker("awg") def start_on(self, trigger: QDac2Trigger_Context) -> None: """Attach internal trigger to start the AWG @@ -996,7 +1022,7 @@ def start_on(self, trigger: QDac2Trigger_Context) -> None: Args: trigger (QDac2Trigger_Context): trigger that will start AWG """ - return super()._start_on(trigger, 'awg') + return super()._start_on(trigger, "awg") def start_on_external(self, trigger: ExternalInput) -> None: """Attach external trigger to start the AWG @@ -1004,14 +1030,20 @@ def start_on_external(self, trigger: ExternalInput) -> None: Args: trigger (ExternalInput): external trigger that will start AWG """ - return super()._start_on_external(trigger, 'awg') + return super()._start_on_external(trigger, "awg") class Measurement_Context(_Channel_Context): - def __init__(self, channel: 'QDac2Channel', delay_s: float, - repetitions: int, current_range: str, - aperture_s: Optional[float], nplc: Optional[int]): + def __init__( + self, + channel: "QDac2Channel", + delay_s: float, + repetitions: int, + current_range: str, + aperture_s: Optional[float], + nplc: Optional[int], + ): super().__init__(channel) self._trigger: Optional[QDac2Trigger_Context] = None self._write_channel(f'sens{"{0}"}:del {delay_s}') @@ -1021,16 +1053,15 @@ def __init__(self, channel: 'QDac2Channel', delay_s: float, self._set_triggering() def start(self) -> None: - """Start a current measurement - """ + """Start a current measurement""" if self._trigger: - return self._write_channel(f'tint {self._trigger.value}') + return self._write_channel(f"tint {self._trigger.value}") self._switch_to_immediate_trigger() - self._write_channel('sens{0}:init') + self._write_channel("sens{0}:init") def _switch_to_immediate_trigger(self) -> None: - self._write_channel('sens{0}:init:cont off') - self._write_channel('sens{0}:trig:sour imm') + self._write_channel("sens{0}:init:cont off") + self._write_channel("sens{0}:trig:sour imm") def start_on(self, trigger: QDac2Trigger_Context) -> None: """Attach internal trigger to start the current measurement @@ -1055,23 +1086,22 @@ def start_on_external(self, trigger: ExternalInput) -> None: self._write_channel(f'sens{"{0}"}:init') def abort(self) -> None: - """Abort current measurement - """ - self._write_channel('sens{0}:abor') + """Abort current measurement""" + self._write_channel("sens{0}:abor") def n_cycles_remaining(self) -> int: """ Returns: int: Number of measurements remaining """ - return int(self._ask_channel('sens{0}:ncl?')) + return int(self._ask_channel("sens{0}:ncl?")) def n_available(self) -> int: """ Returns: int: Number of measurements available """ - return int(self._ask_channel('sens{0}:data:poin?')) + return int(self._ask_channel("sens{0}:data:poin?")) def available_A(self) -> Sequence[float]: """Retrieve current measurements @@ -1084,8 +1114,7 @@ def available_A(self) -> Sequence[float]: # Bug circumvention if self.n_available() == 0: return list() - return comma_sequence_to_list_of_floats( - self._ask_channel('sens{0}:data:rem?')) + return comma_sequence_to_list_of_floats(self._ask_channel("sens{0}:data:rem?")) def peek_A(self) -> float: """Peek at the first available current measurement @@ -1093,206 +1122,179 @@ def peek_A(self) -> float: Returns: float: current in Amperes """ - return float(self._ask_channel('sens{0}:data:last?')) + return float(self._ask_channel("sens{0}:data:last?")) - def _set_aperture(self, aperture_s: Optional[float], nplc: Optional[int] - ) -> None: + def _set_aperture(self, aperture_s: Optional[float], nplc: Optional[int]) -> None: if aperture_s: return self._write_channel(f'sens{"{0}"}:aper {aperture_s}') self._write_channel(f'sens{"{0}"}:nplc {nplc}') def _set_triggering(self) -> None: - self._write_channel('sens{0}:trig:sour bus') - self._write_channel('sens{0}:init') + self._write_channel("sens{0}:trig:sour bus") + self._write_channel("sens{0}:init") class QDac2Channel(InstrumentChannel): - def __init__(self, parent: 'QDac2', name: str, channum: int): + def __init__(self, parent: "QDac2", name: str, channum: int): super().__init__(parent, name) self._channum = channum self.add_parameter( - name='measurement_range', - label='range', - set_cmd='sens{1}:rang {0}'.format('{}', channum), - get_cmd=f'sens{channum}:rang?', - vals=validators.Enum('low', 'high') + name="measurement_range", + label="range", + set_cmd="sens{1}:rang {0}".format("{}", channum), + get_cmd=f"sens{channum}:rang?", + vals=validators.Enum("low", "high"), ) self.add_parameter( - name='measurement_aperture_s', - label='aperture', - unit='s', - set_cmd='sens{1}:aper {0}'.format('{}', channum), - get_cmd=f'sens{channum}:aper?', - get_parser=float + name="measurement_aperture_s", + label="aperture", + unit="s", + set_cmd="sens{1}:aper {0}".format("{}", channum), + get_cmd=f"sens{channum}:aper?", + get_parser=float, ) self.add_parameter( - name='measurement_nplc', - label='PLC', - set_cmd='sens{1}:nplc {0}'.format('{}', channum), - get_cmd=f'sens{channum}:nplc?', - get_parser=int + name="measurement_nplc", + label="PLC", + set_cmd="sens{1}:nplc {0}".format("{}", channum), + get_cmd=f"sens{channum}:nplc?", + get_parser=int, ) self.add_parameter( - name='measurement_delay_s', - label=f'delay', - unit='s', - set_cmd='sens{1}:del {0}'.format('{}', channum), - get_cmd=f'sens{channum}:del?', - get_parser=float - ) - self.add_function( - name='measurement_abort', - call_cmd=f'sens{channum}:abor' + name="measurement_delay_s", + label=f"delay", + unit="s", + set_cmd="sens{1}:del {0}".format("{}", channum), + get_cmd=f"sens{channum}:del?", + get_parser=float, ) + self.add_function(name="measurement_abort", call_cmd=f"sens{channum}:abor") self.add_parameter( - name='measurement_count', - label='count', - set_cmd='sens{1}:coun {0}'.format('{}', channum), - get_cmd=f'sens{channum}:coun?', - get_parser=int + name="measurement_count", + label="count", + set_cmd="sens{1}:coun {0}".format("{}", channum), + get_cmd=f"sens{channum}:coun?", + get_parser=int, ) self.add_parameter( - name='n_masurements_remaining', - label='remaning', - get_cmd=f'sens{channum}:ncl?', - get_parser=int + name="n_masurements_remaining", label="remaning", get_cmd=f"sens{channum}:ncl?", get_parser=int ) self.add_parameter( - name='current_last_A', - label='last', - unit='A', - get_cmd=f'sens{channum}:data:last?', - get_parser=float + name="current_last_A", label="last", unit="A", get_cmd=f"sens{channum}:data:last?", get_parser=float ) self.add_parameter( - name='n_measurements_available', - label='available', - get_cmd=f'sens{channum}:data:poin?', - get_parser=int + name="n_measurements_available", label="available", get_cmd=f"sens{channum}:data:poin?", get_parser=int ) self.add_parameter( - name='current_start_on', + name="current_start_on", # Channel {channum} current measurement on internal trigger set_parser=_trigger_context_to_value, - set_cmd='sens{1}:trig:sour int{0}'.format('{}', channum), + set_cmd="sens{1}:trig:sour int{0}".format("{}", channum), ) self.add_parameter( - name='measurement_start_on_external', + name="measurement_start_on_external", # Channel {channum} current measurement on external input - set_cmd='sens{1}:trig:sour ext{0}'.format('{}', channum), + set_cmd="sens{1}:trig:sour ext{0}".format("{}", channum), ) self.add_parameter( - name='output_range', - label='range', - set_cmd='sour{1}:rang {0}'.format('{}', channum), - get_cmd=f'sour{channum}:rang?', - vals=validators.Enum('low', 'high') + name="output_range", + label="range", + set_cmd="sour{1}:rang {0}".format("{}", channum), + get_cmd=f"sour{channum}:rang?", + vals=validators.Enum("low", "high"), ) self.add_parameter( - name='output_low_range_minimum_V', - label='low range min', - unit='V', - get_cmd=f'sour{channum}:rang:low:min?', - get_parser=float + name="output_low_range_minimum_V", + label="low range min", + unit="V", + get_cmd=f"sour{channum}:rang:low:min?", + get_parser=float, ) self.add_parameter( - name='output_low_range_maximum_V', - label='low voltage max', - unit='V', - get_cmd=f'sour{channum}:rang:low:max?', - get_parser=float + name="output_low_range_maximum_V", + label="low voltage max", + unit="V", + get_cmd=f"sour{channum}:rang:low:max?", + get_parser=float, ) self.add_parameter( - name='output_high_range_minimum_V', - label='high voltage min', - unit='V', - get_cmd=f'sour{channum}:rang:high:min?', - get_parser=float + name="output_high_range_minimum_V", + label="high voltage min", + unit="V", + get_cmd=f"sour{channum}:rang:high:min?", + get_parser=float, ) self.add_parameter( - name='output_high_range_maximum_V', - label='high voltage max', - unit='V', - get_cmd=f'sour{channum}:rang:high:max?', - get_parser=float + name="output_high_range_maximum_V", + label="high voltage max", + unit="V", + get_cmd=f"sour{channum}:rang:high:max?", + get_parser=float, ) self.add_parameter( - name='output_filter', - label=f'low-pass cut-off', - unit='Hz', - set_cmd='sour{1}:filt {0}'.format('{}', channum), - get_cmd=f'sour{channum}:filt?', + name="output_filter", + label=f"low-pass cut-off", + unit="Hz", + set_cmd="sour{1}:filt {0}".format("{}", channum), + get_cmd=f"sour{channum}:filt?", get_parser=str, - vals=validators.Enum('dc', 'med', 'high') + vals=validators.Enum("dc", "med", "high"), ) self.add_parameter( - name='dc_constant_V', - label=f'ch{channum}', - unit='V', + name="dc_constant_V", + label=f"ch{channum}", + unit="V", set_cmd=self._set_fixed_voltage_immediately, - get_cmd=f'sour{channum}:volt?', + get_cmd=f"sour{channum}:volt?", get_parser=float, - vals=validators.Numbers(-10.0, 10.0) + vals=validators.Numbers(-10.0, 10.0), ) self.add_parameter( - name='dc_last_V', - label=f'ch{channum}', - unit='V', - get_cmd=f'sour{channum}:volt:last?', - get_parser=float + name="dc_last_V", label=f"ch{channum}", unit="V", get_cmd=f"sour{channum}:volt:last?", get_parser=float ) self.add_parameter( - name='dc_next_V', - label=f'ch{channum}', - unit='V', - set_cmd='sour{1}:volt:trig {0}'.format('{}', channum), - get_cmd=f'sour{channum}:volt:trig?', - get_parser=float + name="dc_next_V", + label=f"ch{channum}", + unit="V", + set_cmd="sour{1}:volt:trig {0}".format("{}", channum), + get_cmd=f"sour{channum}:volt:trig?", + get_parser=float, ) self.add_parameter( - name='dc_slew_rate_V_per_s', - label=f'ch{channum}', - unit='V/s', - set_cmd='sour{1}:volt:slew {0}'.format('{}', channum), - get_cmd=f'sour{channum}:volt:slew?', - get_parser=float + name="dc_slew_rate_V_per_s", + label=f"ch{channum}", + unit="V/s", + set_cmd="sour{1}:volt:slew {0}".format("{}", channum), + get_cmd=f"sour{channum}:volt:slew?", + get_parser=float, ) self.add_parameter( - name='read_current_A', + name="read_current_A", # Perform immediate current measurement on channel - label=f'ch{channum}', - unit='A', - get_cmd=f'read{channum}?', - get_parser=float + label=f"ch{channum}", + unit="A", + get_cmd=f"read{channum}?", + get_parser=float, ) self.add_parameter( - name='fetch_current_A', + name="fetch_current_A", # Retrieve all available current measurements on channel - label=f'ch{channum}', - unit='A', - get_cmd=f'fetc{channum}?', - get_parser=comma_sequence_to_list_of_floats + label=f"ch{channum}", + unit="A", + get_cmd=f"fetc{channum}?", + get_parser=comma_sequence_to_list_of_floats, ) self.add_parameter( - name='dc_mode', - label=f'DC mode', - set_cmd='sour{1}:volt:mode {0}'.format('{}', channum), - get_cmd=f'sour{channum}:volt:mode?', - vals=validators.Enum('fixed', 'list', 'sweep') - ) - self.add_function( - name='dc_initiate', - call_cmd=f'sour{channum}:dc:init' - ) - self.add_function( - name='dc_abort', - call_cmd=f'sour{channum}:dc:abor' - ) - self.add_function( - name='abort', - call_cmd=f'sour{channum}:all:abor' + name="dc_mode", + label=f"DC mode", + set_cmd="sour{1}:volt:mode {0}".format("{}", channum), + get_cmd=f"sour{channum}:volt:mode?", + vals=validators.Enum("fixed", "list", "sweep"), ) + self.add_function(name="dc_initiate", call_cmd=f"sour{channum}:dc:init") + self.add_function(name="dc_abort", call_cmd=f"sour{channum}:dc:abor") + self.add_function(name="abort", call_cmd=f"sour{channum}:all:abor") @property def number(self) -> int: @@ -1308,16 +1310,18 @@ def clear_measurements(self) -> Sequence[float]: Sequence[float]: list of available current measurements """ # Bug circumvention - if int(self.ask_channel('sens{0}:data:poin?')) == 0: + if int(self.ask_channel("sens{0}:data:poin?")) == 0: return list() - return comma_sequence_to_list_of_floats( - self.ask_channel('sens{0}:data:rem?')) - - def measurement(self, delay_s: float = 0.0, repetitions: int = 1, - current_range: str = 'high', - aperture_s: Optional[float] = None, - nplc: Optional[int] = None - ) -> Measurement_Context: + return comma_sequence_to_list_of_floats(self.ask_channel("sens{0}:data:rem?")) + + def measurement( + self, + delay_s: float = 0.0, + repetitions: int = 1, + current_range: str = "high", + aperture_s: Optional[float] = None, + nplc: Optional[int] = None, + ) -> Measurement_Context: """Set up a sequence of current measurements Args: @@ -1334,14 +1338,12 @@ def measurement(self, delay_s: float = 0.0, repetitions: int = 1, ValueError: configuration error """ if aperture_s and nplc: - raise ValueError('Only one of nplc or aperture_s can be ' - 'specified for a current measurement') + raise ValueError("Only one of nplc or aperture_s can be " "specified for a current measurement") if not aperture_s and not nplc: nplc = 1 - return Measurement_Context(self, delay_s, repetitions, current_range, - aperture_s, nplc) + return Measurement_Context(self, delay_s, repetitions, current_range, aperture_s, nplc) - def output_mode(self, range: str = 'high', filter: str = 'high') -> None: + def output_mode(self, range: str = "high", filter: str = "high") -> None: """Set the output voltage Args: @@ -1351,10 +1353,15 @@ def output_mode(self, range: str = 'high', filter: str = 'high') -> None: self.output_range(range) self.output_filter(filter) - def dc_list(self, voltages: Sequence[float], repetitions: int = 1, - dwell_s: float = 1e-03, delay_s: float = 0, - backwards: bool = False, stepped: bool = False - ) -> List_Context: + def dc_list( + self, + voltages: Sequence[float], + repetitions: int = 1, + dwell_s: float = 1e-03, + delay_s: float = 0, + backwards: bool = False, + stepped: bool = False, + ) -> List_Context: """Set up a DC-list generator Args: @@ -1368,13 +1375,19 @@ def dc_list(self, voltages: Sequence[float], repetitions: int = 1, Returns: List_Context: context manager """ - return List_Context(self, voltages, repetitions, dwell_s, delay_s, - backwards, stepped) - - def dc_sweep(self, start_V: float, stop_V: float, points: int, - repetitions: int = 1, dwell_s: float = 1e-03, - delay_s: float = 0, backwards=False, stepped=True - ) -> Sweep_Context: + return List_Context(self, voltages, repetitions, dwell_s, delay_s, backwards, stepped) + + def dc_sweep( + self, + start_V: float, + stop_V: float, + points: int, + repetitions: int = 1, + dwell_s: float = 1e-03, + delay_s: float = 0, + backwards=False, + stepped=True, + ) -> Sweep_Context: """Set up a DC sweep Args: @@ -1390,16 +1403,21 @@ def dc_sweep(self, start_V: float, stop_V: float, points: int, Returns: Sweep_Context: context manager """ - return Sweep_Context(self, start_V, stop_V, points, repetitions, - dwell_s, delay_s, backwards, stepped) - - def square_wave(self, frequency_Hz: Optional[float] = None, - period_s: Optional[float] = None, repetitions: int = -1, - duty_cycle_percent: float = 50.0, kind: str = 'symmetric', - inverted: bool = False, span_V: float = 0.2, - offset_V: float = 0.0, delay_s: float = 0, - slew_V_s: Optional[float] = None - ) -> Square_Context: + return Sweep_Context(self, start_V, stop_V, points, repetitions, dwell_s, delay_s, backwards, stepped) + + def square_wave( + self, + frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, + repetitions: int = -1, + duty_cycle_percent: float = 50.0, + kind: str = "symmetric", + inverted: bool = False, + span_V: float = 0.2, + offset_V: float = 0.0, + delay_s: float = 0, + slew_V_s: Optional[float] = None, + ) -> Square_Context: """Set up a square-wave generator Args: @@ -1424,16 +1442,31 @@ def square_wave(self, frequency_Hz: Optional[float] = None, raise ValueError(error_ambiguous_wave) if not frequency_Hz and not period_s: frequency_Hz = 1000 - return Square_Context(self, frequency_Hz, repetitions, period_s, - duty_cycle_percent, kind, inverted, span_V, - offset_V, delay_s, slew_V_s) - - def sine_wave(self, frequency_Hz: Optional[float] = None, - period_s: Optional[float] = None, repetitions: int = -1, - inverted: bool = False, span_V: float = 0.2, - offset_V: float = 0.0, delay_s: float = 0, - slew_V_s: Optional[float] = None - ) -> Sine_Context: + return Square_Context( + self, + frequency_Hz, + repetitions, + period_s, + duty_cycle_percent, + kind, + inverted, + span_V, + offset_V, + delay_s, + slew_V_s, + ) + + def sine_wave( + self, + frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, + repetitions: int = -1, + inverted: bool = False, + span_V: float = 0.2, + offset_V: float = 0.0, + delay_s: float = 0, + slew_V_s: Optional[float] = None, + ) -> Sine_Context: """Set up a sine-wave generator Args: @@ -1456,15 +1489,20 @@ def sine_wave(self, frequency_Hz: Optional[float] = None, raise ValueError(error_ambiguous_wave) if not frequency_Hz and not period_s: frequency_Hz = 1000 - return Sine_Context(self, frequency_Hz, repetitions, period_s, - inverted, span_V, offset_V, delay_s, slew_V_s) - - def triangle_wave(self, frequency_Hz: Optional[float] = None, - period_s: Optional[float] = None, repetitions: int = -1, - duty_cycle_percent: float = 50.0, inverted: bool = False, - span_V: float = 0.2, offset_V: float = 0.0, - delay_s: float = 0, slew_V_s: Optional[float] = None - ) -> Triangle_Context: + return Sine_Context(self, frequency_Hz, repetitions, period_s, inverted, span_V, offset_V, delay_s, slew_V_s) + + def triangle_wave( + self, + frequency_Hz: Optional[float] = None, + period_s: Optional[float] = None, + repetitions: int = -1, + duty_cycle_percent: float = 50.0, + inverted: bool = False, + span_V: float = 0.2, + offset_V: float = 0.0, + delay_s: float = 0, + slew_V_s: Optional[float] = None, + ) -> Triangle_Context: """Set up a triangle-wave generator Args: @@ -1488,14 +1526,18 @@ def triangle_wave(self, frequency_Hz: Optional[float] = None, raise ValueError(error_ambiguous_wave) if not frequency_Hz and not period_s: frequency_Hz = 1000 - return Triangle_Context(self, frequency_Hz, repetitions, period_s, - duty_cycle_percent, inverted, span_V, - offset_V, delay_s, slew_V_s) - - def arbitrary_wave(self, trace_name: str, repetitions: int = 1, - scale: float = 1.0, offset_V: float = 0.0, - slew_V_s: Optional[float] = None - ) -> Awg_Context: + return Triangle_Context( + self, frequency_Hz, repetitions, period_s, duty_cycle_percent, inverted, span_V, offset_V, delay_s, slew_V_s + ) + + def arbitrary_wave( + self, + trace_name: str, + repetitions: int = 1, + scale: float = 1.0, + offset_V: float = 0.0, + slew_V_s: Optional[float] = None, + ) -> Awg_Context: """Set up an arbitrary-wave generator Args: @@ -1508,12 +1550,11 @@ def arbitrary_wave(self, trace_name: str, repetitions: int = 1, Returns: Awg_Context: context manager """ - return Awg_Context(self, trace_name, repetitions, scale, offset_V, - slew_V_s) + return Awg_Context(self, trace_name, repetitions, scale, offset_V, slew_V_s) def _set_fixed_voltage_immediately(self, v) -> None: - self.write(f'sour{self._channum}:volt:mode fix') - self.write(f'sour{self._channum}:volt {v}') + self.write(f"sour{self._channum}:volt:mode fix") + self.write(f"sour{self._channum}:volt {v}") def ask_channel(self, cmd: str) -> str: """Inject channel number into SCPI query @@ -1588,16 +1629,21 @@ def waveform(self, values: Sequence[float]) -> None: ValueError: size mismatch """ if len(values) != self.size: - raise ValueError(f'trace length {len(values)} does not match ' - f'allocated length {self.size}') + raise ValueError(f"trace length {len(values)} does not match " f"allocated length {self.size}") self._parent.write_floats(f'trac:data "{self.name}",', values) class Virtual_Sweep_Context: - def __init__(self, arrangement: 'Arrangement_Context', sweep: np.ndarray, - start_trigger: Optional[str], step_time_s: float, - step_trigger: Optional[str], repetitions: Optional[int]): + def __init__( + self, + arrangement: "Arrangement_Context", + sweep: np.ndarray, + start_trigger: Optional[str], + step_time_s: float, + step_trigger: Optional[str], + repetitions: Optional[int], + ): self._arrangement = arrangement self._sweep = sweep self._step_trigger = step_trigger @@ -1627,8 +1673,7 @@ def actual_values_V(self, contact: str) -> np.ndarray: return self._sweep[:, index] def start(self) -> None: - """Start the 2D sweep - """ + """Start the 2D sweep""" self._ensure_qdac_setup() trigger = self._arrangement.get_trigger_by_name(self._start_trigger_name) self._arrangement._qdac.trigger(trigger) @@ -1654,10 +1699,9 @@ def _route_inner_trigger(self) -> None: # All channels change in sync, so just use the first channel to make the # external trigger. channel = self._get_channel(0) - channel.write_channel(f'sour{"{0}"}:dc:mark:sst ' - f'{_trigger_context_to_value(trigger)}') + channel.write_channel(f'sour{"{0}"}:dc:mark:sst ' f"{_trigger_context_to_value(trigger)}") - def _get_channel(self, contact_index: int) -> 'QDac2Channel': + def _get_channel(self, contact_index: int) -> "QDac2Channel": channel_number = self._arrangement._channels[contact_index] qdac = self._arrangement._qdac return qdac.channel(channel_number) @@ -1668,21 +1712,24 @@ def _send_lists_to_qdac(self) -> None: def _send_list_to_qdac(self, contact_index, voltages): channel = self._get_channel(contact_index) - dc_list = channel.dc_list(voltages=voltages, dwell_s=self._step_time_s, - repetitions=self._repetitions) + dc_list = channel.dc_list(voltages=voltages, dwell_s=self._step_time_s, repetitions=self._repetitions) trigger = self._arrangement.get_trigger_by_name(self._start_trigger_name) dc_list.start_on(trigger) def _make_ready_to_start(self): # Bug circumvention for contact_index in range(self._arrangement.shape): channel = self._get_channel(contact_index) - channel.write_channel('sour{0}:dc:init') + channel.write_channel("sour{0}:dc:init") class Arrangement_Context: - def __init__(self, qdac: 'QDac2', contacts: Dict[str, int], - output_triggers: Optional[Dict[str, int]], - internal_triggers: Optional[Sequence[str]]): + def __init__( + self, + qdac: "QDac2", + contacts: dict[str, int], + output_triggers: Optional[dict[str, int]], + internal_triggers: Optional[Sequence[str]], + ): self._qdac = qdac self._fix_contact_order(contacts) self._allocate_triggers(internal_triggers, output_triggers) @@ -1713,9 +1760,7 @@ def contact_names(self) -> Sequence[str]: """ return self._contact_names - def _allocate_internal_triggers(self, - internal_triggers: Optional[Sequence[str]] - ) -> None: + def _allocate_internal_triggers(self, internal_triggers: Optional[Sequence[str]]) -> None: if not internal_triggers: return for name in internal_triggers: @@ -1747,7 +1792,7 @@ def set_virtual_voltage(self, contact: str, voltage: float) -> None: raise ValueError(f'No contact named "{contact}"') self._effectuate_virtual_voltage(index, voltage) - def set_virtual_voltages(self, contacts_to_voltages: Dict[str, float]) -> None: + def set_virtual_voltages(self, contacts_to_voltages: dict[str, float]) -> None: """Set virtual voltages on specific contacts in one go The actual voltage that each contact will receive depends on the @@ -1791,7 +1836,7 @@ def add_correction(self, contact: str, factors: Sequence[float]) -> None: multiplier[index] = factors self._correction = np.matmul(multiplier, self._correction) - def _fix_contact_order(self, contacts: Dict[str, int]) -> None: + def _fix_contact_order(self, contacts: dict[str, int]) -> None: self._contact_names = list() self._contacts = dict() self._channels = list() @@ -1846,12 +1891,12 @@ def get_trigger_by_name(self, name: str) -> QDac2Trigger_Context: try: return self._internal_triggers[name] except KeyError: - print(f'Internal triggers: {list(self._internal_triggers.keys())}') + print(f"Internal triggers: {list(self._internal_triggers.keys())}") raise def _all_channels_as_suffix(self) -> str: channels_str = ints_to_comma_separated_list(self.channel_numbers) - return f'(@{channels_str})' + return f"(@{channels_str})" def currents_A(self, nplc: int = 1, current_range: str = "low") -> Sequence[float]: """Measure currents on all contacts @@ -1861,22 +1906,26 @@ def currents_A(self, nplc: int = 1, current_range: str = "low") -> Sequence[floa current_range (str, optional): Current range (default low) """ channels_suffix = self._all_channels_as_suffix() - self._qdac.write(f'sens:rang {current_range},{channels_suffix}') - self._qdac.write(f'sens:nplc {nplc},{channels_suffix}') + self._qdac.write(f"sens:rang {current_range},{channels_suffix}") + self._qdac.write(f"sens:nplc {nplc},{channels_suffix}") # Discard first reading because of possible output-capacitor effects, etc slowest_line_freq_Hz = 50 sleep_s(1 / slowest_line_freq_Hz) - self._qdac.ask(f'read? {channels_suffix}') + self._qdac.ask(f"read? {channels_suffix}") # Then make a proper reading sleep_s((nplc + 1) / slowest_line_freq_Hz) - currents = self._qdac.ask(f'read? {channels_suffix}') + currents = self._qdac.ask(f"read? {channels_suffix}") return comma_sequence_to_list_of_floats(currents) - def virtual_sweep(self, contact: str, voltages: Sequence[float], - start_sweep_trigger: Optional[str] = None, - step_time_s: float = 1e-5, - step_trigger: Optional[str] = None, - repetitions: int = 1) -> Virtual_Sweep_Context: + def virtual_sweep( + self, + contact: str, + voltages: Sequence[float], + start_sweep_trigger: Optional[str] = None, + step_time_s: float = 1e-5, + step_trigger: Optional[str] = None, + repetitions: int = 1, + ) -> Virtual_Sweep_Context: """Sweep a contact to create a 1D sweep Args: @@ -1892,11 +1941,9 @@ def virtual_sweep(self, contact: str, voltages: Sequence[float], Virtual_Sweep_Context: context manager """ sweep = self._calculate_1d_values(contact, voltages) - return Virtual_Sweep_Context(self, sweep, start_sweep_trigger, - step_time_s, step_trigger, repetitions) + return Virtual_Sweep_Context(self, sweep, start_sweep_trigger, step_time_s, step_trigger, repetitions) - def _calculate_1d_values(self, contact: str, voltages: Sequence[float] - ) -> np.ndarray: + def _calculate_1d_values(self, contact: str, voltages: Sequence[float]) -> np.ndarray: original_voltage = self.virtual_voltage(contact) index = self._contact_index(contact) sweep = list() @@ -1906,12 +1953,17 @@ def _calculate_1d_values(self, contact: str, voltages: Sequence[float] self._virtual_voltages[index] = original_voltage return np.array(sweep) - def virtual_sweep2d(self, inner_contact: str, inner_voltages: Sequence[float], - outer_contact: str, outer_voltages: Sequence[float], - start_sweep_trigger: Optional[str] = None, - inner_step_time_s: float = 1e-5, - inner_step_trigger: Optional[str] = None, - repetitions: int = 1) -> Virtual_Sweep_Context: + def virtual_sweep2d( + self, + inner_contact: str, + inner_voltages: Sequence[float], + outer_contact: str, + outer_voltages: Sequence[float], + start_sweep_trigger: Optional[str] = None, + inner_step_time_s: float = 1e-5, + inner_step_trigger: Optional[str] = None, + repetitions: int = 1, + ) -> Virtual_Sweep_Context: """Sweep two contacts to create a 2D sweep Args: @@ -1927,15 +1979,14 @@ def virtual_sweep2d(self, inner_contact: str, inner_voltages: Sequence[float], Returns: Virtual_Sweep_Context: context manager """ - sweep = self._calculate_2d_values(inner_contact, inner_voltages, - outer_contact, outer_voltages) - return Virtual_Sweep_Context(self, sweep, start_sweep_trigger, - inner_step_time_s, inner_step_trigger, repetitions) + sweep = self._calculate_2d_values(inner_contact, inner_voltages, outer_contact, outer_voltages) + return Virtual_Sweep_Context( + self, sweep, start_sweep_trigger, inner_step_time_s, inner_step_trigger, repetitions + ) - def _calculate_2d_values(self, inner_contact: str, - inner_voltages: Sequence[float], - outer_contact: str, - outer_voltages: Sequence[float]) -> np.ndarray: + def _calculate_2d_values( + self, inner_contact: str, inner_voltages: Sequence[float], outer_contact: str, outer_voltages: Sequence[float] + ) -> np.ndarray: original_fast_voltage = self.virtual_voltage(inner_contact) original_slow_voltage = self.virtual_voltage(outer_contact) outer_index = self._contact_index(outer_contact) @@ -1950,12 +2001,17 @@ def _calculate_2d_values(self, inner_contact: str, self._virtual_voltages[outer_index] = original_slow_voltage return np.array(sweep) - def virtual_detune(self, contacts: Sequence[str], start_V: Sequence[float], - end_V: Sequence[float], steps: int, - start_trigger: Optional[str] = None, - step_time_s: float = 1e-5, - step_trigger: Optional[str] = None, - repetitions: int = 1) -> Virtual_Sweep_Context: + def virtual_detune( + self, + contacts: Sequence[str], + start_V: Sequence[float], + end_V: Sequence[float], + steps: int, + start_trigger: Optional[str] = None, + step_time_s: float = 1e-5, + step_trigger: Optional[str] = None, + repetitions: int = 1, + ) -> Virtual_Sweep_Context: """Sweep any number of contacts linearly from one set of values to another set of values Args: @@ -1970,19 +2026,19 @@ def virtual_detune(self, contacts: Sequence[str], start_V: Sequence[float], """ self._check_same_lengths(contacts, start_V, end_V) sweep = self._calculate_detune_values(contacts, start_V, end_V, steps) - return Virtual_Sweep_Context(self, sweep, start_trigger, step_time_s, - step_trigger, repetitions) + return Virtual_Sweep_Context(self, sweep, start_trigger, step_time_s, step_trigger, repetitions) @staticmethod def _check_same_lengths(contacts, start_V, end_V) -> None: n_contacts = len(contacts) if n_contacts != len(start_V): - raise ValueError(f'There must be exactly one voltage per contact: {start_V}') + raise ValueError(f"There must be exactly one voltage per contact: {start_V}") if n_contacts != len(end_V): - raise ValueError(f'There must be exactly one voltage per contact: {end_V}') + raise ValueError(f"There must be exactly one voltage per contact: {end_V}") - def _calculate_detune_values(self, contacts: Sequence[str], start_V: Sequence[float], - end_V: Sequence[float], steps: int): + def _calculate_detune_values( + self, contacts: Sequence[str], start_V: Sequence[float], end_V: Sequence[float], steps: int + ): original_voltages = [self.virtual_voltage(contact) for contact in contacts] indices = [self._contact_index(contact) for contact in contacts] sweep = list() @@ -2009,14 +2065,14 @@ def leakage(self, modulation_V: float, nplc: int = 2) -> np.ndarray: Returns: ndarray: contact-to-contact resistance in Ohms """ - steady_state_A, currents_matrix = self._leakage_currents(modulation_V, nplc, 'low') - with np.errstate(divide='ignore'): + steady_state_A, currents_matrix = self._leakage_currents(modulation_V, nplc, "low") + with np.errstate(divide="ignore"): return np.abs(modulation_V / diff_matrix(steady_state_A, currents_matrix)) - def _leakage_currents(self, modulation_V: float, nplc: int, - current_range: str - ) -> Tuple[Sequence[float], Sequence[Sequence[float]]]: - steady_state_A = self.currents_A(nplc, 'low') + def _leakage_currents( + self, modulation_V: float, nplc: int, current_range: str + ) -> tuple[Sequence[float], Sequence[Sequence[float]]]: + steady_state_A = self.currents_A(nplc, "low") currents_matrix = list() for index, channel_nr in enumerate(self.channel_numbers): original_V = self._virtual_voltages[index] @@ -2029,16 +2085,14 @@ def _leakage_currents(self, modulation_V: float, nplc: int, def _contact_index(self, contact: str) -> int: return self._contacts[contact] - def _allocate_triggers(self, internal_triggers: Optional[Sequence[str]], - output_triggers: Optional[Dict[str, int]] - ) -> None: - self._internal_triggers: Dict[str, QDac2Trigger_Context] = dict() + def _allocate_triggers( + self, internal_triggers: Optional[Sequence[str]], output_triggers: Optional[dict[str, int]] + ) -> None: + self._internal_triggers: dict[str, QDac2Trigger_Context] = dict() self._allocate_internal_triggers(internal_triggers) self._allocate_external_triggers(output_triggers) - def _allocate_external_triggers(self, output_triggers: - Optional[Dict[str, int]] - ) -> None: + def _allocate_external_triggers(self, output_triggers: Optional[dict[str, int]]) -> None: self._external_triggers = dict() if not output_triggers: return @@ -2071,7 +2125,7 @@ def __init__(self, name: str, address: str, **kwargs) -> None: **kwargs: additional argument to the Visa driver """ self._check_instrument_name(name) - super().__init__(name, address, terminator='\n', **kwargs) + super().__init__(name, address, terminator="\n", **kwargs) self._set_up_serial() self._set_up_debug_settings() self._set_up_channels() @@ -2088,7 +2142,7 @@ def n_channels(self) -> int: Returns: int: Number of channels """ - return len(self.submodules['channels']) + return len(self.submodules["channels"]) def channel(self, ch: int) -> QDac2Channel: """ @@ -2098,7 +2152,7 @@ def channel(self, ch: int) -> QDac2Channel: Returns: QDac2Channel: Visa representation of the channel """ - return getattr(self, f'ch{ch:02}') + return getattr(self, f"ch{ch:02}") @staticmethod def n_triggers() -> int: @@ -2121,7 +2175,7 @@ def n_external_outputs(self) -> int: Returns: int: Number of external output triggers """ - return len(self.submodules['external_triggers']) + return len(self.submodules["external_triggers"]) def allocate_trigger(self) -> QDac2Trigger_Context: """Allocate an internal trigger @@ -2137,7 +2191,7 @@ def allocate_trigger(self) -> QDac2Trigger_Context: try: number = self._internal_triggers.pop() except KeyError: - raise ValueError('no free internal triggers') + raise ValueError("no free internal triggers") return QDac2Trigger_Context(self, number) def free_trigger(self, trigger: QDac2Trigger_Context) -> None: @@ -2158,9 +2212,7 @@ def free_all_triggers(self) -> None: """ self._set_up_internal_triggers() - def connect_external_trigger(self, port: int, trigger: QDac2Trigger_Context, - width_s: float = 1e-6 - ) -> None: + def connect_external_trigger(self, port: int, trigger: QDac2Trigger_Context, width_s: float = 1e-6) -> None: """Route internal trigger to external trigger Args: @@ -2169,11 +2221,11 @@ def connect_external_trigger(self, port: int, trigger: QDac2Trigger_Context, width_s (float, optional): Output trigger width in seconds (default 1ms) """ internal = _trigger_context_to_value(trigger) - self.write(f'outp:trig{port}:sour int{internal}') - self.write(f'outp:trig{port}:widt {width_s}') + self.write(f"outp:trig{port}:sour int{internal}") + self.write(f"outp:trig{port}:widt {width_s}") def reset(self) -> None: - self.write('*rst') + self.write("*rst") sleep_s(5) def errors(self) -> str: @@ -2182,7 +2234,7 @@ def errors(self) -> str: Returns: str: Comma separated list of errors or '0, "No error"' """ - return self.ask('syst:err:all?') + return self.ask("syst:err:all?") def error(self) -> str: """Retrieve next error @@ -2190,7 +2242,7 @@ def error(self) -> str: Returns: str: The next error or '0, "No error"' """ - return self.ask('syst:err?') + return self.ask("syst:err?") def n_errors(self) -> int: """Peek at number of previous errors @@ -2198,7 +2250,7 @@ def n_errors(self) -> int: Returns: int: Number of errors """ - return int(self.ask('syst:err:coun?')) + return int(self.ask("syst:err:coun?")) def start_all(self) -> None: """Trigger the global SCPI bus (``*TRG``) @@ -2206,14 +2258,14 @@ def start_all(self) -> None: All generators, that have not been explicitly set to trigger on an internal or external trigger, will be started. """ - self.write('*trg') + self.write("*trg") def remove_traces(self) -> None: """Delete all trace definitions from the instrument This means that all AWGs loose their data. """ - self.write('trac:rem:all') + self.write("trac:rem:all") def traces(self) -> Sequence[str]: """List all defined traces @@ -2221,7 +2273,7 @@ def traces(self) -> Sequence[str]: Returns: Sequence[str]: trace names """ - return comma_sequence_to_list(self.ask('trac:cat?')) + return comma_sequence_to_list(self.ask("trac:cat?")) def allocate_trace(self, name: str, size: int) -> Trace_Context: """Reserve memory for a new trace @@ -2240,14 +2292,15 @@ def mac(self) -> str: Returns: str: Media Access Control (MAC) address of the instrument """ - mac = self.ask('syst:comm:lan:mac?') - return f'{mac[1:3]}-{mac[3:5]}-{mac[5:7]}-{mac[7:9]}-{mac[9:11]}' \ - f'-{mac[11:13]}' + mac = self.ask("syst:comm:lan:mac?") + return f"{mac[1:3]}-{mac[3:5]}-{mac[5:7]}-{mac[7:9]}-{mac[9:11]}" f"-{mac[11:13]}" - def arrange(self, contacts: Dict[str, int], - output_triggers: Optional[Dict[str, int]] = None, - internal_triggers: Optional[Sequence[str]] = None - ) -> Arrangement_Context: + def arrange( + self, + contacts: dict[str, int], + output_triggers: Optional[dict[str, int]] = None, + internal_triggers: Optional[Sequence[str]] = None, + ) -> Arrangement_Context: """An arrangement of contacts and triggers for virtual gates Each contact corresponds to a particular output channel. Each @@ -2271,8 +2324,7 @@ def arrange(self, contacts: Dict[str, int], Returns: Arrangement_Context: context manager """ - return Arrangement_Context(self, contacts, output_triggers, - internal_triggers) + return Arrangement_Context(self, contacts, output_triggers, internal_triggers) # ----------------------------------------------------------------------- # Instrument-wide functions @@ -2287,10 +2339,10 @@ def start_recording_scpi(self) -> None: Any previous recordings are removed. To inspect the SCPI commands sent to the instrument, call get_recorded_scpi_commands(). """ - self._scpi_sent: List[str] = list() + self._scpi_sent: list[str] = list() self._record_commands = True - def get_recorded_scpi_commands(self) -> List[str]: + def get_recorded_scpi_commands(self) -> list[str]: """ Returns: Sequence[str]: SCPI commands sent to the instrument @@ -2300,8 +2352,7 @@ def get_recorded_scpi_commands(self) -> List[str]: return commands def clear(self) -> None: - """Reset the VISA message queue of the instrument - """ + """Reset the VISA message queue of the instrument""" self.visa_handle.clear() def clear_read_queue(self) -> Sequence[str]: @@ -2361,12 +2412,12 @@ def write_floats(self, cmd: str, values: Sequence[float]) -> None: Remember to include separating space in command if needed. """ if self._no_binary_values: - compiled = f'{cmd}{floats_to_comma_separated_list(values)}' + compiled = f"{cmd}{floats_to_comma_separated_list(values)}" if self._record_commands: self._scpi_sent.append(compiled) return super().write(compiled) if self._record_commands: - self._scpi_sent.append(f'{cmd}{floats_to_comma_separated_list(values)}') + self._scpi_sent.append(f"{cmd}{floats_to_comma_separated_list(values)}") self.visa_handle.write_binary_values(cmd, values) # ----------------------------------------------------------------------- @@ -2383,40 +2434,36 @@ def _set_up_serial(self) -> None: self.visa_handle.baud_rate = 921600 # type: ignore def _check_for_wrong_model(self) -> None: - model = self.IDN()['model'] - if model != 'QDAC-II': - raise ValueError(f'Unknown model {model}. Are you using the right' - ' driver for your instrument?') + model = self.IDN()["model"] + if model != "QDAC-II": + raise ValueError(f"Unknown model {model}. Are you using the right" " driver for your instrument?") def _check_for_incompatiable_firmware(self) -> None: # Only compare the firmware, not the FPGA version - firmware = split_version_string_into_components(self.IDN()['firmware'])[1] - least_compatible_fw = '0.17.5' + firmware = split_version_string_into_components(self.IDN()["firmware"])[1] + least_compatible_fw = "0.17.5" if parse(firmware) < parse(least_compatible_fw): - raise ValueError(f'Incompatible firmware {firmware}. You need at ' - f'least {least_compatible_fw}') + raise ValueError(f"Incompatible firmware {firmware}. You need at " f"least {least_compatible_fw}") def _set_up_channels(self) -> None: - channels = ChannelList(self, 'Channels', QDac2Channel, - snapshotable=False) + channels = ChannelList(self, "Channels", QDac2Channel, snapshotable=False) for i in range(1, 24 + 1): - name = f'ch{i:02}' + name = f"ch{i:02}" channel = QDac2Channel(self, name, i) self.add_submodule(name, channel) channels.append(channel) channels.lock() - self.add_submodule('channels', channels) + self.add_submodule("channels", channels) def _set_up_external_triggers(self) -> None: - triggers = ChannelList(self, 'Channels', QDac2ExternalTrigger, - snapshotable=False) + triggers = ChannelList(self, "Channels", QDac2ExternalTrigger, snapshotable=False) for i in range(1, 5 + 1): - name = f'ext{i}' + name = f"ext{i}" trigger = QDac2ExternalTrigger(self, name, i) self.add_submodule(name, trigger) triggers.append(trigger) triggers.lock() - self.add_submodule('external_triggers', triggers) + self.add_submodule("external_triggers", triggers) def _set_up_internal_triggers(self) -> None: # A set of the available internal triggers @@ -2424,18 +2471,19 @@ def _set_up_internal_triggers(self) -> None: def _set_up_manual_triggers(self) -> None: self.add_parameter( - name='trigger', + name="trigger", # Manually trigger event set_parser=_trigger_context_to_value, - set_cmd='tint {}', + set_cmd="tint {}", ) def _set_up_simple_functions(self) -> None: - self.add_function('abort', call_cmd='abor') + self.add_function("abort", call_cmd="abor") def _check_instrument_name(self, name: str) -> None: if name.isidentifier(): return raise ValueError( f'Instrument name "{name}" is incompatible with QCoDeS parameter ' - 'generation (no spaces, punctuation, prepended numbers, etc)') + "generation (no spaces, punctuation, prepended numbers, etc)" + ) diff --git a/src/qumada/instrument/custom_drivers/ZI/MFLI.py b/src/qumada/instrument/custom_drivers/ZI/MFLI.py index ea5b515..523f1d8 100644 --- a/src/qumada/instrument/custom_drivers/ZI/MFLI.py +++ b/src/qumada/instrument/custom_drivers/ZI/MFLI.py @@ -182,13 +182,13 @@ def __init__( set_cmd=lambda x: self.instr.sigouts[0].range(x), docstring="Range of the output", ) - + self.add_parameter( - name = "output_enabled", - label = "Output Enabled", - get_cmd = lambda: self.instr.sigouts[0].on(), - set_cmd = lambda x: self.instr.sigouts[0].on(x), - docstring = "Turns Output1 on or off" + name="output_enabled", + label="Output Enabled", + get_cmd=lambda: self.instr.sigouts[0].on(), + set_cmd=lambda x: self.instr.sigouts[0].on(x), + docstring="Turns Output1 on or off", ) self.add_parameter( @@ -237,8 +237,8 @@ def __init__( ) def get_idn(self): - #TODO: Implement this function! - # /dev..../features/devtype + # TODO: Implement this function! + # /dev..../features/devtype # /dev..../features/serial # /dev..../features/options - return {} \ No newline at end of file + return {} diff --git a/src/qumada/instrument/mapping/QDevil/qdac2.py b/src/qumada/instrument/mapping/QDevil/qdac2.py index 4dfc8e8..7e911fe 100644 --- a/src/qumada/instrument/mapping/QDevil/qdac2.py +++ b/src/qumada/instrument/mapping/QDevil/qdac2.py @@ -22,8 +22,8 @@ import logging from qcodes.parameters import Parameter -from qumada.instrument.custom_drivers.QDevil.QDAC2 import QDac2 +from qumada.instrument.custom_drivers.QDevil.QDAC2 import QDac2 from qumada.instrument.mapping import QDAC2_MAPPING from qumada.instrument.mapping.base import InstrumentMapping @@ -160,7 +160,8 @@ def pulse( channel.dc_list( voltages=points, dwell_s=delay, - ) for channel, points in zip(channels, setpoints) + ) + for channel, points in zip(channels, setpoints) ] if sync_trigger is not None: @@ -186,10 +187,10 @@ def clean_generators(self): for dc_list in self.dc_lists: dc_list.abort() self.dc_lists = [] - + @staticmethod def query_instrument(parameters: list[Parameter]): - """ Check if all parameters are from the same instrument """ + """Check if all parameters are from the same instrument""" instruments = {parameter.root_instrument for parameter in parameters} if len(instruments) > 1: raise Exception( @@ -199,4 +200,3 @@ def query_instrument(parameters: list[Parameter]): qdac: QDac2 = instruments.pop() assert isinstance(qdac, QDac2) return qdac - \ No newline at end of file diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 676ec05..2505941 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -15,16 +15,16 @@ from qumada.instrument.buffers.buffer import map_triggers from qumada.instrument.mapping import map_terminals_gui from qumada.measurement.scripts import ( + Generic_1D_Hysteresis_buffered, + Generic_1D_parallel_asymm_Sweep, Generic_1D_Sweep, Generic_1D_Sweep_buffered, - Generic_1D_parallel_asymm_Sweep, - Generic_1D_Hysteresis_buffered, Generic_2D_Sweep_buffered, Generic_nD_Sweep, - Timetrace, - Timetrace_buffered, Generic_Pulsed_Measurement, Generic_Pulsed_Repeated_Measurement, + Timetrace, + Timetrace_buffered, ) from qumada.utils.ramp_parameter import ramp_or_set_parameter @@ -251,7 +251,7 @@ def timetrace( metadata=None, station=None, buffered=False, - buffer_settings: dict|None = None, + buffer_settings: dict | None = None, priorize_stored_value=False, ): """ """ @@ -304,7 +304,7 @@ def sweep_2D( metadata=None, station=None, buffered=False, - buffer_settings: dict|None = None, + buffer_settings: dict | None = None, priorize_stored_value=False, restore_state=True, ): @@ -330,7 +330,7 @@ def sweep_2D( fast_param.value - fast_param_range / 2.0, fast_param.value + fast_param_range / 2.0, fast_num_points ) if buffer_settings is None: - buffer_settings = self.buffer_settings + buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if buffered is True: if "num_points" in temp_buffer_settings.keys(): @@ -370,17 +370,18 @@ def sweep_2D( del self.states["_temp_2D"] return data - def sweep_parallel(self, - params: list[Parameter], - setpoints: list[list[float]]|None = None, - target_values: list[float]|None = None, - num_points: int = 100, - name = None, - metadata = None, - station = None, - priorize_stored_value = False, - **kwargs - ): + def sweep_parallel( + self, + params: list[Parameter], + setpoints: list[list[float]] | None = None, + target_values: list[float] | None = None, + num_points: int = 100, + name=None, + metadata=None, + station=None, + priorize_stored_value=False, + **kwargs, + ): """ Sweep multiple parameters in parallel. Provide either setpoints or target_values. Setpoints have to have the same length for all parameters. @@ -395,15 +396,14 @@ def sweep_parallel(self, if not isinstance(station, Station): raise TypeError("No valid station assigned!") if setpoints is None and target_values is None: - raise(Exception("Either setpoints or target_values have to be provided!")) + raise (Exception("Either setpoints or target_values have to be provided!")) if target_values is not None and setpoints is not None: - raise(Exception("Either setpoints or target_values have to be provided, not both!")) + raise (Exception("Either setpoints or target_values have to be provided, not both!")) if setpoints is None: assert len(params) == len(target_values) setpoints = [np.linspace(param(), target, num_points) for param, target in zip(params, target_values)] assert len(params) == len(setpoints) assert all([len(setpoint) == len(setpoints[0]) for setpoint in setpoints]) - for terminal in self.terminals.values(): for parameter in terminal.terminal_parameters.values(): @@ -414,36 +414,34 @@ def sweep_parallel(self, parameter.setpoints = setpoints[params.index(parameter)] script = Generic_1D_parallel_asymm_Sweep() script.setup( - self.save_to_dict(priorize_stored_value=priorize_stored_value), - metadata=metadata, - name=name, - **kwargs + self.save_to_dict(priorize_stored_value=priorize_stored_value), metadata=metadata, name=name, **kwargs ) mapping = self.instrument_parameters map_terminals_gui(station.components, script.gate_parameters, mapping) data = script.run() return data - def pulsed_measurement(self, - params: list[Parameter], - setpoints: list[list[float]], - repetitions: int = 1, - name = None, - metadata = None, - station = None, - buffer_settings: dict|None = None, - priorize_stored_value = False, - **kwargs, - ): + def pulsed_measurement( + self, + params: list[Parameter], + setpoints: list[list[float]], + repetitions: int = 1, + name=None, + metadata=None, + station=None, + buffer_settings: dict | None = None, + priorize_stored_value=False, + **kwargs, + ): if station is None: station = self.station if not isinstance(station, Station): raise TypeError("No valid station assigned!") assert len(params) == len(setpoints) assert all([len(setpoint) == len(setpoints[0]) for setpoint in setpoints]) - assert repetitions >= 1 + assert repetitions >= 1 if buffer_settings is None: - buffer_settings = self.buffer_settings + buffer_settings = self.buffer_settings temp_buffer_settings = deepcopy(buffer_settings) if "num_points" in temp_buffer_settings.keys(): @@ -470,7 +468,7 @@ def pulsed_measurement(self, script.setup( self.save_to_dict(priorize_stored_value=priorize_stored_value), metadata=metadata, - measurement_name=name, # achtung geändert! + measurement_name=name, # achtung geändert! repetitions=repetitions, buffer_settings=temp_buffer_settings, **self.buffer_script_setup, @@ -482,7 +480,7 @@ def pulsed_measurement(self, data = script.run() return data - + def create_hook(func, hook): """ Decorator to hook a function onto an existing function. @@ -753,7 +751,7 @@ def measured_ramp( metadata=None, backsweep=False, buffered=False, - buffer_settings: dict|None = None, + buffer_settings: dict | None = None, priorize_stored_value=False, ): if station is None: @@ -800,7 +798,7 @@ def measured_ramp( self._parent_device.save_to_dict(priorize_stored_value=priorize_stored_value), metadata=metadata, name=name, - iterations = 1, + iterations=1, buffer_settings=temp_buffer_settings, **self._parent_device.buffer_script_setup, ) @@ -856,7 +854,7 @@ def __call__(self, value=None, ramp=None): return self.value else: if ramp is True: - self.ramp(value) + self.ramp(value) else: self.value = value diff --git a/src/qumada/measurement/measurement.py b/src/qumada/measurement/measurement.py index 21e8c8a..1553e7a 100644 --- a/src/qumada/measurement/measurement.py +++ b/src/qumada/measurement/measurement.py @@ -179,7 +179,7 @@ def setup( *, add_script_to_metadata: bool = True, add_parameters_to_metadata: bool = True, - buffer_settings: dict|None = None, + buffer_settings: dict | None = None, measurement_name: str | None = None, **settings: dict, ) -> None: @@ -631,9 +631,10 @@ def initialize(self, dyn_ramp_to_val=False, inactive_dyn_channels: list | None = if self.properties[gate][parameter]["type"].find("comp") >= 0: try: for i in range(len(self.compensating_parameters)): - if self.compensating_parameters[i]["gate"] == gate and self.compensating_parameters[i][ - "parameter" - ] == parameter: + if ( + self.compensating_parameters[i]["gate"] == gate + and self.compensating_parameters[i]["parameter"] == parameter + ): break i = self.compensating_parameters.index({"gate": gate, "parameter": parameter}) leverarms = self.compensating_leverarms[i] @@ -652,8 +653,10 @@ def initialize(self, dyn_ramp_to_val=False, inactive_dyn_channels: list | None = # Get only the relevant list entries for the current parameter try: for i in range(len(self.dynamic_parameters)): - if self.dynamic_parameters[i]["gate"] == comped_param["gate"] and self.dynamic_parameters[i][ - "parameter"] == comped_param["parameter"]: + if ( + self.dynamic_parameters[i]["gate"] == comped_param["gate"] + and self.dynamic_parameters[i]["parameter"] == comped_param["parameter"] + ): comped_index = i break # comped_index = self.dynamic_parameters.index(comped_param) diff --git a/src/qumada/measurement/shuttling_scripts.py b/src/qumada/measurement/shuttling_scripts.py index e6a3db2..a12dec0 100644 --- a/src/qumada/measurement/shuttling_scripts.py +++ b/src/qumada/measurement/shuttling_scripts.py @@ -6,7 +6,7 @@ def shuttle_setpoints(offsets, amplitudes, num_periods, phases, sampling_rate, b def shuttling_sin(x, offset, amplitude, phase, reverse = False): if reverse: sign = -1 - else: + else: sign = 1 return amplitude*np.sin(sign*x+phase)+offset for i in range(0, 4): @@ -16,7 +16,7 @@ def shuttling_sin(x, offset, amplitude, phase, reverse = False): ) for i in range(5, 7): pulses.append([barrier_voltages[i-5] for _ in range(int(sampling_rate*duration))]) - return pulses + return pulses def ramping_setpoints(starts, stops, duration, sampling_rate): setpoints = [] @@ -229,5 +229,3 @@ def run(self): datasets.append(datasaver.dataset) self.clean_up() return datasets - - diff --git a/src/tests/instrument_test.py b/src/tests/instrument_test.py index 0f70356..cca5c6b 100644 --- a/src/tests/instrument_test.py +++ b/src/tests/instrument_test.py @@ -21,8 +21,8 @@ # pylint: disable=missing-function-docstring import pytest from qcodes.instrument import Instrument, VisaInstrument -from qcodes.parameters import Parameter from qcodes.instrument_drivers.mock_instruments import DummyInstrument +from qcodes.parameters import Parameter from qumada.instrument.instrument import is_instrument_class From 39de702c452731545b58c9ac2de73fb9323e8973 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:39:23 +0100 Subject: [PATCH 20/29] Changing faulty QCoDeS Version number in requirements --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b61e5cd..9cf8e18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dynamic = ["version"] requires-python = ">=3.9" dependencies = [ - "qcodes >= 0.48.0", + "qcodes >= 0.46.0", "qcodes_contrib_drivers >= 0.18.0", "matplotlib", "jsonschema", From 4df393f9a39cce5b5a0b201f9c1da203cb29905b Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:40:23 +0100 Subject: [PATCH 21/29] Removing path in example --- src/examples/buffered_dummy_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/buffered_dummy_example.py b/src/examples/buffered_dummy_example.py index 2b561a6..522786c 100644 --- a/src/examples/buffered_dummy_example.py +++ b/src/examples/buffered_dummy_example.py @@ -118,7 +118,7 @@ ) map_terminals_gui(station.components, script.gate_parameters) -map_triggers(station.components, path=r"C:\Users\till3\Documents\PythonScripts\tm.json") +map_triggers(station.components) # %% Run measurement script.run() From 92f35f267da6007117a1c1a59811649f29450156 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:45:45 +0100 Subject: [PATCH 22/29] Removing Shuttling_scripts file (which is WIP). --- src/qumada/measurement/shuttling_scripts.py | 231 -------------------- 1 file changed, 231 deletions(-) delete mode 100644 src/qumada/measurement/shuttling_scripts.py diff --git a/src/qumada/measurement/shuttling_scripts.py b/src/qumada/measurement/shuttling_scripts.py deleted file mode 100644 index a12dec0..0000000 --- a/src/qumada/measurement/shuttling_scripts.py +++ /dev/null @@ -1,231 +0,0 @@ -import numpy as np - - -def shuttle_setpoints(offsets, amplitudes, num_periods, phases, sampling_rate, barrier_voltages, duration, reverse = False): - pulses=[] - def shuttling_sin(x, offset, amplitude, phase, reverse = False): - if reverse: - sign = -1 - else: - sign = 1 - return amplitude*np.sin(sign*x+phase)+offset - for i in range(0, 4): - x_datapoints = np.linspace(0, 2*np.pi*num_periods, int(sampling_rate*duration)) - pulses.append( - shuttling_sin(x_datapoints, offsets[i], amplitudes[i], phases[i], reverse = reverse) - ) - for i in range(5, 7): - pulses.append([barrier_voltages[i-5] for _ in range(int(sampling_rate*duration))]) - return pulses - -def ramping_setpoints(starts, stops, duration, sampling_rate): - setpoints = [] - for start, stop in zip(starts, stops): - setpoints.append(np.linspace(start, stop, int(duration*sampling_rate))) - return setpoints - -def concatenate_setpoints(setpoint_arrays): - - num_gates = len(setpoint_arrays[0]) - conc_array = [[] for _ in range(num_gates)] - for i in range(len(setpoint_arrays)): - for j in range(len(setpoint_arrays[0])): - for val in setpoint_arrays[i][j]: - conc_array[j].append(val) - - return conc_array - - -def Generic_Pulsed_Measurement_w_parameter(device, add_parameter, add_parameter_setpoints, settings): - """ - Measurement script for buffered measurements with abritary setpoints. - Trigger Types: - "software": Sends a software command to each buffer and dynamic parameters - in order to start data acquisition and ramping. Timing - might be off slightly - "hardware": Expects a trigger command for each setpoint. Can be used - with a preconfigured hardware trigger (Todo), a method, - that starts a manually adjusted hardware trigger - (has to be passed as trigger_start() method to - measurement script) or a manual trigger. - "manual" : The trigger setup is done by the user. The measurent script will - just start the first ramp. Usefull for synchronized trigger outputs - as in the QDac. - trigger_start: A callable that triggers the trigger (called to start the measurement) - or the keyword "manual" when triggering is done by user. Defauls is manual. - trigger_reset (optional): Callable to reset the trigger. Default is NONE. - include_gate_name (optional): Appends name of ramped gates to measurement name. Default is TRUE. - reset_time: Time for ramping fast param back to the start value. - TODO: Add Time! - """ - meas - with meas.run() as datasaver: - for setpoint in add_parameter_setpoints: - - script = Generic_Pulsed_Measurement() - script.setup() - - - def run(self): - self.buffered = True - TRIGGER_TYPES = ["software", "hardware", "manual"] - trigger_start = self.settings.get("trigger_start", "manual") # TODO: this should be set elsewhere - trigger_reset = self.settings.get("trigger_reset", None) - trigger_type = _validate_mapping( - self.settings.get("trigger_type"), - TRIGGER_TYPES, - default="software", - default_key_error="software", - ) - self.repetitions = self.settings.get("repetitions", 1) - self.add_parameter = self.settings.get("add_parameter") - self.add_parameter_setpoints = self.settings.get("add_parameter_setpoints") - include_gate_name = self.settings.get("include_gate_name", True) - sync_trigger = self.settings.get("sync_trigger", None) - datasets = [] - timer = ElapsedTimeParameter("time") - self.generate_lists() - self.measurement_name = naming_helper(self, default_name="nD Sweep") - if include_gate_name: - gate_names = [gate["gate"] for gate in self.dynamic_parameters] - self.measurement_name += f" {gate_names}" - - meas = Measurement(name=self.measurement_name) - meas.register_parameter(self.add_parameter) - meas.register_parameter(timer) - for parameter in self.dynamic_parameters: - self.properties[parameter["gate"]][parameter["parameter"]]["_is_triggered"] = True - for dynamic_param in self.dynamic_channels: - meas.register_parameter( - dynamic_param, - setpoints=[ - timer, - ], - ) - - # ------------------- - static_gettables = [] - del_channels = [] - del_params = [] - for parameter, channel in zip(self.gettable_parameters, self.gettable_channels): - if is_bufferable(channel): - meas.register_parameter( - channel, - setpoints=[ - timer, - ], - ) - elif channel in self.static_channels: - del_channels.append(channel) - del_params.append(parameter) - meas.register_parameter( - channel, - setpoints=[ - timer, - ], - ) - parameter_value = self.properties[parameter["gate"]][parameter["parameter"]]["value"] - static_gettables.append((channel, [parameter_value for _ in range(self.buffered_num_points)])) - for channel in del_channels: - self.gettable_channels.remove(channel) - for param in del_params: - self.gettable_parameters.remove(param) - # -------------------------- - - self.initialize() - for c_param in self.active_compensating_channels: - meas.register_parameter( - c_param, - setpoints=[ - timer, - ], - ) - - instruments = {param.root_instrument for param in self.dynamic_channels} - time_setpoints = np.linspace(0, self._burst_duration, int(self.buffered_num_points)) - setpoints = [sweep.get_setpoints() for sweep in self.dynamic_sweeps] - compensating_setpoints = [] - for i in range(len(self.active_compensating_channels)): - index = self.compensating_channels.index(self.active_compensating_channels[i]) - active_setpoints = sum([sweep.get_setpoints() for sweep in self.compensating_sweeps[i]]) - active_setpoints += float(self.compensating_parameters_values[index]) - compensating_setpoints.append(active_setpoints) - if min(active_setpoints) < min(self.compensating_limits[index]) or max(active_setpoints) > max( - self.compensating_limits[index] - ): - raise Exception(f"Setpoints of compensating gate {self.compensating_parameters[index]} exceed limits!") - results = [] - with meas.run() as datasaver: - for k in range(self.repetitions): - self.initialize() - try: - trigger_reset() - except TypeError: - logger.info("No method to reset the trigger defined.") - self.ready_buffers() - for instr in instruments: - try: - instr._qumada_pulse( - parameters=[*self.dynamic_channels, *self.active_compensating_channels], - setpoints=[*setpoints, *compensating_setpoints], - delay=self._burst_duration / self.buffered_num_points, - sync_trigger=sync_trigger, - ) - except AttributeError as ex: - logger.error( - f"Exception: {instr} probably does not have a \ - a qumada_pulse method. Buffered measurements without \ - ramp method are no longer supported. \ - Use the unbuffered script!" - ) - raise ex - - if trigger_type == "manual": - logger.warning( - "You are using manual triggering. If you want to pulse parameters on multiple" - "instruments this can lead to delays and bad timing!" - ) - - if trigger_type == "hardware": - try: - trigger_start() - except NameError as ex: - print("Please set a trigger or define a trigger_start method") - raise ex - - elif trigger_type == "software": - for buffer in self.buffers: - buffer.force_trigger() - logger.warning( - "You are using software trigger, which \ - can lead to significant delays between \ - measurement instruments! Only recommended\ - for debugging." - ) - - while not all(buffer.is_finished() for buffer in list(self.buffers)): - sleep(0.1) - try: - trigger_reset() - except TypeError: - logger.info("No method to reset the trigger defined.") - - results.append(self.readout_buffers()) - average_results = [] - for i in range(len(results[0])): - helper_array = np.zeros(len(results[0][0][1])) - for meas_results in results: - helper_array += meas_results[i][1] - helper_array /= self.repetitions - average_results.append((meas_results[i][0], helper_array)) - - datasaver.add_result( - (timer, time_setpoints), - *(zip(self.dynamic_channels, setpoints)), - *(zip(self.active_compensating_channels, compensating_setpoints)), - *average_results, - *static_gettables, - ) - datasets.append(datasaver.dataset) - self.clean_up() - return datasets From b3fa6b581624f193526b8cc2bd972e29b8223d22 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:53:19 +0100 Subject: [PATCH 23/29] Precommit was complaining... --- src/qumada/instrument/buffers/buffer.py | 3 ++- src/qumada/instrument/custom_drivers/QDevil/QDAC2.py | 10 +++++----- src/qumada/measurement/device_object.py | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qumada/instrument/buffers/buffer.py b/src/qumada/instrument/buffers/buffer.py index 6c10583..722457f 100644 --- a/src/qumada/instrument/buffers/buffer.py +++ b/src/qumada/instrument/buffers/buffer.py @@ -20,6 +20,7 @@ from __future__ import annotations +import json import logging from abc import ABC, abstractmethod from collections.abc import Mapping @@ -31,7 +32,7 @@ logger = logging.getLogger(__name__) -import json + def is_bufferable(object: Instrument | Parameter): diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index 3afe53f..c30db11 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -3,10 +3,10 @@ import uuid from collections.abc import Sequence from time import sleep as sleep_s -from typing import Dict, List, NewType, Optional, Tuple +from typing import NewType, Optional import numpy as np -from packaging.version import Version, parse +from packaging.version import parse from pyvisa.errors import VisaIOError from qcodes.instrument.channel import ChannelList, InstrumentChannel from qcodes.instrument.visa import VisaInstrument @@ -1163,7 +1163,7 @@ def __init__(self, parent: "QDac2", name: str, channum: int): ) self.add_parameter( name="measurement_delay_s", - label=f"delay", + label="delay", unit="s", set_cmd="sens{1}:del {0}".format("{}", channum), get_cmd=f"sens{channum}:del?", @@ -1234,7 +1234,7 @@ def __init__(self, parent: "QDac2", name: str, channum: int): ) self.add_parameter( name="output_filter", - label=f"low-pass cut-off", + label="low-pass cut-off", unit="Hz", set_cmd="sour{1}:filt {0}".format("{}", channum), get_cmd=f"sour{channum}:filt?", @@ -1287,7 +1287,7 @@ def __init__(self, parent: "QDac2", name: str, channum: int): ) self.add_parameter( name="dc_mode", - label=f"DC mode", + label="DC mode", set_cmd="sour{1}:volt:mode {0}".format("{}", channum), get_cmd=f"sour{channum}:volt:mode?", vals=validators.Enum("fixed", "list", "sweep"), diff --git a/src/qumada/measurement/device_object.py b/src/qumada/measurement/device_object.py index 2505941..b88b30f 100644 --- a/src/qumada/measurement/device_object.py +++ b/src/qumada/measurement/device_object.py @@ -388,8 +388,8 @@ def sweep_parallel( If no setpoints are provided, the target_values will be used to create the setpoints. Ramps will start from the current value of the parameters then. Gettable parameters and break conditions will be set according to their state in the device object. - You can pass backsweep_after_break as a kwarg. If set to True, the sweep will continue in the opposite direction after - a break condition is reached. + You can pass backsweep_after_break as a kwarg. If set to True, the sweep will continue in the opposite + direction after a break condition is reached. """ if station is None: station = self.station @@ -447,7 +447,7 @@ def pulsed_measurement( if "num_points" in temp_buffer_settings.keys(): temp_buffer_settings["num_points"] = len(setpoints[0]) logger.warning( - f"Temporarily changed buffer settings to match the \ + "Temporarily changed buffer settings to match the \ number of points specified in the setpoints" ) else: From 68b668abf4d3c5a35d726353ad82b6d2115f0f00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:53:22 +0000 Subject: [PATCH 24/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qumada/instrument/buffers/buffer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qumada/instrument/buffers/buffer.py b/src/qumada/instrument/buffers/buffer.py index 722457f..a6527df 100644 --- a/src/qumada/instrument/buffers/buffer.py +++ b/src/qumada/instrument/buffers/buffer.py @@ -33,8 +33,6 @@ logger = logging.getLogger(__name__) - - def is_bufferable(object: Instrument | Parameter): """Checks if the instrument or parameter is bufferable using the qumada Buffer definition.""" if isinstance(object, Parameter): From 4485254c6bfa52132e9b4f56c20c595257b856b2 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:55:38 +0100 Subject: [PATCH 25/29] precommit keeps complaining... --- src/qumada/instrument/custom_drivers/QDevil/QDAC2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index c30db11..e5923e7 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -81,7 +81,8 @@ def split_version_string_into_components(version: str) -> list[str]: return version.split("-") -"""External input trigger +""" +External input trigger There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4). """ From 47825ef218b38876441e4d9468f05a2bc917c590 Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 17:57:51 +0100 Subject: [PATCH 26/29] precommit... --- .../instrument/custom_drivers/QDevil/QDAC2.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index e5923e7..6102fbe 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -1,17 +1,3 @@ -import abc -import itertools -import uuid -from collections.abc import Sequence -from time import sleep as sleep_s -from typing import NewType, Optional - -import numpy as np -from packaging.version import parse -from pyvisa.errors import VisaIOError -from qcodes.instrument.channel import ChannelList, InstrumentChannel -from qcodes.instrument.visa import VisaInstrument -from qcodes.utils import validators - # Version 1.2.0 # # Guiding principles for this driver for QDevil QDAC-II @@ -47,6 +33,22 @@ # - Detect and handle mixing of internal and external triggers (_trigger). # + +import abc +import itertools +import uuid +from collections.abc import Sequence +from time import sleep as sleep_s +from typing import NewType, Optional + +import numpy as np +from packaging.version import parse +from pyvisa.errors import VisaIOError +from qcodes.instrument.channel import ChannelList, InstrumentChannel +from qcodes.instrument.visa import VisaInstrument +from qcodes.utils import validators + + error_ambiguous_wave = "Only one of frequency_Hz or period_s can be " "specified for a wave form" From 43c1dc0dfb3802a50110d56a7a0c15eeb0e15586 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:57:54 +0000 Subject: [PATCH 27/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qumada/instrument/custom_drivers/QDevil/QDAC2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index 6102fbe..1231da5 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -48,7 +48,6 @@ from qcodes.instrument.visa import VisaInstrument from qcodes.utils import validators - error_ambiguous_wave = "Only one of frequency_Hz or period_s can be " "specified for a wave form" From 25e9bbe1b0ba90b705975a071ddb603fe723fe7b Mon Sep 17 00:00:00 2001 From: THuckemann Date: Thu, 31 Oct 2024 18:01:53 +0100 Subject: [PATCH 28/29] no idea... --- .../instrument/custom_drivers/QDevil/QDAC2.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index 1231da5..8edf781 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -1,3 +1,17 @@ +import abc +import itertools +import uuid +from collections.abc import Sequence +from time import sleep as sleep_s +from typing import NewType, Optional + +import numpy as np +from packaging.version import parse +from pyvisa.errors import VisaIOError +from qcodes.instrument.channel import ChannelList, InstrumentChannel +from qcodes.instrument.visa import VisaInstrument +from qcodes.utils import validators + # Version 1.2.0 # # Guiding principles for this driver for QDevil QDAC-II @@ -34,19 +48,6 @@ # -import abc -import itertools -import uuid -from collections.abc import Sequence -from time import sleep as sleep_s -from typing import NewType, Optional - -import numpy as np -from packaging.version import parse -from pyvisa.errors import VisaIOError -from qcodes.instrument.channel import ChannelList, InstrumentChannel -from qcodes.instrument.visa import VisaInstrument -from qcodes.utils import validators error_ambiguous_wave = "Only one of frequency_Hz or period_s can be " "specified for a wave form" @@ -82,11 +83,11 @@ def split_version_string_into_components(version: str) -> list[str]: return version.split("-") -""" -External input trigger -There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4). -""" +# External input trigger + +# There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4). + ExternalInput = NewType("ExternalInput", int) From 35a336c65941054feaf0ebc7c7855201d4ef20e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:01:56 +0000 Subject: [PATCH 29/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qumada/instrument/custom_drivers/QDevil/QDAC2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py index 8edf781..7ff7c47 100644 --- a/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py +++ b/src/qumada/instrument/custom_drivers/QDevil/QDAC2.py @@ -48,7 +48,6 @@ # - error_ambiguous_wave = "Only one of frequency_Hz or period_s can be " "specified for a wave form" @@ -83,7 +82,6 @@ def split_version_string_into_components(version: str) -> list[str]: return version.split("-") - # External input trigger # There are four 3V3 non-isolated triggers on the back (1, 2, 3, 4).