def get_raw(self) -> Tuple[Tuple[float, ...], Tuple[float, ...]]: measurement_mode = self.instrument.get_measurement_mode() if len(measurement_mode['channels']) != 2: raise ValueError('Two measurement channels are needed, one for ' 'gate current and other for source drain ' 'current.') smu = self.instrument.by_channel[measurement_mode['channels'][0]] if not smu.setup_fnc_already_run: raise Exception(f'Sweep setup has not yet been run successfully ' f'on {smu.full_name}') delay_time = smu.iv_sweep.step_delay() if smu._average_coefficient < 0: # negative coefficient means nplc and positive means just # averaging nplc = 128 * abs(smu._average_coefficient) power_line_time_period = 1 / smu.power_line_frequency calculated_time = 2 * nplc * power_line_time_period else: calculated_time = smu._average_coefficient * \ delay_time num_steps = smu.iv_sweep.sweep_steps() estimated_timeout = max(delay_time, calculated_time) * num_steps new_timeout = estimated_timeout * self._fudge format_and_mode = self.instrument.get_response_format_and_mode() fmt_format = format_and_mode['format'] fmt_mode = format_and_mode['mode'] try: self.root_instrument.write(MessageBuilder().fmt(1, 1).message) with self.root_instrument.timeout.set_to(new_timeout): raw_data = self.instrument.ask(MessageBuilder().xe().message) parsed_data = fmt_response_base_parser(raw_data) finally: self.root_instrument.write(MessageBuilder().fmt(fmt_format, fmt_mode).message) self.param1 = _FMTResponse( *[parsed_data[i][::3] for i in range(0, 4)]) self.param2 = _FMTResponse( *[parsed_data[i][1::3] for i in range(0, 4)]) self.source_voltage = _FMTResponse( *[parsed_data[i][2::3] for i in range(0, 4)]) self.shapes = ((len(self.source_voltage.value),),) * 2 self.setpoints = ((self.source_voltage.value,),) * 2 convert_dummy_val_to_nan(self.param1) convert_dummy_val_to_nan(self.param2) return self.param1.value, self.param2.value
def test_convert_dummy_val_to_nan(): status = ['C', 'V', 'N', 'V', 'N', 'N'] value = [0, 199.999e99, 1, 199.999e99, 2, 3] channel = [1, 1, 1, 1, 1, 1] param_type = ['V', 'V', 'V', 'V', 'V', 'V'] param = _FMTResponse(value, status, channel, param_type) convert_dummy_val_to_nan(param) assert math.isnan(param.value[1]) assert math.isnan(param.value[3])
def __init__(self, name: str, instrument: B1517A, **kwargs: Any): super().__init__(name, names=tuple(['param1', 'param2']), units=tuple(['A', 'A']), labels=tuple(['Param1 Current', 'Param2 Current']), shapes=((1, ), ) * 2, setpoint_names=(('Voltage', ), ) * 2, setpoint_labels=(('Voltage', ), ) * 2, setpoint_units=(('V', ), ) * 2, instrument=instrument, **kwargs) self.instrument: B1517A self.root_instrument: KeysightB1500 self.param1 = _FMTResponse(None, None, None, None) self.param2 = _FMTResponse(None, None, None, None) self.source_voltage = _FMTResponse(None, None, None, None) self._fudge: float = 1.5
def get_raw(self) -> Tuple[Tuple[float, ...], ...]: measurement_mode = self.instrument.get_measurement_mode() channels = measurement_mode['channels'] n_channels = len(channels) if n_channels < 1: raise ValueError('At least one measurement channel is needed for ' 'an IV sweep.') if (len(self.names) != n_channels or len(self.units) != n_channels or len(self.labels) != n_channels or len(self.shapes) != n_channels): raise ValueError( f"The number of `.names` ({len(self.names)}), " f"`.units` ({len(self.units)}), `.labels` (" f"{len(self.labels)}), or `.shapes` ({len(self.shapes)}) " f"of the {self.full_name} parameter " f"does not match the number of channels expected for the IV " f"sweep measurement, which is {n_channels}. One must set " f"enough names, units, and labels for all the channels that " f"are to be measured.") smu = self.instrument.by_channel[channels[0]] if not smu.setup_fnc_already_run: raise Exception(f'Sweep setup has not yet been run successfully ' f'on {smu.full_name}') delay_time = smu.iv_sweep.step_delay() if smu._average_coefficient < 0: # negative coefficient means nplc and positive means just # averaging, see B1517A.set_average_samples_for_high_speed_adc # for more info nplc = 128 * abs(smu._average_coefficient) power_line_time_period = 1 / smu.power_line_frequency calculated_time = 2 * nplc * power_line_time_period else: calculated_time = smu._average_coefficient * delay_time num_steps = smu.iv_sweep.sweep_steps() estimated_timeout = max(delay_time, calculated_time) * num_steps new_timeout = estimated_timeout * self._fudge format_and_mode = self.instrument.get_response_format_and_mode() fmt_format = format_and_mode['format'] fmt_mode = format_and_mode['mode'] try: self.root_instrument.write(MessageBuilder().fmt(1, 1).message) with self.root_instrument.timeout.set_to(new_timeout): raw_data = self.instrument.ask(MessageBuilder().xe().message) finally: self.root_instrument.write(MessageBuilder().fmt( fmt_format, fmt_mode).message) parsed_data = fmt_response_base_parser(raw_data) # The `4` comes from the len(_FMTResponse(None, None, None, None)), # the _FMTResponse tuple declares these items that the instrument # gives for each data point n_items_per_data_point = 4 # sourced voltage values are also returned, hence the `+1` n_all_data_channels = n_channels + 1 for channel_index in range(n_channels): parsed_data_items = [ parsed_data[i][channel_index::n_all_data_channels] for i in range(0, n_items_per_data_point) ] single_channel_data = _FMTResponse(*parsed_data_items) convert_dummy_val_to_nan(single_channel_data) # Store the results to `.param#` attributes for convenient access # to all the data, e.g. status of each value in the arrays setattr(self, f"param{channel_index+1}", single_channel_data) channel_values_to_return = tuple( getattr(self, f"param{n + 1}").value for n in range(n_channels)) source_voltage_index = n_channels parsed_source_voltage_items = [ parsed_data[i][source_voltage_index::n_all_data_channels] for i in range(0, n_items_per_data_point) ] self.source_voltage = _FMTResponse(*parsed_source_voltage_items) self.shapes = ((len(self.source_voltage.value), ), ) * n_channels self.setpoints = ((self.source_voltage.value, ), ) * n_channels return channel_values_to_return
def set_names_labels_and_units( self, names: Optional[Sequence[str]] = None, labels: Optional[Sequence[str]] = None, units: Optional[Sequence[str]] = None) -> None: """ Set names, labels, and units of the measured parts of the MultiParameter. If units are not provided, "A" will be used because this parameter measures currents. If labels are not provided, names will be used. If names are not provided, ``param#`` will be used as names; the number of those names will be the same as the number of measured channels that ``B1500.get_measurement_mode`` method returns. Note that it is possible to not provide names and provide labels at the same time. In case, neither names nor labels are provided, the labels will be generated as ``Param# Current``. The number of provided names, labels, and units must be the same. Moreover, that number has to be equal to the number of channels that ``B1500.get_measurement_mode`` method returns. It is recommended to set measurement mode and number of channels first, and only then call this method to provide names/labels/units. The name/label/unit of the setpoint of this parameter will also be updated to defaults dictated by the ``set_setpoint_name_label_and_unit`` method. Note that ``.shapes`` of this parameter will also be updated to be in sync with the number of names. """ measurement_mode = self.instrument.get_measurement_mode() channels = measurement_mode['channels'] if names is None: names = [f"param{n+1}" for n in range(len(channels))] if labels is None: labels = [ f"Param{n + 1} Current" for n in range(len(channels)) ] if labels is None: labels = tuple(names) if units is None: units = ['A'] * len(names) if len(labels) != len(names) or len(units) != len(names): raise ValueError( f"If provided, the number of names, labels, and units must be " f"the same, instead got {len(names)} names, {len(labels)} " f"labels, {len(units)} units.") if len(names) != len(channels): raise ValueError( f"The number of names ({len(names)}) does not match the number " f"of channels expected for the IV sweep measurement, " f"which is {len(channels)}. Please, when providing names, " f"provide them for every channel.") self.names = tuple(names) self.labels = tuple(labels) self.units = tuple(units) for n in range(len(channels)): setattr(self, f"param{n+1}", _FMTResponse(None, None, None, None)) self.shapes = ((1, ), ) * len(self.names) self.set_setpoint_name_label_and_unit()