def __init__(self, sample, readout=None): self.sample = sample if readout is None: self.readout = sample.readout else: self.readout = readout self.comment = None self.mode = None self.x_set_obj = None self.y_set_obj = None self.z_set_obj = None self.dirname = None self.data_dir = None self.plotSuffix = '' self.hold = False self.show_progress_bar = True self.ReadoutTrace = False self.open_qviewkit = True self.create_averaged_data = False self.qviewkit_singleInstance = True self._qvk_process = False self._plot_comment = '' self.multiplex_attribute = "readout_pulse_frequency" self.multiplex_unit = "Hz" self.init = iniTD(sample) self._measurement_object = Measurement() self._measurement_object.measurement_type = 'timedomain' self._measurement_object.sample = self.sample
def __init__(self, IV_Device, exp_name = '', sample = None): self.IVD = IV_Device self.exp_name = exp_name self._sample = sample self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._plot_comment="" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'transport' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode
def __init__(self, quartz=None, ohmmeter=None, mfc=None, film_name='', sample=None): self.quartz = quartz self.ohmmeter = ohmmeter self.mfc = mfc self.film_name = film_name self._sample = sample self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = False self._show_ideal = False self._fit_resistance = False self._fit_every = 1 self._fit_points = 5 self._p0 = None self._reference_uid = None self._plot_comment = "" self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'sputter_deposition' self._measurement_object.sample = self._sample self._qvk_process = True self._duration = 60. # Duration of monitoring self._resolution = 2. # How often values should be taken self._target_resistance = 1000. self._target_thickness = 20. self._target_marker = False self._depmon_queue = Queue( ) # for communication with the depmon thread
def __init__(self): self.exp_name = None self.dirname = "" # TODO new name self.comment = "" self.sourcecode = "" self.sample = None self._measurement_object = Measurement() self._measurement_object.measurement_type = 'TimeDomain' self._measurement_object.sample = self.sample # data self.coords = {} self.values = {}
def __init__(self, vna, exp_name = '', sample = None): self.vna = vna self.averaging_start_ready = "start_measurement" in self.vna.get_function_names() and "ready" in self.vna.get_function_names() if not self.averaging_start_ready: logging.warning(__name__ + ': With your VNA instrument driver (' + self.vna.get_type() + '), I can not see when a measurement is complete. So I only wait for a specific time and hope the VNA has finished. Please consider implemeting the necessary functions into your driver.') self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 #[Hz] self.tdx = 0.002 #[s] self.tdy = 0.002 #[s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._fit_resonator = False self._plot_comment="" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode
def __init__(self, vna, exp_name='', sample=None): self.vna = vna self.averaging_start_ready = "start_measurement" in self.vna.get_function_names( ) and "ready" in self.vna.get_function_names() if not self.averaging_start_ready: logging.warning( __name__ + ': With your VNA instrument driver (' + self.vna.get_type() + '), I can not see when a measurement is complete. So I only wait for a specific time and hope the VNA has finished. Please consider implemeting the necessary functions into your driver.' ) self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 #[Hz] self.tdx = 0.002 #[s] self.tdy = 0.002 #[s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._fit_resonator = False self._plot_comment = "" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode
def __init__(self, sig_analyzer, exp_name='', sample=None): self.sig_analyzer = sig_analyzer self.averaging_start_ready = "start_measurement" in self.sig_analyzer.get_function_names() \ and "ready" in self.sig_analyzer.get_function_names() if not self.averaging_start_ready: logging.warning( __name__ + ': With your signal analyzer driver (' + self.sig_analyzer.get_type() + '),' ' I can not see when a measurement is complete. ' 'So I only wait for a specific time and hope the VNA has finished. ' 'Please consider implemeting the necessary functions into your driver.' ) self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 # [Hz] self.tdx = 0.002 # [s] self.tdy = 0.002 # [s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._plot_comment = "" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False
class SputterMonitor(object): """ Sputter monitoring class, derived from spectroscopy.py monitors and stores sputter parameters live during deposition facilitates dynamic flow adjustment Example: m = SPUTTER_Monitor(quartz, ohmmeter, film_name, sample) m.set_filmparameters() ... see in corresponding docstring m.monitor_depo() Args: quartz (Instrument): The quartz oscillator instrument measuring thickness and rate. ohmmeter (Instrument): The resistance measurement instrument. mfc (Instrument): The mass flow controller instrument. film_name (str): A name can be given, that is added to the directory and filename. sample (Sample): A sample object can be given that is stored in the h5 file. Attributes: Should be listed here... Until then, use tabbing. """ def __init__(self, quartz=None, ohmmeter=None, mfc=None, film_name='', sample=None): self.quartz = quartz self.ohmmeter = ohmmeter self.mfc = mfc self.film_name = film_name self._sample = sample self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = False self._show_ideal = False self._fit_resistance = False self._fit_every = 1 self._fit_points = 5 self._p0 = None self._reference_uid = None self._plot_comment = "" self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'sputter_deposition' self._measurement_object.sample = self._sample self._qvk_process = True self._duration = 60. # Duration of monitoring self._resolution = 2. # How often values should be taken self._target_resistance = 1000. self._target_thickness = 20. self._target_marker = False self._depmon_queue = Queue( ) # for communication with the depmon thread def set_duration(self, duration=60.): """ Set the duration for the monitoring. Better too long than too short. Args: duration (float): Monitoring duration in seconds. """ self._duration = duration def get_duration(self): """ Get the duration for the monitoring. Returns: duration (float): Monitoring duration in seconds. """ return self._duration def set_resolution(self, resolution=2.0): """ Set the resolution for the monitoring. Every x second, the parameters will be read and stored. Args: resolution (float): Monitoring resolution in seconds. """ self._resolution = resolution def get_resolution(self): """ Get the resolution for the monitoring. Every x second, the parameters will be read and stored. Returns: resolution (float): Monitoring resolution in seconds. """ return self._resolution def set_filmparameters(self, resistance=1000., thickness=20., target_marker=False): """ Set the target parameters of the film to be deposited. They are used to calculate the ideal trend of the measured parameters and their deviation from the ideal. Args: resistance (float): The target sheet resistance in Ohm. thickness (float): The target thickness in nm. target_marker (bool): Display a cross marking the target resistance and thickness. """ self._target_resistance = resistance self._target_thickness = thickness self._target_marker = target_marker def get_filmparameters(self): """ Get the set film parameters. Returns: Tuple containing: [0]: target_resistance (float): The target sheet resistance in Ohm. [1]: target_thickness (float): The target thickness in nm. """ return self._target_resistance, self._target_thickness def ideal_resistance(self, thickness): """ Calculates the ideal resistance at a given thickness depending on the set film parameters. Args: thickness (float): Film thickness in nm for which the ideal resistance should be calculated. Returns: The ideal resistance as a float. """ try: return 1. / (thickness * (1. / self._target_resistance) / self._target_thickness) except: return np.nan def ideal_trend(self): """ Calculates a list of ideal resistances that is used as comparison in a view together with the real values. """ t = np.linspace(1, self._target_thickness, 300) return [t, self.ideal_resistance(t)] def set_fit(self, fit=False, fit_every=1, fit_points=5, p0=None): """ Settings for the fit used to estimate final resistance at target thickness and thickness at target resistance. Args: fit (bool): Turn on the fit. fit_every (int): Fit after every x measurement. fit_points (int): Fit to the last x points. p0 (list): Provide initial values for the curvefit. """ self._fit_resistance = fit self._fit_every = fit_every self._fit_points = fit_points self._p0 = p0 def set_reference_uid(self, uid=None): """ Set a UID to be shown as a reference trace in the "resistance_thickness" view. Args: uid (str): The UID of the h5 file from which the resistance and thickness data is to be displayed. """ try: ref = hdf.Data(qkit.fid[uid]) self._ref_resistance = ref.data.resistance[:] self._ref_thickness = ref.data.thickness[:] self._reference_uid = uid ref.close() return self._reference_uid except: self._reference_uid = None return "Invalid UID" def set_resistance_4W(self, four_wire=False): """ Sets 2 (default) or 4 wire measurement mode, if the device supports it. Args: four_wire = False """ self.ohmmeter.set_measure_4W(four_wire) def get_rate(self, test_thickness): """ Tests deposition rate. Args: test_thickness (in nm) """ self.quartz.set_timer_zero() self.quartz.set_thickness_zero() time.sleep(3) while self.quartz.get_thickness() < test_thickness * 1e-2: time.sleep(1) mins, secs = self.quartz.get_time().split(':') t = 60 * float(mins) + float(secs) rate = test_thickness / t return rate, t def _theory(self, thickness, thickness_start, conductivity, cond_per_layer): """ Calculate a prediction for the resistance at a certain film thickness, given the current thickness, current conductivity and added conductivity per layer. Args: thickness (float): Film thickness for which the resistance shall be predicted thickness_start (float): Film thickness at the start of the prediction. conductivity (float): Film conductivity at the start of the prediction. cond_per_layer (float): Added conductivity per layer for the remaining film growth. Returns: The projected film resistance (float). """ t_added = thickness - thickness_start try: return 1. / (conductivity + t_added * cond_per_layer) except: return np.nan def _linear(self, x, a, b): """ Helper function providing a linear fitting function. """ return a * x + b def _fit_layer(self, t_points, R_points): """ Fit the changing film resistance to extract the gained conductivity per layer. This is then used to project the final film resistance with the provided target film parameters. Args: t_points (numpy array): Thickness data from which the conductivity per layer is to be extracted. R_points (numpy array): Resistance data from which the conductivity per layer is to be extracted. Returns: Tuple containing: [0]: t_final (float): The predicted thickness at which the target resistance will be reached. [1]: R_final (float): The predicted resistance that will be reached at the target thickness. [2]: popt[0] (float): Conductivity per layer as obtained by linear fit. """ # ToDo: Store all fits, Store time of fit to sync it to other datasets, revise theory. for i in R_points: if i == 0: return [np.nan, np.nan, np.nan] try: c_target = 1. / self._target_resistance c_points = 1. / R_points popt, _ = curve_fit(self._linear, t_points, c_points, p0=self._p0) R_final = self._theory(self._target_thickness, t_points[0], c_points[0], popt[0]) t_final = ((c_target - c_points[-1]) / popt[0]) + t_points[-1] return [t_final, R_final, popt[0]] except: return [np.nan, np.nan, np.nan] def _reciprocal(self, thickness, cond_per_layer): """ Function used by the curvefit routine. Args: thickness: Film thickness in nm. cond_per_layer: Film conductance per layer. Returns: The resistance for the given parameters as a float. """ try: return 1. / (thickness * cond_per_layer) except: return np.nan def _fit_trend(self, t_points, R_points): """ Do the fit used to estimate final resistance at target thickness and thickness at target resistance. Args: t_points: The last x points of film thickness in nm. R_points: The last x points of film resistivity in Ohm. Returns: List of [R_final, t_final] with R_final (float): Estimated final resistance at target thickness. t_final (float): Estimated thickness at target resistance. """ for i in t_points: if i == 0: return [np.nan, np.nan] try: self._popt, _ = curve_fit(self._reciprocal, t_points, R_points, p0=self._p0) t_final = 1. / (self._target_resistance * self._popt[0]) R_final = 1. / (self._target_thickness * self._popt[0]) return [t_final, R_final] except: return [np.nan, np.nan] def _prepare_measurement_quartz(self): """ All the relevant settings from the quartz are updated and called. """ # self.quartz.get_all() # self.quartz.get_frequency() # Store it somewhere pass def _prepare_measurement_ohmmeter(self): """ All the relevant settings from the ohmmeter are updated and called. """ # self.ohmmeter.get_all() pass def _prepare_measurement_mfc(self): """ All the relevant settings from the ohmmeter are updated and called. """ # self.mfc.get_all() self.Ar_channel = self.mfc.predef_channels['Ar'] self.ArO_channel = self.mfc.predef_channels['ArO'] pass def _prepare_monitoring_file(self): """ Creates the output .h5-file with distinct the required datasets and views. At this point all measurement parameters are known and put in the output file. """ self._data_file = hdf.Data(name=self._file_name, mode='a') self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qkit.instruments.get_instrument_names( ) self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings self._write_settings_dataset() self._log = waf.open_log_file(self._data_file.get_filepath()) ''' Time record ''' self._data_time = self._data_file.add_value_vector('time', x=None, unit='s') ''' Quartz datasets ''' self._data_rate = self._data_file.add_value_vector( 'rate', x=self._data_time, unit='nm/s', save_timestamp=False) self._data_thickness = self._data_file.add_value_vector( 'thickness', x=self._data_time, unit='nm', save_timestamp=False) ''' Ohmmeter datasets ''' self._data_resistance = self._data_file.add_value_vector( 'resistance', x=self._data_time, unit='Ohm', save_timestamp=False) self._data_conductance = self._data_file.add_value_vector( 'conductance', x=self._data_time, unit='S', save_timestamp=False) self._data_deviation_abs = self._data_file.add_value_vector( 'deviation_absolute', x=self._data_time, unit='Ohm', save_timestamp=False) self._data_deviation_rel = self._data_file.add_value_vector( 'deviation_relative', x=self._data_time, unit='relative', save_timestamp=False) ''' MFC datasets ''' # FIXME: units? if self.mfc: self._data_pressure = self._data_file.add_value_vector( 'pressure', x=self._data_time, unit='ubar', save_timestamp=False) self._data_Ar_flow = self._data_file.add_value_vector( 'Ar_flow', x=self._data_time, unit='sccm', save_timestamp=False) self._data_ArO_flow = self._data_file.add_value_vector( 'ArO_flow', x=self._data_time, unit='sccm', save_timestamp=False) ''' Calculate ideal trend and create record ''' self._thickness_coord = self._data_file.add_coordinate( 'thickness_coord', unit='nm') self._thickness_coord.add(self.ideal_trend()[0]) if self._show_ideal: self._data_ideal = self._data_file.add_value_vector( 'ideal_resistance', x=self._thickness_coord, unit='Ohm', save_timestamp=False) self._data_ideal.append(self.ideal_trend()[1]) ''' Create target marker ''' if self._target_marker: self._target_thickness_line = self._data_file.add_value_vector( 'target_thickness', x=None, unit='nm', save_timestamp=False) self._target_thickness_line.append([ 0.8 * self._target_thickness, self._target_thickness, self._target_thickness, self._target_thickness, self._target_thickness, 1.2 * self._target_thickness ]) self._target_resistance_line = self._data_file.add_value_vector( 'target_resistance', x=None, unit='Ohm', save_timestamp=False) self._target_resistance_line.append([ self._target_resistance, self._target_resistance, 1.2 * self._target_resistance, 0.8 * self._target_resistance, self._target_resistance, self._target_resistance ]) self._target_conductance_line = self._data_file.add_value_vector( 'target_conductance', x=None, unit='S', save_timestamp=False) self._target_conductance_line.append([ 1. / self._target_resistance, 1. / self._target_resistance, 1. / 1.2 / self._target_resistance, 1. / 0.8 / self._target_resistance, 1. / self._target_resistance, 1. / self._target_resistance ]) ''' Estimation datasets ''' if self._fit_resistance: self._thickness_estimation = self._data_file.add_value_vector( 'thickness_estimation', x=None, unit='nm', save_timestamp=False) self._resistance_estimation = self._data_file.add_value_vector( 'resistance_estimation', x=None, unit='Ohm', save_timestamp=False) self._last_resistance_fit = self._data_file.add_value_vector( 'last_resistance_fit', x=None, unit='Ohm', save_timestamp=False) ''' Reference dataset ''' if not self._reference_uid == None: self._thickness_reference = self._data_file.add_value_vector( 'thickness_reference', x=None, unit='nm', save_timestamp=False) self._thickness_reference.append(self._ref_thickness) self._resistance_reference = self._data_file.add_value_vector( 'reference_' + self._reference_uid, x=None, unit='Ohm', save_timestamp=False) self._resistance_reference.append(self._ref_resistance) ''' Create Views ''' self._resist_view = self._data_file.add_view('resistance_thickness', x=self._data_thickness, y=self._data_resistance) if self._show_ideal: self._resist_view.add(x=self._thickness_coord, y=self._data_ideal) if self._target_marker: self._resist_view.add(x=self._target_thickness_line, y=self._target_resistance_line) if not self._reference_uid == None: self._resist_view.add(x=self._thickness_reference, y=self._resistance_reference) if self._fit_resistance: self._resist_view.add(x=self._thickness_coord, y=self._last_resistance_fit) self._conductance_view = self._data_file.add_view( 'conductance_thickness', x=self._data_thickness, y=self._data_conductance) if self._target_marker: self._conductance_view.add(x=self._target_thickness_line, y=self._target_conductance_line) self._deviation_abs_view = self._data_file.add_view( 'deviation_absolute', x=self._data_thickness, y=self._data_deviation_abs) self._deviation_rel_view = self._data_file.add_view( 'deviation_relative', x=self._data_thickness, y=self._data_deviation_rel) ''' Create comment ''' if self.comment: self._data_file.add_comment(self.comment) ''' Open GUI ''' if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() # terminate an old qviewkit instance def _write_settings_dataset(self): """ Writes a dataset containing the settings of the measurement instruments. """ self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def _init_depmon(self): """ Initializes the measurement, the required files and instruments. Should only be called by the main functions monitor_depo or start_depmon. """ self._measurement_object.measurement_func = 'sputter_monitoring' self._measurement_object.x_axis = 'time' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = True if not self.dirname: self.dirname = 'SPUTTER_monitoring' self._file_name = self.dirname.replace(' ', '').replace(',', '_') if self.film_name: self._file_name += '_' + self.film_name self.x_vec = np.arange(0, self._duration, self._resolution) self._prepare_measurement_quartz() self._prepare_measurement_ohmmeter() if self.mfc: self._prepare_measurement_mfc() self._prepare_monitoring_file() def _acquire(self, mon_data): """ Acquires and stores current information from the instruments. Should only be called by main functions monitor_depo or start_depmon. """ self._data_time.append(time.time() - mon_data.t0) # mon_data.resistance.append(self.ohmmeter.get_resistance()) mon_data.resistance = np.append(mon_data.resistance, self.ohmmeter.get_resistance()) if self.quartz: rate = self.quartz.get_rate(nm=True) # mon_data.thickness.append(self.quartz.get_thickness(nm=True)) mon_data.thickness = np.append(mon_data.thickness, self.quartz.get_thickness(nm=True)) if self.mfc: pressure = self.mfc.get_pressure() Ar_flow = self.mfc.get_flow(self.Ar_channel) ArO_flow = self.mfc.get_flow(self.ArO_channel) if mon_data.resistance[-1] > 1.e9: mon_data.resistance[-1] = np.nan self._data_resistance.append(mon_data.resistance[-1]) if not mon_data.resistance[-1] == 0: self._data_conductance.append(1. / mon_data.resistance[-1]) else: self._data_conductance.append(np.nan) if self.quartz: self._data_rate.append(rate) self._data_thickness.append(mon_data.thickness[-1]) if self.mfc: self._data_pressure.append(pressure) self._data_Ar_flow.append(Ar_flow) self._data_ArO_flow.append(ArO_flow) if self.quartz: deviation_abs = mon_data.resistance[-1] - self.ideal_resistance( mon_data.thickness[-1]) deviation_rel = deviation_abs / self._target_resistance self._data_deviation_abs.append(deviation_abs) self._data_deviation_rel.append(deviation_rel) if ((self._fit_resistance) and (mon_data.it % self._fit_every == 0) and (len(mon_data.resistance) >= self._fit_points)): """ # OLD ROUTINE estimation = self._fit_trend(mon_data.thickness[-self._fit_points:None], mon_data.resistance[-self._fit_points:None]) self._thickness_estimation.append(estimation[0]) self._resistance_estimation.append(estimation[1]) self._last_resistance_fit.append(self._reciprocal(self.ideal_trend()[0],self._popt[0]), reset=True) """ estimation = self._fit_layer( mon_data.thickness[-self._fit_points:None], mon_data.resistance[-self._fit_points:None]) self._thickness_estimation.append(estimation[0]) self._resistance_estimation.append(estimation[1]) self._last_resistance_fit.append(self._theory( self.ideal_trend()[0], mon_data.thickness[-1], 1. / mon_data.resistance[-1], estimation[2]), reset=True) def monitor_depo(self): """ Main sputter deposition monitoring function. Consider using the threaded version by calling start_depmon(). Records the film resistance, thickness and deposition rate live during the sputter process. Stores everything into a h5 file. Provides measures to estimate the resulting resistance of the final film and thereby facilitates live adjustments to the sputter parameters. Note: set_duration and set_resolution should be called before to set the monitoring length and time resolution. set_filmparameters should be called before to provide the actual target values. """ self._init_depmon() if self.progress_bar: self._p = Progress_Bar( self._duration / self._resolution, 'EVAP_timetrace ' + self.dirname, self._resolution) # FIXME: Doesn't make much sense... print('Monitoring deposition...') sys.stdout.flush() if self.open_qviewkit: self._qvk_process = qviewkit.plot( self._data_file.get_filepath(), datasets=['views/resistance_thickness']) try: """ Initialize the data lists. """ class MonData(object): resistance = np.array([]) thickness = np.array([]) mon_data = MonData() """ Main loop: """ mon_data.t0 = time.time( ) # note: Windows has limitations on time precision (about 16ms) for mon_data.it, _ in enumerate(self.x_vec): self._acquire(mon_data) if self.progress_bar: self._p.iterate() # calculate the time when the next iteration should take place ti = mon_data.t0 + (float(mon_data.it) + 1) * self._resolution # wait until the total dt(iteration) has elapsed while time.time() < ti: time.sleep(0.05) except KeyboardInterrupt: print('Monitoring interrupted by user.') except Exception as e: print(e) print(e.__doc__) print(e.message) finally: self._end_measurement() def _monitor_depo_bg(self): """ Main sputter deposition monitoring function, threaded version Should only be called by main function start_depmon. Records the film resistance, thickness and deposition rate live during the sputter process. Stores everything into a h5 file. Provides measures to estimate the resulting resistance of the final film and thereby facilitates live adjustments to the sputter parameters. Note: set_duration and set_resolution should be called before to set the monitoring length and time resolution. set_filmparameters should be called before to provide the actual target values. """ class StopPauseExeption(Exception): pass class StartPauseExeption(Exception): pass """ A few boilerplate functions for the thread management: continue, stop, status, etc... """ def stop(): raise StopIteration def do_nothing(): pass def status(): self._depmon_queue.put(loop_status) def cont(): raise StopPauseExeption def pause(): raise StartPauseExeption loop_status = 0 tasks = {} tasks[0] = stop tasks[1] = do_nothing tasks[2] = status tasks[3] = pause tasks[4] = cont """ Initialize the data lists. """ class MonData(object): resistance = np.array([]) thickness = np.array([]) mon_data = MonData() """ Main loop: """ mon_data.it = 0 mon_data.t0 = time.time( ) # note: Windows has limitations on time precision (about 16ms)? while True: self._acquire(mon_data) # Handle external commands try: tasks.get(self._depmon_queue.get(False), do_nothing)() self._depmon_queue.task_done() except Empty: pass except StartPauseExeption: print('monitoring paused') while True: try: time.sleep(0.1) tasks.get(self._depmon_queue.get(False), do_nothing)() self._depmon_queue.task_done() except StopPauseExeption: print('monitoring restarted') break except Empty: pass except StopPauseExeption: pass except StopIteration: break # calculate the time when the next iteration should take place mon_data.it += 1 ti = mon_data.t0 + (float(mon_data.it)) * self._resolution loop_status = ti # for now we simply return the runtime # wait until the total dt(iteration) has elapsed while time.time() < ti: time.sleep(0.05) self._end_measurement() def start_depmon(self): """ Starts the main sputter deposition monitoring function background process (depmon). Records the film resistance, thickness and deposition rate live during the sputter process. Stores everything into a h5 file. Provides measures to estimate the resulting resistance of the final film and thereby facilitates live adjustments to the sputter parameters. Note: set_duration and set_resolution should be called before to set the monitoring length and time resolution. set_filmparameters should be called before to provide the actual target values. Hint: Check with 'list_depmon_threads()' Stop with 'stop_depmon()' """ print('Monitoring deposition...') sys.stdout.flush() self._init_depmon() if self.open_qviewkit: self._qvk_process = qviewkit.plot( self._data_file.get_filepath(), datasets=['views/resistance_thickness'], refresh=1.) self._depmon = Thread(target=self._monitor_depo_bg, name="depmon-1") self._depmon.daemon = True self._depmon.start() def stop_depmon(self): """ Stop the deposition monitoring thread. Can be executed several times if more than one monitoring thread is running. Hint: Check with 'list_depmon_threads()' """ self._depmon_queue.put(0) def status_depmon(self): """ Give a heartbeat of the deposition monitoring thread (in the moment just the runtime). """ self._depmon_queue.put(2) print(self._depmon_queue.get()) self._depmon_queue.task_done() def pause_depmon(self): print('Pause Monitoring deposition...') self._depmon_queue.put(3) def continue_depmon(self): print('Continue Monitoring deposition...') self._depmon_queue.put(4) def list_depmon_threads(self): """ List all bg deposition monitoring threads. """ for thread in threading.enumerate(): if thread.getName()[:3] == "dep": print(thread) def _end_measurement(self): """ The data file is closed and file path is printed. """ print(self._data_file.get_filepath()) # qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) # #old version where we have to wait for the plots t = threading.Thread( target=qviewkit.save_plots, args=[self._data_file.get_filepath(), self._plot_comment]) t.start() self._data_file.close_file() waf.close_log_file(self._log) self.dirname = None
class spectrum(object): ''' usage: m = spectrum(vna = vna1) m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current',coil.set_current, unit = 'mA') m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency',mw_src1.set_frequency, unit = 'Hz') m.gen_fit_function(...) several times m.measure_XX() ''' def __init__(self, vna, exp_name='', sample=None): self.vna = vna self.averaging_start_ready = "start_measurement" in self.vna.get_function_names( ) and "ready" in self.vna.get_function_names() if not self.averaging_start_ready: logging.warning( __name__ + ': With your VNA instrument driver (' + self.vna.get_type() + '), I can not see when a measurement is complete. So I only wait for a specific time and hope the VNA has finished. Please consider implemeting the necessary functions into your driver.' ) self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 #[Hz] self.tdx = 0.002 #[s] self.tdy = 0.002 #[s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._fit_resonator = False self._plot_comment = "" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode def set_log_function(self, func=None, name=None, unit=None, log_dtype=None): ''' A function (object) can be passed to the measurement loop which is excecuted before every x iteration but after executing the x_object setter in 2D measurements and before every line (but after setting the x value) in 3D measurements. The return value of the function of type float or similar is stored in a value vector in the h5 file. Call without any arguments to delete all log functions. The timestamp is automatically saved. func: function object in list form name: name of logging parameter appearing in h5 file, default: 'log_param' unit: unit of logging parameter, default: '' log_dtype: h5 data type, default: 'f' (float32) ''' if name == None: try: name = ['log_param'] * len(func) except Exception: name = None if unit == None: try: unit = [''] * len(func) except Exception: unit = None if log_dtype == None: try: log_dtype = ['f'] * len(func) except Exception: log_dtype = None self.log_function = [] self.log_name = [] self.log_unit = [] self.log_dtype = [] if func != None: for i, f in enumerate(func): self.log_function.append(f) self.log_name.append(name[i]) self.log_unit.append(unit[i]) self.log_dtype.append(log_dtype[i]) def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit=""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: x_vec (array): conains the sweeping values x_coordname (string) x_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) x_unit (string): optional ''' self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj self.delete_fit_function() self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit=""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: y_vec (array): contains the sweeping values y_coordname (string) y_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) y_unit (string): optional ''' self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj self.delete_fit_function() self.y_unit = y_unit def gen_fit_function(self, curve_f, curve_p, units='', p0=[-1, 0.1, 7]): ''' curve_f: 'parab', 'hyp', specifies the fit function to be employed curve_p: set of points that are the basis for the fit in the format [[x1,x2,x3,...],[y1,y2,y3,...]], frequencies in Hz units: set this to 'Hz' in order to avoid large values that cause the fit routine to diverge p0 (optional): start parameters for the fit, must be an 1D array of length 3 ([a,b,c,d]), where for the parabula p0[3] will be ignored The parabolic function takes the form y = a*(x-b)**2 + c , where (a,b,c) = p0 The hyperbolic function takes the form y = sqrt[ a*(x-b)**2 + c ], where (a,b,c) = p0 adds a trace to landscape ''' if not self.landscape: self.landscape = [] x_fit = curve_p[0] if units == 'Hz': y_fit = np.array(curve_p[1]) * 1e-9 else: y_fit = np.array(curve_p[1]) try: multiplier = 1 if units == 'Hz': multiplier = 1e9 fit_fct = None if curve_f == 'parab': fit_fct = self.f_parab elif curve_f == 'hyp': fit_fct = self.f_hyp elif curve_f == 'lin': fit_fct = self.f_lin p0 = p0[:2] else: print 'function type not known...aborting' raise ValueError popt, pcov = curve_fit(fit_fct, x_fit, y_fit, p0=p0) self.landscape.append(multiplier * fit_fct(self.x_vec, *popt)) except Exception as message: print 'fit not successful:', message popt = p0 def _prepare_measurement_vna(self): ''' all the relevant settings from the vna are updated and called ''' self.vna.get_all() #ttip.get_temperature() self._nop = self.vna.get_nop() self._sweeptime_averages = self.vna.get_sweeptime_averages() self._freqpoints = self.vna.get_freqpoints() if self.averaging_start_ready: self.vna.pre_measurement() def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' self._data_file = hdf.Data(name=self._file_name) self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qt.instruments.get_instruments() self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings self._write_settings_dataset() self._log = waf.open_log_file(self._data_file.get_filepath()) if not self._scan_time: self._data_freq = self._data_file.add_coordinate('frequency', unit='Hz') self._data_freq.add(self._freqpoints) if self._scan_1D: self._data_real = self._data_file.add_value_vector( 'real', x=self._data_freq, unit='', save_timestamp=True) self._data_imag = self._data_file.add_value_vector( 'imag', x=self._data_freq, unit='', save_timestamp=True) self._data_amp = self._data_file.add_value_vector( 'amplitude', x=self._data_freq, unit='arb. unit', save_timestamp=True) self._data_pha = self._data_file.add_value_vector( 'phase', x=self._data_freq, unit='rad', save_timestamp=True) if self._scan_2D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit=self.x_unit) self._data_x.add(self.x_vec) self._data_amp = self._data_file.add_value_matrix( 'amplitude', x=self._data_x, y=self._data_freq, unit='arb. unit', save_timestamp=True) self._data_pha = self._data_file.add_value_matrix( 'phase', x=self._data_x, y=self._data_freq, unit='rad', save_timestamp=True) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append( self._data_file.add_value_vector( self.log_name[i], x=self._data_x, unit=self.log_unit[i], dtype=self.log_dtype[i])) if self._nop < 10: """creates view: plot middle point vs x-parameter, for qubit measurements""" self._data_amp_mid = self._data_file.add_value_vector( 'amplitude_midpoint', unit='arb. unit', x=self._data_x, save_timestamp=True) self._data_pha_mid = self._data_file.add_value_vector( 'phase_midpoint', unit='rad', x=self._data_x, save_timestamp=True) #self._view = self._data_file.add_view("amplitude vs. " + self.x_coordname, x = self._data_x, y = self._data_amp[self._nop/2]) if self._scan_3D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit=self.x_unit) self._data_x.add(self.x_vec) self._data_y = self._data_file.add_coordinate(self.y_coordname, unit=self.y_unit) self._data_y.add(self.y_vec) if self._nop == 0: #saving in a 2D matrix instead of a 3D box HR: does not work yet !!! test things before you put them online. self._data_amp = self._data_file.add_value_matrix( 'amplitude', x=self._data_x, y=self._data_y, unit='arb. unit', save_timestamp=False) self._data_pha = self._data_file.add_value_matrix( 'phase', x=self._data_x, y=self._data_y, unit='rad', save_timestamp=False) else: self._data_amp = self._data_file.add_value_box( 'amplitude', x=self._data_x, y=self._data_y, z=self._data_freq, unit='arb. unit', save_timestamp=False) self._data_pha = self._data_file.add_value_box( 'phase', x=self._data_x, y=self._data_y, z=self._data_freq, unit='rad', save_timestamp=False) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append( self._data_file.add_value_vector( self.log_name[i], x=self._data_x, unit=self.log_unit[i], dtype=self.log_dtype[i])) if self._scan_time: self._data_freq = self._data_file.add_coordinate('frequency', unit='Hz') self._data_freq.add([self.vna.get_centerfreq()]) self._data_time = self._data_file.add_coordinate('time', unit='s') self._data_time.add( np.arange(0, self._nop, 1) * self.vna.get_sweeptime() / (self._nop - 1)) self._data_x = self._data_file.add_coordinate('trace_number', unit='') self._data_x.add(np.arange(0, self.number_of_timetraces, 1)) self._data_amp = self._data_file.add_value_matrix( 'amplitude', x=self._data_x, y=self._data_time, unit='lin. mag.', save_timestamp=False) self._data_pha = self._data_file.add_value_matrix( 'phase', x=self._data_x, y=self._data_time, unit='rad.', save_timestamp=False) if self.comment: self._data_file.add_comment(self.comment) if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() #terminate an old qviewkit instance def _write_settings_dataset(self): self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def measure_1D(self, rescan=True, web_visible=True): ''' measure method to record a single (averaged) VNA trace, S11 or S21 according to the setting on the VNA rescan: If True (default), the averages on the VNA are cleared and a new measurement is started. If False, it will directly take the data from the VNA without waiting. ''' self._scan_1D = True self._scan_2D = False self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_1D' self._measurement_object.x_axis = 'frequency' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'VNA_tracedata' self._file_name = self.dirname.replace(' ', '').replace(',', '_') if self.exp_name: self._file_name += '_' + self.exp_name self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) print 'recording trace...' sys.stdout.flush() qt.mstart() if rescan: if self.averaging_start_ready: self.vna.start_measurement() ti = time() if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(), self.dirname, self.vna.get_sweeptime()) qt.msleep(.2) while not self.vna.ready(): if time() - ti > self.vna.get_sweeptime(query=False): if self.progress_bar: self._p.iterate() ti = time() qt.msleep(.2) if self.progress_bar: while self._p.progr < self._p.max_it: self._p.iterate() else: self.vna.avg_clear() if self.vna.get_averages() == 1 or self.vna.get_Average( ) == False: #no averaging if self.progress_bar: self._p = Progress_Bar(1, self.dirname, self.vna.get_sweeptime()) qt.msleep(self.vna.get_sweeptime()) #wait single sweep if self.progress_bar: self._p.iterate() else: #with averaging if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(), self.dirname, self.vna.get_sweeptime()) if "avg_status" in self.vna.get_function_names(): for a in range(self.vna.get_averages()): while self.vna.avg_status() <= a: qt.msleep( .2 ) #maybe one would like to adjust this at a later point if self.progress_bar: self._p.iterate() else: #old style for a in range(self.vna.get_averages()): qt.msleep(self.vna.get_sweeptime() ) #wait single sweep time if self.progress_bar: self._p.iterate() data_amp, data_pha = self.vna.get_tracedata() data_real, data_imag = self.vna.get_tracedata('RealImag') self._data_amp.append(data_amp) self._data_pha.append(data_pha) self._data_real.append(data_real) self._data_imag.append(data_imag) if self._fit_resonator: self._do_fit_resonator() qt.mend() self._end_measurement() def measure_2D(self, web_visible=True): ''' measure method to record a (averaged) VNA trace, S11 or S21 according to the setting on the VNA for all parameters x_vec in x_obj ''' if not self.x_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = True self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_2D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = 'frequency' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname self._file_name = '2D_' + self.dirname.replace(' ', '').replace( ',', '_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec), '2D VNA sweep ' + self.dirname, self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self._nop < 10: if self.open_qviewkit: self._qvk_process = qviewkit.plot( self._data_file.get_filepath(), datasets=['amplitude_midpoint', 'phase_midpoint']) else: if self.open_qviewkit: self._qvk_process = qviewkit.plot( self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) self._measure() def measure_3D(self, web_visible=True): ''' measure full window of vna while sweeping x_set_obj and y_set_obj with parameters x_vec/y_vec. sweep over y_set_obj is the inner loop, for every value x_vec[i] all values y_vec are measured. optional: measure method to perform the measurement according to landscape, if set self.span is the range (in units of the vertical plot axis) data is taken around the specified funtion(s) note: make sure to have properly set x,y vectors before generating traces ''' if not self.x_set_obj or not self.y_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = False self._scan_3D = True self._scan_time = False self._measurement_object.measurement_func = 'measure_3D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = self.y_coordname self._measurement_object.z_axis = 'frequency' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname + ', ' + self.y_coordname self._file_name = '3D_' + self.dirname.replace(' ', '').replace( ',', '_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar( len(self.x_vec) * len(self.y_vec), '3D VNA sweep ' + self.dirname, self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" """only middle point in freq array is plotted vs x and y""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) if self.landscape: self.center_freqs = np.array(self.landscape).T else: self.center_freqs = [] #load default sequence for i in range(len(self.x_vec)): self.center_freqs.append([0]) self._measure() def measure_timetrace(self, web_visible=True): ''' measure method to record a single VNA timetrace, this only makes sense when span is set to 0 Hz!, tested only with KEYSIGHT E5071C ENA and its corresponding qkit driver LGruenhaupt 11/2016 ''' if self.vna.get_span() > 0: print 'VNA span not set to 0 Hz... aborting' return self._scan_1D = False self._scan_2D = False self._scan_3D = False self._scan_time = True self._measurement_object.measurement_func = 'measure_timetrace' self._measurement_object.x_axis = 'time' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'VNA_timetrace' self._file_name = self.dirname.replace(' ', '').replace(',', '_') if self.exp_name: self._file_name += '_' + self.exp_name self.x_vec = np.arange(0, self.number_of_timetraces, 1) self._prepare_measurement_vna() self._prepare_measurement_file() if self.progress_bar: self._p = Progress_Bar(self.number_of_timetraces, 'VNA timetrace ' + self.dirname, self.vna.get_sweeptime_averages()) print 'recording timetrace(s)...' sys.stdout.flush() qt.mstart() try: """ loop: x_obj with parameters from x_vec """ for i, x in enumerate(self.x_vec): if self.log_function != None: for i, f in enumerate(self.log_function): self._log_value[i].append(float(f())) if self.averaging_start_ready: #LG 11/2016 self.vna.start_measurement() ti = time( ) #changed from time.time() to time() - LGruenhaupt OCT_2016 tp = time() #added to enable use of progress bar #self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime_averages()) moved to line 303 ''' This is to prevent the vna.ready() function from timing out. LG NOV/16 ''' if self.vna.get_Average(): print 'this function only makes sense without averaging' qt.mend() self._end_measuremt() else: #self._p = Progress_Bar(1,self.dirname,self.vna.get_sweeptime()) qt.msleep(self.vna.get_sweeptime()) #self._p.iterate() while not self.vna.ready(): qt.msleep( .2 ) #this is just to check if the measurement has finished data_amp, data_pha = self.vna.get_tracedata() self._data_amp.append(data_amp) self._data_pha.append(data_pha) qt.msleep() if self.progress_bar: self._p.iterate() else: print 'not implemented for this VNA, only works with Keysight ENA 5071C' qt.mend() self._end_measurement() except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() qt.mend() def _measure(self): ''' measures and plots the data depending on the measurement type. the measurement loops feature the setting of the objects and saving the data in the .h5 file. ''' qt.mstart() try: """ loop: x_obj with parameters from x_vec """ for ix, x in enumerate(self.x_vec): self.x_set_obj(x) sleep(self.tdx) if self.log_function != None: for i, f in enumerate(self.log_function): self._log_value[i].append(float(f())) if self._scan_3D: for y in self.y_vec: """ loop: y_obj with parameters from y_vec (only 3D measurement) """ if ( np.min( np.abs(self.center_freqs[ix] - y * np.ones(len(self.center_freqs[ix]))) ) > self.span / 2. ) and self.landscape: #if point is not of interest (not close to one of the functions) data_amp = np.zeros(int(self._nop)) data_pha = np.zeros(int( self._nop)) #fill with zeros else: self.y_set_obj(y) sleep(self.tdy) if self.averaging_start_ready: self.vna.start_measurement() qt.msleep( .2 ) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) #if "avg_status" in self.vna.get_function_names(): # while self.vna.avg_status() < self.vna.get_averages(): # qt.msleep(.2) #maybe one would like to adjust this at a later point """ measurement """ data_amp, data_pha = self.vna.get_tracedata() if self._nop == 0: # this does not work yet. print data_amp[0], data_amp, self._nop self._data_amp.append(data_amp[0]) self._data_pha.append(data_pha[0]) else: self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() """ filling of value-box is done here. after every y-loop the data is stored the next 2d structure """ self._data_amp.next_matrix() self._data_pha.next_matrix() if self._scan_2D: if self.averaging_start_ready: self.vna.start_measurement() qt.msleep( .2 ) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) """ measurement """ data_amp, data_pha = self.vna.get_tracedata() self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._nop < 10: #print data_amp[self._nop/2] self._data_amp_mid.append( float(data_amp[self._nop / 2])) self._data_pha_mid.append( float(data_pha[self._nop / 2])) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() qt.mend() def _end_measurement(self): ''' the data file is closed and filepath is printed ''' print self._data_file.get_filepath() #qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) #old version where we have to wait for the plots t = threading.Thread( target=qviewkit.save_plots, args=[self._data_file.get_filepath(), self._plot_comment]) t.start() self._data_file.close_file() waf.close_log_file(self._log) self.dirname = None if self.averaging_start_ready: self.vna.post_measurement() def set_resonator_fit(self, fit_resonator=True, fit_function='', f_min=None, f_max=None): ''' sets fit parameter for resonator fit_resonator (bool): True or False, default: True (optional) fit_function (string): function which will be fitted to the data (optional) f_min (float): lower frequency boundary for the fitting function, default: None (optional) f_max (float): upper frequency boundary for the fitting function, default: None (optional) fit types: 'lorentzian','skewed_lorentzian','circle_fit_reflection', 'circle_fit_notch','fano' ''' if not fit_resonator: self._fit_resonator = False return self._functions = { 'lorentzian': 0, 'skewed_lorentzian': 1, 'circle_fit_reflection': 2, 'circle_fit_notch': 3, 'fano': 5, 'all_fits': 5 } try: self._fit_function = self._functions[fit_function] except KeyError: logging.error( 'Fit function not properly set. Must be either \'lorentzian\', \'skewed_lorentzian\', \'circle_fit_reflection\', \'circle_fit_notch\', \'fano\', or \'all_fits\'.' ) else: self._fit_resonator = True self._f_min = f_min self._f_max = f_max def _do_fit_resonator(self): ''' calls fit function in resonator class fit function is specified in self.set_fit, with boundaries f_mim and f_max only the last 'slice' of data is fitted, since we fit live while measuring. ''' if self._fit_function == 0: #lorentzian self._resonator.fit_lorentzian(f_min=self._f_min, f_max=self._f_max) if self._fit_function == 1: #skewed_lorentzian self._resonator.fit_skewed_lorentzian(f_min=self._f_min, f_max=self._f_max) if self._fit_function == 2: #circle_reflection self._resonator.fit_circle(reflection=True, f_min=self._f_min, f_max=self._f_max) if self._fit_function == 3: #circle_notch self._resonator.fit_circle(notch=True, f_min=self._f_min, f_max=self._f_max) if self._fit_function == 4: #fano self._resonator.fit_fano(f_min=self._f_min, f_max=self._f_max) #if self._fit_function == 5: #all fits #self._resonator.fit_all_fits(f_min=self._f_min, f_max = self._f_max) def delete_fit_function(self, n=None): ''' delete single fit function n (with 0 being the first one generated) or the complete landscape for n not specified ''' if n: self.landscape = np.delete(self.landscape, n, axis=0) else: self.landscape = None def plot_fit_function(self, num_points=100): ''' try: x_coords = np.linspace(self.x_vec[0], self.x_vec[-1], num_points) except Exception as message: print 'no x axis information specified', message return ''' if self.landscape: for trace in self.landscape: try: #plt.clear() plt.plot(self.x_vec, trace) plt.fill_between(self.x_vec, trace + float(self.span) / 2, trace - float(self.span) / 2, alpha=0.5) except Exception: print 'invalid trace...skip' plt.axhspan(self.y_vec[0], self.y_vec[-1], facecolor='0.5', alpha=0.5) plt.show() else: print 'No trace generated.' def set_span(self, span): self.span = span def get_span(self): return self.span def set_tdx(self, tdx): self.tdx = tdx def set_tdy(self, tdy): self.tdy = tdy def get_tdx(self): return self.tdx def get_tdy(self): return self.tdy def f_parab(self, x, a, b, c): return a * (x - b)**2 + c def f_hyp(self, x, a, b, c): "hyperbolic function with the form y = sqrt[ a*(x-b)**2 + c ]" return np.sqrt(a * (x - b)**2 + c) def f_lin(self, x, a, b): return a * x + b def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment = comment
class spectrum(object): """ usage: similar to the old spectroscopy file.However class generates hdf vectors/matrices depending on the number of traces of your signal_analyzer. m = spectrum(sig_analyzer = specki) m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current',coil.set_current, unit = 'mA') m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency',mw_src1.set_frequency, unit = 'Hz') m.gen_fit_function(...) several times m.measure_XX() functions that must be additionally implemented in your signal analyzer's driver get_y_unit(trace) - returns y-unit of your trace get_active_traces() - returns number of traces get_trace_name(trace) - returns name of your trace """ def __init__(self, sig_analyzer, exp_name='', sample=None): self.sig_analyzer = sig_analyzer self.averaging_start_ready = "start_measurement" in self.sig_analyzer.get_function_names() \ and "ready" in self.sig_analyzer.get_function_names() if not self.averaging_start_ready: logging.warning( __name__ + ': With your signal analyzer driver (' + self.sig_analyzer.get_type() + '),' ' I can not see when a measurement is complete. ' 'So I only wait for a specific time and hope the VNA has finished. ' 'Please consider implemeting the necessary functions into your driver.' ) self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 # [Hz] self.tdx = 0.002 # [s] self.tdy = 0.002 # [s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._plot_comment = "" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False def set_log_function(self, func=None, name=None, unit=None, log_dtype=None): ''' A function (object) can be passed to the measurement loop which is excecuted before every x iteration but after executing the x_object setter in 2D measurements and before every line (but after setting the x value) in 3D measurements. The return value of the function of type float or similar is stored in a value vector in the h5 file. Call without any arguments to delete all log functions. The timestamp is automatically saved. func: function object in list form name: name of logging parameter appearing in h5 file, default: 'log_param' unit: unit of logging parameter, default: '' log_dtype: h5 data type, default: 'f' (float32) ''' if name == None: try: name = ['log_param'] * len(func) except Exception: name = None if unit == None: try: unit = [''] * len(func) except Exception: unit = None if log_dtype == None: try: log_dtype = ['f'] * len(func) except Exception: log_dtype = None self.log_function = [] self.log_name = [] self.log_unit = [] self.log_dtype = [] if func != None: for i, f in enumerate(func): self.log_function.append(f) self.log_name.append(name[i]) self.log_unit.append(unit[i]) self.log_dtype.append(log_dtype[i]) def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit=""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: x_vec (array): conains the sweeping values x_coordname (string) x_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) x_unit (string): optional ''' self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj self.delete_fit_function() self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit=""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: y_vec (array): contains the sweeping values y_coordname (string) y_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) y_unit (string): optional ''' self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj self.delete_fit_function() self.y_unit = y_unit def gen_fit_function(self, curve_f, curve_p, units='', p0=[-1, 0.1, 7]): ''' curve_f: 'parab', 'hyp', specifies the fit function to be employed curve_p: set of points that are the basis for the fit in the format [[x1,x2,x3,...],[y1,y2,y3,...]], frequencies in Hz units: set this to 'Hz' in order to avoid large values that cause the fit routine to diverge p0 (optional): start parameters for the fit, must be an 1D array of length 3 ([a,b,c,d]), where for the parabula p0[3] will be ignored The parabolic function takes the form y = a*(x-b)**2 + c , where (a,b,c) = p0 The hyperbolic function takes the form y = sqrt[ a*(x-b)**2 + c ], where (a,b,c) = p0 adds a trace to landscape ''' if not qkit.module_available("scipy"): raise ImportError('scipy not available.') if not self.landscape: self.landscape = [] x_fit = curve_p[0] if units == 'Hz': y_fit = np.array(curve_p[1]) * 1e-9 else: y_fit = np.array(curve_p[1]) try: multiplier = 1 if units == 'Hz': multiplier = 1e9 fit_fct = None if curve_f == 'parab': fit_fct = self.f_parab elif curve_f == 'hyp': fit_fct = self.f_hyp elif curve_f == 'lin': fit_fct = self.f_lin p0 = p0[:2] else: print('function type not known...aborting') raise ValueError popt, pcov = curve_fit(fit_fct, x_fit, y_fit, p0=p0) self.landscape.append(multiplier * fit_fct(self.x_vec, *popt)) except Exception as message: print('fit not successful:', message) popt = p0 def _prepare_measurement_sig_analyzer(self): ''' all the relevant settings from the vna are updated and called ''' self.sig_analyzer.get_all() # ttip.get_temperature() self._nop = self.sig_analyzer.get_nop() self._sweeptime_averages = self.sig_analyzer.get_sweeptime_averages() self._freqpoints = self.sig_analyzer.get_freqpoints() self.num_traces = self.sig_analyzer.get_active_traces() self.traces_names = [] self.units = [] for i in range(self.num_traces): self.traces_names.append(self.sig_analyzer.get_trace_name( i + 1)) # must be implemented in driver self.units.append( self.sig_analyzer.get_y_unit(i + 1)) # must also be impelmented if self.averaging_start_ready: self.sig_analyzer.pre_measurement() def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' self._data_file = hdf.Data(name=self._file_name, mode='a') self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qkit.instruments.get_instrument_names( ) self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings self._write_settings_dataset() self._log = waf.open_log_file(self._data_file.get_filepath()) self._data_freq = self._data_file.add_coordinate('frequency', unit='Hz') self._data_freq.add(self._freqpoints) self._data = [] # empty list as we have a variable number of channels if self._scan_1D: for i in range(self.num_traces): self._data.append( self._data_file.add_value_vector(self.traces_names[i], x=self._data_freq, unit=self.units[i], save_timestamp=True)) if self._scan_2D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit=self.x_unit) self._data_x.add(self.x_vec) for i in range(self.num_traces): self._data.append( self._data_file.add_value_matrix(self.traces_names[i], x=self._data_x, y=self._data_freq, unit=self.units[i], save_timestamp=True)) if self.log_function != None: # use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append( self._data_file.add_value_vector( self.log_name[i], x=self._data_x, unit=self.log_unit[i], dtype=self.log_dtype[i])) if self._scan_3D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit=self.x_unit) self._data_x.add(self.x_vec) self._data_y = self._data_file.add_coordinate(self.y_coordname, unit=self.y_unit) self._data_y.add(self.y_vec) for i in range(self.num_traces): self._data.append( self._data_file.add_value_box(self.traces_names[i], x=self._data_x, y=self.data_y, z=self._data_freq, unit=self.units[i], save_timestamp=True)) if self.log_function != None: # use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append( self._data_file.add_value_vector( self.log_name[i], x=self._data_x, unit=self.log_unit[i], dtype=self.log_dtype[i])) if self.comment: self._data_file.add_comment(self.comment) if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() # terminate an old qviewkit instance def _write_settings_dataset(self): self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def measure_1D(self, rescan=True, web_visible=True): ''' measure method to record a single (averaged) VNA trace, S11 or S21 according to the setting on the VNA rescan: If True (default), the averages on the VNA are cleared and a new measurement is started. If False, it will directly take the data from the VNA without waiting. ''' self._scan_1D = True self._scan_2D = False self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_1D' self._measurement_object.x_axis = 'frequency' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'signal_analyzer_tracedata' self._file_name = self.dirname.replace(' ', '').replace(',', '_') if self.exp_name: self._file_name += '_' + self.exp_name self._prepare_measurement_sig_analyzer() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=self.traces_names) print('recording trace...') sys.stdout.flush() qkit.flow.start() if rescan: if self.averaging_start_ready: self.sig_analyzer.start_measurement() ti = time() if self.progress_bar: self._p = Progress_Bar(self.sig_analyzer.get_averages(), self.dirname, self.sig_analyzer.get_sweeptime()) qkit.flow.sleep(.2) while not self.sig_analyzer.ready(): if time() - ti > self.sig_analyzer.get_sweeptime( query=False): if self.progress_bar: self._p.iterate() ti = time() qkit.flow.sleep(.2) if self.progress_bar: while self._p.progr < self._p.max_it: self._p.iterate() else: # not tested!!!!!! self.sig_analyzer.avg_clear() if self.sig_analyzer.get_averages( ) == 1 or self.sig_analyzer.get_Average( ) == False: # no averaging if self.progress_bar: self._p = Progress_Bar( 1, self.dirname, self.sig_analyzer.get_sweeptime()) qkit.flow.sleep( self.sig_analyzer.get_sweeptime()) # wait single sweep if self.progress_bar: self._p.iterate() else: # with averaging if self.progress_bar: self._p = Progress_Bar( self.sig_analyzer.get_averages(), self.dirname, self.sig_analyzer.get_sweeptime()) if "avg_status" in self.sig_analyzer.get_function_names(): for a in range(self.sig_analyzer.get_averages()): while self.sig_analyzer.avg_status() <= a: qkit.flow.sleep( .2 ) # maybe one would like to adjust this at a later point if self.progress_bar: self._p.iterate() else: # old style for a in range(self.sig_analyzer.get_averages()): qkit.flow.sleep(self.sig_analyzer.get_sweeptime() ) # wait single sweep time if self.progress_bar: self._p.iterate() for i in range(self.num_traces): self._data[i].append(self.sig_analyzer.get_tracedata(i + 1)) qkit.flow.end() self._end_measurement() def measure_2D(self, web_visible=True): ''' measure method to record a (averaged) VNA trace, S11 or S21 according to the setting on the VNA for all parameters x_vec in x_obj ''' if not self.x_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = True self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_2D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = 'frequency' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname self._file_name = '2D_' + self.dirname.replace(' ', '').replace( ',', '_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec), '2D signal analyzer sweep ' + self.dirname, self.sig_analyzer.get_sweeptime_averages()) self._prepare_measurement_sig_analyzer() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=self.traces_names) self._measure() def measure_3D(self, web_visible=True): ''' measure full window of vna while sweeping x_set_obj and y_set_obj with parameters x_vec/y_vec. sweep over y_set_obj is the inner loop, for every value x_vec[i] all values y_vec are measured. optional: measure method to perform the measurement according to landscape, if set self.span is the range (in units of the vertical plot axis) data is taken around the specified funtion(s) note: make sure to have properly set x,y vectors before generating traces ''' if not self.x_set_obj or not self.y_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = False self._scan_3D = True self._scan_time = False self._measurement_object.measurement_func = 'measure_3D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = self.y_coordname self._measurement_object.z_axis = 'frequency' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname + ', ' + self.y_coordname self._file_name = '3D_' + self.dirname.replace(' ', '').replace( ',', '_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar( len(self.x_vec) * len(self.y_vec), '3D signal analyzer sweep ' + self.dirname, self.sig_analyzer.get_sweeptime_averages()) self._prepare_measurement_sig_analyzer() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" """only middle point in freq array is plotted vs x and y""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=self.traces_names) ### ??? if self.landscape: self.center_freqs = np.array(self.landscape).T else: self.center_freqs = [] # load default sequence for i in range(len(self.x_vec)): self.center_freqs.append([0]) self._measure() def _measure(self): ''' measures and plots the data depending on the measurement type. the measurement loops feature the setting of the objects and saving the data in the .h5 file. ''' qkit.flow.start() try: """ loop: x_obj with parameters from x_vec """ for ix, x in enumerate(self.x_vec): self.x_set_obj(x) sleep(self.tdx) if self.log_function != None: for i, f in enumerate(self.log_function): self._log_value[i].append(float(f())) #### not tested if self._scan_3D: for y in self.y_vec: """ loop: y_obj with parameters from y_vec (only 3D measurement) """ if ( np.min( np.abs(self.center_freqs[ix] - y * np.ones(len(self.center_freqs[ix]))) ) > self.span / 2. ) and self.landscape: # if point is not of interest (not close to one of the functions) data = [] for i in range(self.num_traces): data.append(np.zeros(int(self._nop))) else: self.y_set_obj(y) sleep(self.tdy) if self.averaging_start_ready: self.sig_analyzer.start_measurement() qkit.flow.sleep( .2 ) # just to make sure, the ready command does not *still* show ready while not self.sig_analyzer.ready(): qkit.flow.sleep(.2) else: self.sig_analyzer.avg_clear() qkit.flow.sleep(self._sweeptime_averages) # if "avg_status" in self.sig_analyzer.get_function_names(): # while self.sig_analyzer.avg_status() < self.sig_analyzer.get_averages(): # qkit.flow.sleep(.2) #maybe one would like to adjust this at a later point """ measurement """ for i in range(self.num_traces): data[i] = self.sig_analyzer.get_tracedata(i + 1) self._data[i].append(data[i]) if self.progress_bar: self._p.iterate() qkit.flow.sleep() """ filling of value-box is done here. after every y-loop the data is stored the next 2d structure """ for i in range(self.num_traces): self._data[i].next_matrix() if self._scan_2D: if self.averaging_start_ready: self.sig_analyzer.start_measurement() qkit.flow.sleep( .2 ) # just to make sure, the ready command does not *still* show ready while not self.sig_analyzer.ready(): qkit.flow.sleep(.2) else: self.sig_analyzer.avg_clear() qkit.flow.sleep(self._sweeptime_averages) """ measurement """ for i in range(self.num_traces): self._data[i].append( self.sig_analyzer.get_tracedata(i + 1)) if self.progress_bar: self._p.iterate() qkit.flow.sleep() finally: self._end_measurement() qkit.flow.end() def _end_measurement(self): ''' the data file is closed and filepath is printed ''' print(self._data_file.get_filepath()) # qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) #old version where we have to wait for the plots t = threading.Thread( target=qviewkit.save_plots, args=[self._data_file.get_filepath(), self._plot_comment]) t.start() self._data_file.close_file() waf.close_log_file(self._log) self.dirname = None if self.averaging_start_ready: self.sig_analyzer.post_measurement() def delete_fit_function(self, n=None): ''' delete single fit function n (with 0 being the first one generated) or the complete landscape for n not specified ''' if n: self.landscape = np.delete(self.landscape, n, axis=0) else: self.landscape = None def plot_fit_function(self, num_points=100): ''' try: x_coords = np.linspace(self.x_vec[0], self.x_vec[-1], num_points) except Exception as message: print 'no x axis information specified', message return ''' if not qkit.module_available("matplotlib"): raise ImportError("matplotlib not found.") if self.landscape: for trace in self.landscape: try: # plt.clear() plt.plot(self.x_vec, trace) plt.fill_between(self.x_vec, trace + float(self.span) / 2, trace - float(self.span) / 2, alpha=0.5) except Exception: print('invalid trace...skip') plt.axhspan(self.y_vec[0], self.y_vec[-1], facecolor='0.5', alpha=0.5) plt.show() else: print('No trace generated.') def set_span(self, span): self.span = span def get_span(self): return self.span def set_tdx(self, tdx): self.tdx = tdx def set_tdy(self, tdy): self.tdy = tdy def get_tdx(self): return self.tdx def get_tdy(self): return self.tdy def f_parab(self, x, a, b, c): return a * (x - b)**2 + c def f_hyp(self, x, a, b, c): "hyperbolic function with the form y = sqrt[ a*(x-b)**2 + c ]" return np.sqrt(a * (x - b)**2 + c) def f_lin(self, x, a, b): return a * x + b def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment = comment
class spectrum(object): ''' usage: m = spectrum(vna = vna1) m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current',coil.set_current, unit = 'mA') m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency',mw_src1.set_frequency, unit = 'Hz') m.gen_fit_function(...) several times m.measure_XX() ''' def __init__(self, vna, exp_name = '', sample = None): self.vna = vna self.averaging_start_ready = "start_measurement" in self.vna.get_function_names() and "ready" in self.vna.get_function_names() if not self.averaging_start_ready: logging.warning(__name__ + ': With your VNA instrument driver (' + self.vna.get_type() + '), I can not see when a measurement is complete. So I only wait for a specific time and hope the VNA has finished. Please consider implemeting the necessary functions into your driver.') self.exp_name = exp_name self._sample = sample self.landscape = None self.span = 200e6 #[Hz] self.tdx = 0.002 #[s] self.tdy = 0.002 #[s] self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._fit_resonator = False self._plot_comment="" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'spectroscopy' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode def set_log_function(self, func=None, name = None, unit = None, log_dtype = None): ''' A function (object) can be passed to the measurement loop which is excecuted before every x iteration but after executing the x_object setter in 2D measurements and before every line (but after setting the x value) in 3D measurements. The return value of the function of type float or similar is stored in a value vector in the h5 file. Call without any arguments to delete all log functions. The timestamp is automatically saved. func: function object in list form name: name of logging parameter appearing in h5 file, default: 'log_param' unit: unit of logging parameter, default: '' log_dtype: h5 data type, default: 'f' (float32) ''' if name == None: try: name = ['log_param']*len(func) except Exception: name = None if unit == None: try: unit = ['']*len(func) except Exception: unit = None if log_dtype == None: try: log_dtype = ['f']*len(func) except Exception: log_dtype = None self.log_function = [] self.log_name = [] self.log_unit = [] self.log_dtype = [] if func != None: for i,f in enumerate(func): self.log_function.append(f) self.log_name.append(name[i]) self.log_unit.append(unit[i]) self.log_dtype.append(log_dtype[i]) def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: x_vec (array): conains the sweeping values x_coordname (string) x_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) x_unit (string): optional ''' self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj self.delete_fit_function() self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: y_vec (array): contains the sweeping values y_coordname (string) y_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) y_unit (string): optional ''' self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj self.delete_fit_function() self.y_unit = y_unit def gen_fit_function(self, curve_f, curve_p, units = '', p0 = [-1,0.1,7]): ''' curve_f: 'parab', 'hyp', specifies the fit function to be employed curve_p: set of points that are the basis for the fit in the format [[x1,x2,x3,...],[y1,y2,y3,...]], frequencies in Hz units: set this to 'Hz' in order to avoid large values that cause the fit routine to diverge p0 (optional): start parameters for the fit, must be an 1D array of length 3 ([a,b,c,d]), where for the parabula p0[3] will be ignored The parabolic function takes the form y = a*(x-b)**2 + c , where (a,b,c) = p0 The hyperbolic function takes the form y = sqrt[ a*(x-b)**2 + c ], where (a,b,c) = p0 adds a trace to landscape ''' if not self.landscape: self.landscape = [] x_fit = curve_p[0] if units == 'Hz': y_fit = np.array(curve_p[1])*1e-9 else: y_fit = np.array(curve_p[1]) try: if curve_f == 'parab': "remove the last item" #p0.pop() popt, pcov = curve_fit(self.f_parab, x_fit, y_fit, p0=p0) if units == 'Hz': self.landscape.append(1e9*self.f_parab(self.x_vec, *popt)) else: self.landscape.append(self.f_parab(self.x_vec, *popt)) elif curve_f == 'hyp': popt, pcov = curve_fit(self.f_hyp, x_fit, y_fit, p0=p0) print popt if units == 'Hz': self.landscape.append(1e9*self.f_hyp(self.x_vec, *popt)) else: self.landscape.append(self.f_hyp(self.x_vec, *popt)) else: print 'function type not known...aborting' raise ValueError except Exception as message: print 'fit not successful:', message popt = p0 def _prepare_measurement_vna(self): ''' all the relevant settings from the vna are updated and called ''' self.vna.get_all() #ttip.get_temperature() self._nop = self.vna.get_nop() self._sweeptime_averages = self.vna.get_sweeptime_averages() self._freqpoints = self.vna.get_freqpoints() if self.averaging_start_ready: self.vna.pre_measurement() def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' self._data_file = hdf.Data(name=self._file_name) self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qt.instruments.get_instruments() self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings self._write_settings_dataset() self._log = waf.open_log_file(self._data_file.get_filepath()) if not self._scan_time: self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') self._data_freq.add(self._freqpoints) if self._scan_1D: self._data_real = self._data_file.add_value_vector('real', x = self._data_freq, unit = '', save_timestamp = True) self._data_imag = self._data_file.add_value_vector('imag', x = self._data_freq, unit = '', save_timestamp = True) self._data_amp = self._data_file.add_value_vector('amplitude', x = self._data_freq, unit = 'arb. unit', save_timestamp = True) self._data_pha = self._data_file.add_value_vector('phase', x = self._data_freq, unit = 'rad', save_timestamp = True) if self._scan_2D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_freq, unit = 'arb. unit', save_timestamp = True) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_freq, unit='rad', save_timestamp = True) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self._nop < 10: """creates view: plot middle point vs x-parameter, for qubit measurements""" self._data_amp_mid = self._data_file.add_value_vector('amplitude_midpoint', unit = 'arb. unit', x = self._data_x, save_timestamp = True) self._data_pha_mid = self._data_file.add_value_vector('phase_midpoint', unit = 'rad', x = self._data_x, save_timestamp = True) #self._view = self._data_file.add_view("amplitude vs. " + self.x_coordname, x = self._data_x, y = self._data_amp[self._nop/2]) if self._scan_3D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) self._data_y = self._data_file.add_coordinate(self.y_coordname, unit = self.y_unit) self._data_y.add(self.y_vec) if self._nop == 0: #saving in a 2D matrix instead of a 3D box HR: does not work yet !!! test things before you put them online. self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_y, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_y, unit = 'rad', save_timestamp = False) else: self._data_amp = self._data_file.add_value_box('amplitude', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_box('phase', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'rad', save_timestamp = False) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self._scan_time: self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') self._data_freq.add([self.vna.get_centerfreq()]) self._data_time = self._data_file.add_coordinate('time', unit = 's') self._data_time.add(np.arange(0,self._nop,1)*self.vna.get_sweeptime()/(self._nop-1)) self._data_x = self._data_file.add_coordinate('trace_number', unit = '') self._data_x.add(np.arange(0, self.number_of_timetraces, 1)) self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_time, unit = 'lin. mag.', save_timestamp = False) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_time, unit = 'rad.', save_timestamp = False) if self.comment: self._data_file.add_comment(self.comment) if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() #terminate an old qviewkit instance def _write_settings_dataset(self): self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def measure_1D(self, rescan = True, web_visible = True): ''' measure method to record a single (averaged) VNA trace, S11 or S21 according to the setting on the VNA rescan: If True (default), the averages on the VNA are cleared and a new measurement is started. If False, it will directly take the data from the VNA without waiting. ''' self._scan_1D = True self._scan_2D = False self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_1D' self._measurement_object.x_axis = 'frequency' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'VNA_tracedata' self._file_name = self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) print 'recording trace...' sys.stdout.flush() qt.mstart() if rescan: if self.averaging_start_ready: self.vna.start_measurement() ti = time() if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime()) qt.msleep(.2) while not self.vna.ready(): if time()-ti > self.vna.get_sweeptime(query=False): if self.progress_bar: self._p.iterate() ti = time() qt.msleep(.2) if self.progress_bar: while self._p.progr < self._p.max_it: self._p.iterate() else: self.vna.avg_clear() if self.vna.get_averages() == 1 or self.vna.get_Average() == False: #no averaging if self.progress_bar:self._p = Progress_Bar(1,self.dirname,self.vna.get_sweeptime()) qt.msleep(self.vna.get_sweeptime()) #wait single sweep if self.progress_bar: self._p.iterate() else: #with averaging if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime()) if "avg_status" in self.vna.get_function_names(): for a in range(self.vna.get_averages()): while self.vna.avg_status() <= a: qt.msleep(.2) #maybe one would like to adjust this at a later point if self.progress_bar: self._p.iterate() else: #old style for a in range(self.vna.get_averages()): qt.msleep(self.vna.get_sweeptime()) #wait single sweep time if self.progress_bar: self._p.iterate() data_amp, data_pha = self.vna.get_tracedata() data_real, data_imag = self.vna.get_tracedata('RealImag') self._data_amp.append(data_amp) self._data_pha.append(data_pha) self._data_real.append(data_real) self._data_imag.append(data_imag) if self._fit_resonator: self._do_fit_resonator() qt.mend() self._end_measurement() def measure_2D(self, web_visible = True): ''' measure method to record a (averaged) VNA trace, S11 or S21 according to the setting on the VNA for all parameters x_vec in x_obj ''' if not self.x_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = True self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_2D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = 'frequency' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname self._file_name = '2D_' + self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec),'2D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self._nop < 10: if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude_midpoint', 'phase_midpoint']) else: if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) self._measure() def measure_3D(self, web_visible = True): ''' measure full window of vna while sweeping x_set_obj and y_set_obj with parameters x_vec/y_vec. sweep over y_set_obj is the inner loop, for every value x_vec[i] all values y_vec are measured. optional: measure method to perform the measurement according to landscape, if set self.span is the range (in units of the vertical plot axis) data is taken around the specified funtion(s) note: make sure to have properly set x,y vectors before generating traces ''' if not self.x_set_obj or not self.y_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = False self._scan_3D = True self._scan_time = False self._measurement_object.measurement_func = 'measure_3D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = self.y_coordname self._measurement_object.z_axis = 'frequency' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname + ', ' + self.y_coordname self._file_name = '3D_' + self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec)*len(self.y_vec),'3D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" """only middle point in freq array is plotted vs x and y""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) if self.landscape: self.center_freqs = np.array(self.landscape).T else: self.center_freqs = [] #load default sequence for i in range(len(self.x_vec)): self.center_freqs.append([0]) self._measure() def measure_timetrace(self, web_visible = True): ''' measure method to record a single VNA timetrace, this only makes sense when span is set to 0 Hz!, tested only with KEYSIGHT E5071C ENA and its corresponding qkit driver LGruenhaupt 11/2016 ''' if self.vna.get_span() > 0: print 'VNA span not set to 0 Hz... aborting' return self._scan_1D = False self._scan_2D = False self._scan_3D = False self._scan_time = True self._measurement_object.measurement_func = 'measure_timetrace' self._measurement_object.x_axis = 'time' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'VNA_timetrace' self._file_name = self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name self.x_vec = np.arange(0,self.number_of_timetraces,1) self._prepare_measurement_vna() self._prepare_measurement_file() if self.progress_bar: self._p = Progress_Bar(self.number_of_timetraces,'VNA timetrace '+self.dirname,self.vna.get_sweeptime_averages()) print 'recording timetrace(s)...' sys.stdout.flush() qt.mstart() try: """ loop: x_obj with parameters from x_vec """ for i, x in enumerate(self.x_vec): if self.log_function != None: for i,f in enumerate(self.log_function): self._log_value[i].append(float(f())) if self.averaging_start_ready: #LG 11/2016 self.vna.start_measurement() ti = time() #changed from time.time() to time() - LGruenhaupt OCT_2016 tp = time() #added to enable use of progress bar #self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime_averages()) moved to line 303 ''' This is to prevent the vna.ready() function from timing out. LG NOV/16 ''' if self.vna.get_Average(): print 'this function only makes sense without averaging' qt.mend() self._end_measuremt() else: #self._p = Progress_Bar(1,self.dirname,self.vna.get_sweeptime()) qt.msleep(self.vna.get_sweeptime()) #self._p.iterate() while not self.vna.ready(): qt.msleep(.2) #this is just to check if the measurement has finished data_amp, data_pha = self.vna.get_tracedata() self._data_amp.append(data_amp) self._data_pha.append(data_pha) qt.msleep() if self.progress_bar: self._p.iterate() else: print 'not implemented for this VNA, only works with Keysight ENA 5071C' qt.mend() self._end_measurement() except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() qt.mend() def _measure(self): ''' measures and plots the data depending on the measurement type. the measurement loops feature the setting of the objects and saving the data in the .h5 file. ''' qt.mstart() try: """ loop: x_obj with parameters from x_vec """ for ix, x in enumerate(self.x_vec): self.x_set_obj(x) sleep(self.tdx) if self.log_function != None: for i,f in enumerate(self.log_function): self._log_value[i].append(float(f())) if self._scan_3D: for y in self.y_vec: """ loop: y_obj with parameters from y_vec (only 3D measurement) """ if (np.min(np.abs(self.center_freqs[ix]-y*np.ones(len(self.center_freqs[ix])))) > self.span/2.) and self.landscape: #if point is not of interest (not close to one of the functions) data_amp = np.zeros(int(self._nop)) data_pha = np.zeros(int(self._nop)) #fill with zeros else: self.y_set_obj(y) sleep(self.tdy) if self.averaging_start_ready: self.vna.start_measurement() qt.msleep(.2) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) #if "avg_status" in self.vna.get_function_names(): # while self.vna.avg_status() < self.vna.get_averages(): # qt.msleep(.2) #maybe one would like to adjust this at a later point """ measurement """ data_amp, data_pha = self.vna.get_tracedata() if self._nop == 0: # this does not work yet. print data_amp[0], data_amp, self._nop self._data_amp.append(data_amp[0]) self._data_pha.append(data_pha[0]) else: self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() """ filling of value-box is done here. after every y-loop the data is stored the next 2d structure """ self._data_amp.next_matrix() self._data_pha.next_matrix() if self._scan_2D: if self.averaging_start_ready: self.vna.start_measurement() qt.msleep(.2) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) """ measurement """ data_amp, data_pha = self.vna.get_tracedata() self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._nop < 10: #print data_amp[self._nop/2] self._data_amp_mid.append(float(data_amp[self._nop/2])) self._data_pha_mid.append(float(data_pha[self._nop/2])) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() qt.mend() def _end_measurement(self): ''' the data file is closed and filepath is printed ''' print self._data_file.get_filepath() #qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) #old version where we have to wait for the plots t = threading.Thread(target=qviewkit.save_plots,args=[self._data_file.get_filepath(),self._plot_comment]) t.start() self._data_file.close_file() waf.close_log_file(self._log) self.dirname = None if self.averaging_start_ready: self.vna.post_measurement() def set_resonator_fit(self,fit_resonator=True,fit_function='',f_min=None,f_max=None): ''' sets fit parameter for resonator fit_resonator (bool): True or False, default: True (optional) fit_function (string): function which will be fitted to the data (optional) f_min (float): lower frequency boundary for the fitting function, default: None (optional) f_max (float): upper frequency boundary for the fitting function, default: None (optional) fit types: 'lorentzian','skewed_lorentzian','circle_fit_reflection', 'circle_fit_notch','fano' ''' if not fit_resonator: self._fit_resonator = False return self._functions = {'lorentzian':0,'skewed_lorentzian':1,'circle_fit_reflection':2,'circle_fit_notch':3,'fano':5,'all_fits':5} try: self._fit_function = self._functions[fit_function] except KeyError: logging.error('Fit function not properly set. Must be either \'lorentzian\', \'skewed_lorentzian\', \'circle_fit_reflection\', \'circle_fit_notch\', \'fano\', or \'all_fits\'.') else: self._fit_resonator = True self._f_min = f_min self._f_max = f_max def _do_fit_resonator(self): ''' calls fit function in resonator class fit function is specified in self.set_fit, with boundaries f_mim and f_max only the last 'slice' of data is fitted, since we fit live while measuring. ''' if self._fit_function == 0: #lorentzian self._resonator.fit_lorentzian(f_min=self._f_min, f_max = self._f_max) if self._fit_function == 1: #skewed_lorentzian self._resonator.fit_skewed_lorentzian(f_min=self._f_min, f_max = self._f_max) if self._fit_function == 2: #circle_reflection self._resonator.fit_circle(reflection = True, f_min=self._f_min, f_max = self._f_max) if self._fit_function == 3: #circle_notch self._resonator.fit_circle(notch = True, f_min=self._f_min, f_max = self._f_max) if self._fit_function == 4: #fano self._resonator.fit_fano(f_min=self._f_min, f_max = self._f_max) #if self._fit_function == 5: #all fits #self._resonator.fit_all_fits(f_min=self._f_min, f_max = self._f_max) def delete_fit_function(self, n = None): ''' delete single fit function n (with 0 being the first one generated) or the complete landscape for n not specified ''' if n: self.landscape = np.delete(self.landscape, n, axis=0) else: self.landscape = None def plot_fit_function(self, num_points = 100): ''' try: x_coords = np.linspace(self.x_vec[0], self.x_vec[-1], num_points) except Exception as message: print 'no x axis information specified', message return ''' if self.landscape: for trace in self.landscape: try: #plt.clear() plt.plot(self.x_vec, trace) plt.fill_between(self.x_vec, trace+float(self.span)/2, trace-float(self.span)/2, alpha=0.5) except Exception: print 'invalid trace...skip' plt.axhspan(self.y_vec[0], self.y_vec[-1], facecolor='0.5', alpha=0.5) plt.show() else: print 'No trace generated.' def set_span(self, span): self.span = span def get_span(self): return self.span def set_tdx(self, tdx): self.tdx = tdx def set_tdy(self, tdy): self.tdy = tdy def get_tdx(self): return self.tdx def get_tdy(self): return self.tdy def f_parab(self,x,a,b,c): return a*(x-b)**2+c def f_hyp(self,x,a,b,c): "hyperbolic function with the form y = sqrt[ a*(x-b)**2 + c ]" return np.sqrt(a*(x-b)**2+c) def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment=comment
class QmQkitWrapper: def __init__(self): self.exp_name = None self.dirname = "" # TODO new name self.comment = "" self.sourcecode = "" self.sample = None self._measurement_object = Measurement() self._measurement_object.measurement_type = 'TimeDomain' self._measurement_object.sample = self.sample # data self.coords = {} self.values = {} # Decorator to start qkit def measure(func): def wrapper(self, *args, **kwargs): save = False if 'save' in kwargs: save = kwargs['save'] kwargs.pop('save', None) if save: qkit.flow.start() output = func(self, *args, **kwargs) if save: if self.dirname: self._file_name = 'QM_experiment_' + self.exp_name + '_' + self.dirname else: self._file_name = 'QM_experiment_' + self.exp_name self._file_name = self._file_name.replace(' ', '').replace( ',', '_') # Save function arguments and qm program astring = "" for value in args: astring += "{}, ".format(value) kstring = "" for key, value in kwargs.items(): kstring += "{} = {}, ".format(key, value) self.sourcecode = astring + kstring + "\n\n\n" + inspect.getsource( func) self._prepare_measurement_file() self.store_data() qkit.flow.end() self.close_files() print('Measurement complete: {:s}'.format( self._data_file.get_filepath())) else: print('Measurement complete') return output return wrapper def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' self._data_file = hdf.Data(name=self._file_name, mode='a') self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qkit.instruments.get_instrument_names( ) self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # instrument settings and logfile self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) self._log_file = waf.open_log_file(self._data_file.get_filepath()) def close_files(self): # save plots and close files # t = threading.Thread(target=qviewkit.save_plots, args=[self._data_file.get_filepath()]) # t.start() self._data_file.flush() self._data_file.close_file() waf.close_log_file(self._log_file) # TODO open qkit # if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: # self._qvk_process.terminate() # terminate an old qviewkit instance def store_data(self): """ """ coord_dic = {} for key in self.coords: coord_vec = self.coords[key][0] unit = self.coords[key][1] coords_file = self._data_file.add_coordinate(key, unit=unit) coords_file.add(coord_vec) coord_dic[key] = coords_file for key in self.values: values = self.values[key][0] coord_key_list = self.values[key][1] unit = self.values[key][2] if len(coord_key_list) == 1: value_file = self._data_file.add_value_vector( key, x=coord_dic[coord_key_list[0]], unit=unit) value_file.append(values) elif len(coord_key_list) == 2: value_file = self._data_file.add_value_matrix( key, x=coord_dic[coord_key_list[0]], y=coord_dic[coord_key_list[1]], unit=unit) # file initialization - workaround value_file.append(values[0, :]) elif len(coord_key_list) == 3: value_file = self._data_file.add_value_box( key, x=coord_dic[coord_key_list[0]], y=coord_dic[coord_key_list[1]], z=coord_dic[coord_key_list[2]], unit=unit) # file initialization - workaround value_file.append(values[0, 0, :]) value_file.ds.resize(values.shape) value_file.ds[:] = values # source code sourcecode_file = self._data_file.add_textlist('sourcecode') sourcecode_file.append(self.sourcecode) # comment if self.comment: self._data_file.add_comment(self.comment)
class Measure_td(object): ''' useage: m = Measure_td() m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current (mA)',coil.set_current) m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency (Hz)',mw_src1.set_frequency) m.measure_XX() Generally, we want to use ReadoutTrace = True -> if we want to record the readout pulse or AWGTrace = True -> if we have a N different time steps in the AWG (Not used, this is done via the mode variable now.) ToDO (S1, 09/2017): - Include LogFunctions - Include 2D_AWG with pre-averaging - Check multi-tone readout ''' def __init__(self, sample, readout=None): self.sample = sample if readout is None: self.readout = sample.readout else: self.readout = readout self.comment = None self.mode = None self.x_set_obj = None self.y_set_obj = None self.z_set_obj = None self.dirname = None self.data_dir = None self.plotSuffix = '' self.hold = False self.show_progress_bar = True self.ReadoutTrace = False self.open_qviewkit = True self.create_averaged_data = False self.qviewkit_singleInstance = True self._qvk_process = False self._plot_comment = '' self.multiplex_attribute = "readout_pulse_frequency" self.multiplex_unit = "Hz" self.init = iniTD(sample) self._measurement_object = Measurement() self._measurement_object.measurement_type = 'timedomain' self._measurement_object.sample = self.sample self.state_0_cal = None self.state_1_cal = None def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit=None): self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj if x_unit is None: logging.warning(__name__ + ': Unit of the x-axis is not set.') self.x_unit = '' else: self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit=None): self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj if y_unit is None: logging.warning(__name__ + ': Unit of the y-axis is not set.') self.y_unit = '' else: self.y_unit = y_unit def set_z_parameters(self, z_vec, z_coordname, z_set_obj, z_unit=None): self.z_vec = z_vec self.z_coordname = z_coordname self.z_set_obj = z_set_obj if z_unit is None: logging.warning(__name__ + ': Unit of the z-axis is not set.') self.z_unit = '' else: self.z_unit = z_unit def measure_1D(self): if self.x_set_obj is None: raise ValueError('x-axes parameters not properly set') self.mode = 1 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG, 4:3D_AWG self._prepare_measurement_file() if self.show_progress_bar: p = Progress_Bar(len(self.x_vec), name=self.dirname) try: # measurement loop for x in self.x_vec: self.x_set_obj(x) qkit.flow.sleep() self._append_data() if self.show_progress_bar: p.iterate() finally: self._end_measurement() def measure_2D(self): if self.x_set_obj is None or self.y_set_obj is None: raise ValueError('Axes parameters not properly set') if self.ReadoutTrace: raise ValueError( 'ReadoutTrace is currently not supported for 2D measurements') self.mode = 2 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG, 4:3D_AWG self._prepare_measurement_file() if self.show_progress_bar: p = Progress_Bar(len(self.x_vec) * len(self.y_vec), name=self.dirname) try: # measurement loop for x in self.x_vec: self.x_set_obj(x) for y in self.y_vec: qkit.flow.sleep() self.y_set_obj(y) qkit.flow.sleep() self._append_data() if self.show_progress_bar: p.iterate() for i in range(self.ndev): self._hdf_amp[i].next_matrix() self._hdf_pha[i].next_matrix() finally: self._end_measurement() def measure_1D_AWG(self, iterations=100): ''' use AWG sequence for x_vec, averaging over iterations ''' self.y_vec = range(iterations) self.y_coordname = 'iteration' self.y_set_obj = lambda y: True self.y_unit = '' self.create_averaged_data = True self.avg_complex_sum = np.zeros_like(self.x_vec) try: return self.measure_2D_AWG(iterations=1) finally: self.create_averaged_data = False # This is ALWAYS done after the return! Looks strange and it really is, but it works. def measure_2D_AWG(self, iterations=1): ''' x_vec is sequence in AWG ''' if self.y_set_obj is None: raise ValueError('y-axes parameters not properly set') qkit.flow.sleep( ) # if stop button was pressed by now, abort without creating data files if iterations > 1: self.z_vec = range(iterations) self.z_coordname = 'iteration' self.z_set_obj = lambda z: True self.z_unit = '' self.measure_3D_AWG() # For 3D measurements with iterations, averages are only created at the end to get a consistent averaging base. hdf_file = hdf.Data(self._hdf.get_filepath()) for j in range(self.ndev): amp = np.array(hdf_file["/entry/data0/amplitude_%i" % j]) pha = np.array(hdf_file["/entry/data0/phase_%i" % j]) amp_avg = sum(amp[i] for i in range(iterations)) / iterations pha_avg = sum(pha[i] for i in range(iterations)) / iterations hdf_amp_avg = hdf_file.add_value_matrix('amplitude_avg_%i' % i, x=self._hdf_y, y=self._hdf_x, unit='a.u.') hdf_pha_avg = hdf_file.add_value_matrix('phase_avg_%i' % i, x=self._hdf_y, y=self._hdf_x, unit='rad') for i in range(len(self.y_vec)): hdf_amp_avg.append(amp_avg[i]) hdf_pha_avg.append(pha_avg[i]) hdf_file.close_file() else: self.mode = 3 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG, 4:3D_AWG self._prepare_measurement_file() # if self.ndev > 1: raise ValueError('Multiplexed readout is currently not supported for 2D measurements') if self.show_progress_bar: p = Progress_Bar(len(self.y_vec), name=self.dirname) try: # measurement loop for it in range(len(self.y_vec)): qkit.flow.sleep( ) # better done during measurement (waiting for trigger) self.y_set_obj(self.y_vec[it]) self._append_data(iteration=it) if self.show_progress_bar: p.iterate() finally: self._end_measurement() def measure_3D_AWG(self): ''' x_vec is sequence in AWG ''' if self.z_set_obj is None or self.y_set_obj is None: raise ValueError('x-axes parameters not properly set') if self.ReadoutTrace: raise ValueError( 'ReadoutTrace is currently not supported for 3D_AWG measurements' ) self.mode = 4 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG, 4:3D_AWG self._prepare_measurement_file() if self.show_progress_bar: p = Progress_Bar(len(self.y_vec) * len(self.z_vec), name=self.dirname) try: # measurement loop for z in self.z_vec: self.z_set_obj(z) for y in self.y_vec: qkit.flow.sleep() self.y_set_obj(y) qkit.flow.sleep() self._append_data() if self.show_progress_bar: p.iterate() for i in range(self.ndev): self._hdf_amp[i].next_matrix() self._hdf_pha[i].next_matrix() finally: self._end_measurement() def measure_1D_ddc_time_trace(self): """ measures the time evolution of your transmission / reflection in your network by performing a digital down conversion :return: None """ time_end = float(self.sample.mspec.get_samples() ) / self.sample.mspec.get_samplerate() time_array = np.linspace(0, time_end, self.sample.mspec.get_samples()) self.set_x_parameters(time_array, 'time', True, 'sec') self.mode = 1 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG self._prepare_measurement_file() try: qkit.flow.sleep() self._append_data(ddc=True) finally: self._end_measurement() def measure_2D_ddc_time_trace(self): """ Performs a digital down conversion for exactly one value in your awg sequence. But you can sweep other parameters, such as mw-power or so. :return: """ if self.y_set_obj is None: raise ValueError('y-axes parameters not properly set') time_end = float(self.sample.mspec.get_samples() ) / self.sample.mspec.get_samplerate() time_array = np.linspace(0, time_end, self.sample.mspec.get_samples()) self.set_x_parameters(time_array, 'time', True, 'sec') self.mode = 2 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG self._prepare_measurement_file() if self.show_progress_bar: p = Progress_Bar(len(self.y_vec), name=self.dirname) try: for y in self.y_vec: qkit.flow.sleep() self.y_set_obj(y) qkit.flow.sleep() self._append_data(ddc=True) if self.show_progress_bar: p.iterate() finally: self._end_measurement() def measure_1D_awg_ddc_timetrace(self): """ Performs a digital down conversion of your readout trace for every value in your y-vector, whereas y_vec should be awg sequence. This function is useful for magnon cavity experiments. x-vec is automatically set by acquisition window. (Also, all data are there at once) :return: """ if self.y_vec is None: raise ValueError('y-axes parameters not properly set') time_end = float(self.sample.mspec.get_samples() ) / self.sample.mspec.get_samplerate() time_array = np.linspace(0, time_end, self.sample.mspec.get_samples()) self.set_x_parameters(time_array, 'time', True, 'sec') self.mode = 2 # 1: 1D, 2: 2D, 3:1D_AWG/2D_AWG self._prepare_measurement_file() try: qkit.flow.sleep() self._append_data(ddc=True) finally: self._end_measurement() def measure_1D_awg_1D_ddc_timetrace(self): """ Performs a digital down conversion of your readout trace for every value in your y-vector while also sweeping another parameter. y_vec should be awg sequence and z_vec/obj the sweeping parameter. x-vec is automatically set by acquisition window. :return: """ if (self.y_vec is None) or (self.z_set_obj is None): raise ValueError('Axes parameters not properly set') time_end = float(self.sample.mspec.get_samples() ) / self.sample.mspec.get_samplerate() time_array = np.linspace(0, time_end, self.sample.mspec.get_samples()) self.set_x_parameters(time_array, 'time', True, 'sec') self.mode = 4 self._prepare_measurement_file() try: for z in self.z_vec: self.z_set_obj(z) qkit.flow.sleep() self._append_data(ddc=True) for i in range(self.ndev): self._hdf_amp[i].next_matrix() self._hdf_pha[i].next_matrix() if self.ReadoutTrace: self._hdf_I.next_matrix() self._hdf_Q.next_matrix() finally: self._end_measurement() def _prepare_measurement_file(self): qkit.flow.start() if self.dirname is None: self.dirname = self.x_coordname self.ndev = len(self.readout.get_tone_freq( )) # returns array of readout freqs (=1 for non-multiplexed readout) self._hdf = hdf.Data(name=self.dirname, mode='a') self._hdf_x = self._hdf.add_coordinate(self.x_coordname, unit=self.x_unit) self._hdf_x.add(self.x_vec) self._measurement_object.uuid = self._hdf._uuid self._measurement_object.hdf_relpath = self._hdf._relpath self._measurement_object.instruments = qkit.instruments.get_instrument_names( ) self._measurement_object.save() self._mo = self._hdf.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) self._settings = self._hdf.add_textlist('settings') settings = waf.get_instrument_settings(self._hdf.get_filepath()) self._settings.append(settings) self._log = waf.open_log_file(self._hdf.get_filepath()) self._hdf_readout_frequencies = self._hdf.add_coordinate( self.multiplex_attribute, unit=self.multiplex_unit) self._hdf_readout_frequencies.add(self.readout.get_tone_freq()) if self.state_0_cal and self.state_1_cal and self.mode > 2: #Adds calibration of ground and excited state of qubit for visualization of population during measurement self._hdf_state_0 = self._hdf.add_coordinate("State 0 Calibration") self._hdf_state_0.add(self.state_0_cal) self._hdf_state_1 = self._hdf.add_coordinate("State 1 Calibration") self._hdf_state_1.add(self.state_1_cal) if self.ReadoutTrace: self._hdf_TimeTraceAxis = self._hdf.add_coordinate( 'recorded timepoint', unit='s') self._hdf_TimeTraceAxis.add( np.arange(self.sample.mspec.get_samples()) / self.readout.get_adc_clock()) if self.mode == 1: # 1D self._hdf_amp = [] self._hdf_pha = [] for i in range(self.ndev): self._hdf_amp.append( self._hdf.add_value_vector('amplitude_%i' % i, x=self._hdf_x, unit='a.u.')) self._hdf_pha.append( self._hdf.add_value_vector('phase_%i' % i, x=self._hdf_x, unit='rad')) if self.ReadoutTrace: self._hdf_I = self._hdf.add_value_matrix( 'I_TimeTrace', x=self._hdf_x, y=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) self._hdf_Q = self._hdf.add_value_matrix( 'Q_TimeTrace', x=self._hdf_x, y=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) elif self.mode == 2: # 2D self._hdf_y = self._hdf.add_coordinate(self.y_coordname, unit=self.y_unit) self._hdf_y.add(self.y_vec) self._hdf_amp = [] self._hdf_pha = [] for i in range(self.ndev): self._hdf_amp.append( self._hdf.add_value_matrix('amplitude_%i' % i, x=self._hdf_x, y=self._hdf_y, unit='a.u.')) self._hdf_pha.append( self._hdf.add_value_matrix('phase_%i' % i, x=self._hdf_x, y=self._hdf_y, unit='rad')) if self.ReadoutTrace: # TODO: One dimension missing here? self._hdf_I = self._hdf.add_value_matrix( 'I_TimeTrace', x=self._hdf_y, y=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) self._hdf_Q = self._hdf.add_value_matrix( 'Q_TimeTrace', x=self._hdf_y, y=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) elif self.mode == 3: # 1D_AWG/2D_AWG self._hdf_y = self._hdf.add_coordinate(self.y_coordname, unit=self.y_unit) self._hdf_y.add(self.y_vec) self._hdf_amp = [] self._hdf_pha = [] self._hdf_p1 = [] for i in range(self.ndev): self._hdf_amp.append( self._hdf.add_value_matrix('amplitude_%i' % i, x=self._hdf_y, y=self._hdf_x, unit='a.u.')) self._hdf_pha.append( self._hdf.add_value_matrix('phase_%i' % i, x=self._hdf_y, y=self._hdf_x, unit='rad')) if self.state_0_cal and self.state_1_cal: self._hdf_p1.append( self._hdf.add_value_matrix("p1_%i" % i, x=self._hdf_y, y=self._hdf_x, unit="")) if self.ReadoutTrace: self._hdf_I = self._hdf.add_value_box( 'I_TimeTrace', x=self._hdf_y, y=self._hdf_x, z=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) self._hdf_Q = self._hdf.add_value_box( 'Q_TimeTrace', x=self._hdf_y, y=self._hdf_x, z=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) elif self.mode == 4: # 3D_AWG self._hdf_y = self._hdf.add_coordinate(self.y_coordname, unit=self.y_unit) self._hdf_y.add(self.y_vec) self._hdf_z = self._hdf.add_coordinate(self.z_coordname, unit=self.z_unit) self._hdf_z.add(self.z_vec) self._hdf_amp = [] self._hdf_pha = [] for i in range(self.ndev): self._hdf_amp.append( self._hdf.add_value_box('amplitude_%i' % i, x=self._hdf_z, y=self._hdf_y, z=self._hdf_x, unit='a.u.')) self._hdf_pha.append( self._hdf.add_value_box('phase_%i' % i, x=self._hdf_z, y=self._hdf_y, z=self._hdf_x, unit='rad')) if self.state_0_cal and self.state_1_cal: self._hdf_p1.append( self._hdf.add_value_box("p1_%i" % i, x=self._hdf_z, y=self._hdf_y, z=self._hdf_x, unit="")) if self.ReadoutTrace: self._hdf_I = self._hdf.add_value_box( 'I_TimeTrace', x=self._hdf_z, y=self._hdf_y, z=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) self._hdf_Q = self._hdf.add_value_box( 'Q_TimeTrace', x=self._hdf_y, y=self._hdf_y, z=self._hdf_TimeTraceAxis, unit='V', save_timestamp=False) if self.create_averaged_data: self._hdf_amp_avg = [] self._hdf_pha_avg = [] for i in range(self.ndev): self._hdf_amp_avg.append( self._hdf.add_value_vector('amplitude_avg_%i' % i, x=self._hdf_x, unit='a.u.')) self._hdf_pha_avg.append( self._hdf.add_value_vector('phase_avg_%i' % i, x=self._hdf_x, unit='rad')) if self.comment: self._hdf.add_comment(self.comment) self._hdf.hf.hf.attrs['default_ds'] = ['data0/amplitude_%i' % i for i in range(min(5,self.ndev))] +\ ['data0/phase_%i' % i for i in range(min(5,self.ndev))] if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() # terminate an old qviewkit instance if self.open_qviewkit: self._qvk_process = qviewkit.plot( self._hdf.get_filepath(), datasets=[ 'amplitude_%i' % i for i in range(min(5, self.ndev)) ] + ['phase_%i' % i for i in range(min(5, self.ndev))]) try: self.readout.start() except AttributeError: pass def _append_data(self, iteration=0, ddc=None): if self.ReadoutTrace: ampliData, phaseData, Is, Qs = self.readout.readout(timeTrace=True, ddc=ddc) else: ampliData, phaseData = self.readout.readout(timeTrace=False, ddc=ddc) if len(ampliData.shape) < 3: # "normal" measurements for i in range(self.ndev): self._hdf_amp[i].append(np.atleast_1d(ampliData.T[i])) self._hdf_pha[i].append(np.atleast_1d(phaseData.T[i])) if self.state_0_cal and self.state_1_cal and self.mode > 2: I_iter = np.atleast_1d(ampliData.T[i]) * np.sin( np.atleast_1d(phaseData.T[i])) Q_iter = np.atleast_1d(ampliData.T[i]) * np.cos( np.atleast_1d(phaseData.T[i])) self._hdf_p1[i].append( ((self.state_1_cal[0][0] - self.state_0_cal[0][0]) * (I_iter - self.state_0_cal[0][0]) + (self.state_1_cal[1][0] - self.state_0_cal[1][0]) * (Q_iter - self.state_0_cal[1][0])) / ((self.state_1_cal[0][0] - self.state_0_cal[0][0])**2 + (self.state_1_cal[1][0] - self.state_0_cal[1][0])**2)) if self.ReadoutTrace: if self.mode < 3: # mode 2 not yet fully supported but working for DDC timetrace experiments self._hdf_I.append(Is) self._hdf_Q.append(Qs) elif self.mode == 3: # mode 4 not supported for 3D_awg yet for ix in range(len(self.x_vec)): self._hdf_I.append(Is[:, ix]) self._hdf_Q.append(Qs[:, ix]) self._hdf_I.next_matrix() self._hdf_Q.next_matrix() else: # for AWG DDC ReadoutTrace, all data are there at once for i in range(self.ndev): for j in range(ampliData.T.shape[2]): self._hdf_amp[i].append(np.atleast_1d(ampliData.T[i, :, j])) self._hdf_pha[i].append(np.atleast_1d(phaseData.T[i, :, j])) if self.ReadoutTrace: self._hdf_I.append(Is[:, j]) self._hdf_Q.append(Qs[:, j]) if self.create_averaged_data: if iteration == 0: self.avg_complex_sum = ampliData * np.exp(1j * phaseData) for i in range(self.ndev): self._hdf_amp_avg[i].append(np.atleast_1d(ampliData.T[i])) self._hdf_pha_avg[i].append(np.atleast_1d(phaseData.T[i])) else: self.avg_complex_sum += ampliData * np.exp(1j * phaseData) amp_avg = np.abs(self.avg_complex_sum / (iteration + 1)) pha_avg = np.angle(self.avg_complex_sum / (iteration + 1)) for i in range(self.ndev): self._hdf_amp_avg[i].ds.write_direct( np.ascontiguousarray(np.atleast_1d(amp_avg.T[i]))) self._hdf_pha_avg[i].ds.write_direct( np.ascontiguousarray(np.atleast_1d(pha_avg.T[i]))) self._hdf_pha_avg[i].ds.attrs['iteration'] = iteration + 1 self._hdf_amp_avg[i].ds.attrs['iteration'] = iteration + 1 self._hdf.flush() def _end_measurement(self): try: self.readout.cleanup() except AttributeError: pass t = threading.Thread( target=qviewkit.save_plots, args=[self._hdf.get_filepath(), self._plot_comment]) t.start() self._hdf.close_file() waf.close_log_file(self._log) qkit.flow.end() def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment = comment def add_qubit_state_cal(self, filepath_to_0, filepath_to_1): state_0_data = hdf.Data(filepath_to_0) state_1_data = hdf.Data(filepath_to_1) self.state_0_cal = (state_0_data.data.amplitude_avg_0[:] * np.sin(state_0_data.data.phase_avg_0[:]), state_0_data.data.amplitude_avg_0[:] * np.cos(state_0_data.data.phase_avg_0[:])) self.state_1_cal = (state_1_data.data.amplitude_avg_0[:] * np.sin(state_1_data.data.phase_avg_0[:]), state_1_data.data.amplitude_avg_0[:] * np.cos(state_1_data.data.phase_avg_0[:])) state_0_data.close_file() state_1_data.close_file()
class transport(object): ''' usage: m = spectrum(vna = vna1) m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current',coil.set_current, unit = 'mA') m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency',mw_src1.set_frequency, unit = 'Hz') m.gen_fit_function(...) several times m.measure_XX() ''' def __init__(self, IV_Device, exp_name = '', sample = None): self.IVD = IV_Device self.exp_name = exp_name self._sample = sample self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._plot_comment="" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'transport' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode self._web_visible = True self.sweep = self.sweeps() self._Fraunhofer = True def add_sweep_4quadrants(self, start, stop, step, offset=0): self.sweep.add_sweep(start+offset, stop+offset, step) self.sweep.add_sweep(stop+offset, start+offset, -step) self.sweep.add_sweep(start+offset, -stop+offset, -step) self.sweep.add_sweep(-stop+offset, start+offset, step) def set_log_function(self, func=None, name = None, unit = None, log_dtype = None): ''' A function (object) can be passed to the measurement loop which is excecuted before every x iteration but after executing the x_object setter in 2D measurements and before every line (but after setting the x value) in 3D measurements. The return value of the function of type float or similar is stored in a value vector in the h5 file. Call without any arguments to delete all log functions. The timestamp is automatically saved. func: function object in list form name: name of logging parameter appearing in h5 file, default: 'log_param' unit: unit of logging parameter, default: '' log_dtype: h5 data type, default: 'f' (float32) ''' if name == None: try: name = ['log_param']*len(func) except Exception: name = None if unit == None: try: unit = ['']*len(func) except Exception: unit = None if log_dtype == None: try: log_dtype = ['f']*len(func) except Exception: log_dtype = None self.log_function = [] self.log_name = [] self.log_unit = [] self.log_dtype = [] if func != None: for i,f in enumerate(func): self.log_function.append(f) self.log_name.append(name[i]) self.log_unit.append(unit[i]) self.log_dtype.append(log_dtype[i]) def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: x_vec (array): conains the sweeping values x_coordname (string) x_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) x_unit (string): optional ''' self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj #self.delete_fit_function() self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: y_vec (array): contains the sweeping values y_coordname (string) y_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) y_unit (string): optional ''' self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj self.delete_fit_function() self.y_unit = y_unit def set_web_visible(self, web_visible = True): ''' Sets the web_visible parameter for the measurement class Input: web_visible = True (Default) | False ''' self._web_visible = web_visible def set_sweep_type(self, sweep_type = 1): ''' # FIXME: HR this should go into the IVD driver Sets the sweep type, in the moment only simple sweep types are defined: Input: sweep_type: 0: single sweep START -> END 1: double sweep START -> END -> START (default) 2: triple sweep START -> END -> START -> -END 3: quad sweep START -> END -> START -> -END -> START ... ''' # define the number of datasets for each sweep type self.IV_sweep_types = { 0:1 , 1:2, 2:3, 3:4 } self.IV_sweep_type = sweep_type def get_sweep_type(self): return self.IV_sweep_type def get_num_ds_from_sweep_type(self,sweep_type): # should be self.IVD.IV_sweep_types[sweep_type] return self.IV_sweep_types[sweep_type] def _prepare_measurement_IVD(self): ''' all the relevant settings from the vna are updated and called ''' self.IVD.get_all() #ttip.get_temperature() # bias mode is either current=0 or voltage=1 self._bias_mode = self.IVD.get_bias_mode() #self._nop = self.IVD.get_nop() #self._sweeptime_averages = self.IVD.get_sweeptime_averages() #self._freqpoints = self.IVD.get_freqpoints() #if self.averaging_start_ready: self.vna.pre_measurement() def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' print ('filename '+self._file_name) self._data_file = hdf.Data(name=self._file_name) self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qt.instruments.get_instruments() #self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings #self._write_settings_dataset() #self._log = waf.open_log_file(self._data_file.get_filepath()) #if not self._scan_time: # self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') # self._data_freq.add(self._freqpoints) self._data_I = [] self._data_V = [] #self._data_dVdI = [] if self._scan_1D: if self._bias_mode:# current bias #self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') for st in range(self.sweep.get_nos()): self._data_V.append(self._data_file.add_value_vector('V_'+str(st), unit = 'V', save_timestamp = False)) self._data_I.append(self._data_file.add_value_vector('I_'+str(st), x = self._data_V[st], unit = 'A', save_timestamp = False)) #self._data_dVdI.append(self._data_file.add_value_vector('_data_dVdI_'+str(st), x = self._data_V[st], unit = 'V/A', save_timestamp = False)) IV = self._data_file.add_view('IV', x = self._data_V[0], y = self._data_I[0]) #dVdI = self._data_file.add_view('dVdI', x = self._data_I[0] , y = self._data_dVdI[0]) for i in range(1, self.sweep.get_nos()): IV.add(x=self._data_V[i],y=self._data_I[i]) #dVdI.add(x=self._data_I[i],y=self._data_dVdI[i]) if self._scan_2D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) for st in range(self.sweep.get_nos()): self._data_I.append(self._data_file.add_value_vector('I_'+str(st), unit = 'A', save_timestamp = False)) self._data_V.append(self._data_file.add_value_matrix('V_'+str(st), x = self._data_x, y = self._data_I[st], unit = 'V', save_timestamp = False)) #self._data_dVdI.append(self._data_file.add_value_matrix('dVdI_'+str(st), x = self._data_x, y = self._data_I[st], unit = 'V/A', save_timestamp = False)) if self._Fraunhofer: self._data_Ic = [] for st in range(self.sweep.get_nos()): self._data_Ic.append(self._data_file.add_value_vector('Ic_'+str(st), x = self._data_x, unit = 'A', save_timestamp = False)) Fraunhofer = self._data_file.add_view('Fraunhofer', x=self._data_x, y=self._data_Ic[0]) for i in range(1, self.sweep.get_nos()): Fraunhofer.add(x=self._data_x, y=self._data_Ic[i]) #if self.log_function != None: #use logging # self._log_value = [] # for i in range(len(self.log_function)): # self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self._scan_3D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) self._data_y = self._data_file.add_coordinate(self.y_coordname, unit = self.y_unit) self._data_y.add(self.y_vec) if self._nop == 0: #saving in a 2D matrix instead of a 3D box HR: does not work yet !!! test things before you put them online. self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_y, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_y, unit = 'rad', save_timestamp = False) else: self._data_amp = self._data_file.add_value_box('amplitude', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_box('phase', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'rad', save_timestamp = False) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self.comment: self._data_file.add_comment(self.comment) if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() #terminate an old qviewkit instance def _write_settings_dataset(self): self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def _wait_progress_bar(self): ti = time() if self.progress_bar: self._p = Progress_Bar(self.IVD.get_averages(),self.dirname,self.IVD.get_sweeptime()) qt.msleep(.2) # wait for data while not self.IVD.ready(): if time()-ti > self.IVD.get_sweeptime(query=False): if self.progress_bar: self._p.iterate() ti = time() qt.msleep(.2) if self.progress_bar: while self._p.progr < self._p.max_it: self._p.iterate() def measure_1D(self, sweep_type=1): ''' measure method to record a single (averaged) VNA trace, S11 or S21 according to the setting on the VNA rescan: If True (default), the averages on the VNA are cleared and a new measurement is started. If False, it will directly take the data from the VNA without waiting. ''' self._sweep_type = sweep_type self._scan_1D = True self._scan_2D = False self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_1D' self._measurement_object.x_axis = 'frequency' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = self._web_visible if not self.dirname: self.dirname = 'IVD_tracedata' self._file_name = self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name # prepare storage self._prepare_measurement_IVD() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['I_0', 'V_0']) print('recording trace...') sys.stdout.flush() qt.mstart() self.sweep.create_iterator() self.IVD.set_status(True) for st in range(self.sweep.get_nos()): #print st self.IVD.set_sweep_parameters(self.sweep.get_sweep()) data_bias, data_sense = self.IVD.take_IV() self._data_I[st].append(data_bias) self._data_V[st].append(data_sense) #self._data_dVdI[st].append(np.gradient(self._data_V[st])/np.gradient(self._data_I[st])) self.IVD.set_status(False) qt.mend() self._end_measurement() def measure_2D(self): ''' measure method to record a (averaged) VNA trace, S11 or S21 according to the setting on the VNA for all parameters x_vec in x_obj ''' if not self.x_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = True self._scan_3D = False self._measurement_object.measurement_func = 'measure_2D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = 'current' self._measurement_object.z_axis = '' self._measurement_object.web_visible = self._web_visible if not self.dirname: self.dirname = self.x_coordname self._file_name = '2D_' + self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name #if self.progress_bar: self._p = Progress_Bar(len(self.x_vec),'2D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) self._prepare_measurement_IVD() self._prepare_measurement_file() '''opens qviewkit to plot measurement, amp and pha are opened by default''' if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['I_0', 'V_0']) self._measure() # # # def measure_3D(self): # ''' # measure full window of vna while sweeping x_set_obj and y_set_obj with parameters x_vec/y_vec. sweep over y_set_obj is the inner loop, for every value x_vec[i] all values y_vec are measured. # # optional: measure method to perform the measurement according to landscape, if set # self.span is the range (in units of the vertical plot axis) data is taken around the specified funtion(s) # note: make sure to have properly set x,y vectors before generating traces # ''' # if not self.x_set_obj or not self.y_set_obj: # logging.error('axes parameters not properly set...aborting') # return # self._scan_1D = False # self._scan_2D = False # self._scan_3D = True # self._scan_time = False # # self._measurement_object.measurement_func = 'measure_3D' # self._measurement_object.x_axis = self.x_coordname # self._measurement_object.y_axis = self.y_coordname # self._measurement_object.z_axis = 'frequency' # self._measurement_object.web_visible = self._web_visible # # if not self.dirname: # self.dirname = self.x_coordname + ', ' + self.y_coordname # self._file_name = '3D_' + self.dirname.replace(' ', '').replace(',','_') # if self.exp_name: # self._file_name += '_' + self.exp_name # # if self.progress_bar: self._p = Progress_Bar(len(self.x_vec)*len(self.y_vec),'3D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) # # self._prepare_measurement_vna() # self._prepare_measurement_file() # '''opens qviewkit to plot measurement, amp and pha are opened by default''' # '''only middle point in freq array is plotted vs x and y''' # if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) # if self._fit_resonator: # self._resonator = resonator(self._data_file.get_filepath()) # # if self.landscape: # self.center_freqs = np.array(self.landscape).T # else: # self.center_freqs = [] #load default sequence # for i in range(len(self.x_vec)): # self.center_freqs.append([0]) # # self._measure() def _measure(self): ''' measures and plots the data depending on the measurement type. the measurement loops feature the setting of the objects and saving the data in the .h5 file. ''' qt.mstart() try: """ loop: x_obj with parameters from x_vec """ self.IVD.set_status(True) for ix, x in enumerate(self.x_vec): self.x_set_obj(x) sleep(1) #if self.log_function != None: # for i,f in enumerate(self.log_function): # self._log_value[i].append(float(f())) if self._scan_2D: """ measurement """ self.sweep.create_iterator() for st in range(self.sweep.get_nos()): self.IVD.set_sweep_parameters(self.sweep.get_sweep()) data_bias, data_sense = self.IVD.take_IV(channel=1) self._data_I[st].add(data_bias) self._data_V[st].append(data_sense) #self._data_dVdI[st].append(np.array(np.gradient(self._data_V[st]))[-1]/np.gradient(self._data_I[st])) if self._Fraunhofer: self._IVC = IV_curve() self._data_Ic[st].append(self._IVC.get_Ic(V=data_sense, I=data_bias, direction=self.IVD._direction)) #if self.progress_bar: # self._p.iterate() qt.msleep() self.IVD.set_status(False) except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() self.IVD.set_status(False, 1) #self.IVD.ramp_current(0, 100e-6, channel=2) self.IVD.set_status(False, 2) qt.mend() def _end_measurement(self): ''' the data file is closed and filepath is printed ''' print self._data_file.get_filepath() #qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) #old version where we have to wait for the plots t = threading.Thread(target=qviewkit.save_plots,args=[self._data_file.get_filepath(),self._plot_comment]) t.start() self._data_file.close_file() #waf.close_log_file(self._log) self.dirname = None def set_span(self, span): self.span = span def get_span(self): return self.span def set_tdx(self, tdx): self.tdx = tdx def set_tdy(self, tdy): self.tdy = tdy def get_tdx(self): return self.tdx def get_tdy(self): return self.tdy def f_parab(self,x,a,b,c): return a*(x-b)**2+c def f_hyp(self,x,a,b,c): "hyperbolic function with the form y = sqrt[ a*(x-b)**2 + c ]" return np.sqrt(a*(x-b)**2+c) def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment=comment class sweeps(object): def __init__(self, name='default'): self._starts = [] self._stops = [] self._steps = [] self.create_iterator() def create_iterator(self): self._start_iter = iter(self._starts) self._stop_iter = iter(self._stops) self._step_iter = iter(self._steps) def add_sweep(self, start, stop, step): self._starts.append(start) self._stops.append(stop) self._steps.append(step) def reset_sweeps(self): self._starts = [] self._stops = [] self._stops = [] def get_sweep(self): return (self._start_iter.next(), self._stop_iter.next(), self._step_iter.next()) def get_nos(self): return len(self._starts) def print_sweeps(self): print(self._starts, self._stops, self._steps)
class spectrum(object): ''' usage: m = spectrum(vna = vna1) m.set_x_parameters(arange(-0.05,0.05,0.01),'flux coil current',coil.set_current, unit = 'mA') m.set_y_parameters(arange(4e9,7e9,10e6),'excitation frequency',mw_src1.set_frequency, unit = 'Hz') m.gen_fit_function(...) several times m.measure_XX() ''' def __init__(self, IV_Device, exp_name = '', sample = None): self.IVD = IV_Device self.exp_name = exp_name self._sample = sample self.comment = '' self.dirname = None self.x_set_obj = None self.y_set_obj = None self.progress_bar = True self._plot_comment="" self.set_log_function() self.open_qviewkit = True self.qviewkit_singleInstance = False self._measurement_object = Measurement() self._measurement_object.measurement_type = 'transport' self._measurement_object.sample = self._sample self._qvk_process = False self.number_of_timetraces = 1 #relevant in time domain mode def set_log_function(self, func=None, name = None, unit = None, log_dtype = None): ''' A function (object) can be passed to the measurement loop which is excecuted before every x iteration but after executing the x_object setter in 2D measurements and before every line (but after setting the x value) in 3D measurements. The return value of the function of type float or similar is stored in a value vector in the h5 file. Call without any arguments to delete all log functions. The timestamp is automatically saved. func: function object in list form name: name of logging parameter appearing in h5 file, default: 'log_param' unit: unit of logging parameter, default: '' log_dtype: h5 data type, default: 'f' (float32) ''' if name == None: try: name = ['log_param']*len(func) except Exception: name = None if unit == None: try: unit = ['']*len(func) except Exception: unit = None if log_dtype == None: try: log_dtype = ['f']*len(func) except Exception: log_dtype = None self.log_function = [] self.log_name = [] self.log_unit = [] self.log_dtype = [] if func != None: for i,f in enumerate(func): self.log_function.append(f) self.log_name.append(name[i]) self.log_unit.append(unit[i]) self.log_dtype.append(log_dtype[i]) def set_x_parameters(self, x_vec, x_coordname, x_set_obj, x_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: x_vec (array): conains the sweeping values x_coordname (string) x_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) x_unit (string): optional ''' self.x_vec = x_vec self.x_coordname = x_coordname self.x_set_obj = x_set_obj self.delete_fit_function() self.x_unit = x_unit def set_y_parameters(self, y_vec, y_coordname, y_set_obj, y_unit = ""): ''' Sets parameters for sweep. In a 3D measurement, the x-parameters will be the "outer" sweep. For every x value all y values are swept Input: y_vec (array): contains the sweeping values y_coordname (string) y_instrument (obj): callable object to execute with x_vec-values (i.e. vna.set_power()) y_unit (string): optional ''' self.y_vec = y_vec self.y_coordname = y_coordname self.y_set_obj = y_set_obj self.delete_fit_function() self.y_unit = y_unit def set_sweep_type(self, sweep_type = 1): ''' Sets the sweep type, in the moment only simple sweep types are defined: Input: sweep_type: 0: single sweep START -> END 1: double sweep START -> END -> START (default) 2: triple sweep START -> END -> START -> -END 3: quad sweep START -> END -> START -> -END -> START ''' self.IV_sweep_type = sweep_type def _prepare_measurement_IVD(self): ''' all the relevant settings from the vna are updated and called ''' self.IVD.get_all() #ttip.get_temperature() # bias mode is either current=0 or voltage=1 self._bias_mode = self.IVD.get_bias_mode() self._nop = self.IVD.get_nop() self._sweeptime_averages = self.IVD.get_sweeptime_averages() #self._freqpoints = self.IVD.get_freqpoints() if self.averaging_start_ready: self.vna.pre_measurement() def _prepare_measurement_file(self): ''' creates the output .h5-file with distinct dataset structures for each measurement type. at this point all measurement parameters are known and put in the output file ''' self._data_file = hdf.Data(name=self._file_name) self._measurement_object.uuid = self._data_file._uuid self._measurement_object.hdf_relpath = self._data_file._relpath self._measurement_object.instruments = qt.instruments.get_instruments() self._measurement_object.save() self._mo = self._data_file.add_textlist('measurement') self._mo.append(self._measurement_object.get_JSON()) # write logfile and instrument settings self._write_settings_dataset() self._log = waf.open_log_file(self._data_file.get_filepath()) #if not self._scan_time: # self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') # self._data_freq.add(self._freqpoints) if self._scan_1D: if self._bias_mode:# current bias #self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') for st in range(self.sweep_type): self._data_I[st] = self._data_file.add_value_vector('I_'+str(st), unit = 'A', save_timestamp = True) self._data_V[st] = self._data_file.add_value_vector('V_'+str(st), x = self._data_I, unit = 'V', save_timestamp = True) if self._scan_2D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_freq, unit = 'arb. unit', save_timestamp = True) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_freq, unit='rad', save_timestamp = True) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self._nop < 10: """creates view: plot middle point vs x-parameter, for qubit measurements""" self._data_amp_mid = self._data_file.add_value_vector('amplitude_midpoint', unit = 'arb. unit', x = self._data_x, save_timestamp = True) self._data_pha_mid = self._data_file.add_value_vector('phase_midpoint', unit = 'rad', x = self._data_x, save_timestamp = True) #self._view = self._data_file.add_view("amplitude vs. " + self.x_coordname, x = self._data_x, y = self._data_amp[self._nop/2]) if self._scan_3D: self._data_x = self._data_file.add_coordinate(self.x_coordname, unit = self.x_unit) self._data_x.add(self.x_vec) self._data_y = self._data_file.add_coordinate(self.y_coordname, unit = self.y_unit) self._data_y.add(self.y_vec) if self._nop == 0: #saving in a 2D matrix instead of a 3D box HR: does not work yet !!! test things before you put them online. self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_y, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_y, unit = 'rad', save_timestamp = False) else: self._data_amp = self._data_file.add_value_box('amplitude', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'arb. unit', save_timestamp = False) self._data_pha = self._data_file.add_value_box('phase', x = self._data_x, y = self._data_y, z = self._data_freq, unit = 'rad', save_timestamp = False) if self.log_function != None: #use logging self._log_value = [] for i in range(len(self.log_function)): self._log_value.append(self._data_file.add_value_vector(self.log_name[i], x = self._data_x, unit = self.log_unit[i], dtype=self.log_dtype[i])) if self._scan_time: self._data_freq = self._data_file.add_coordinate('frequency', unit = 'Hz') self._data_freq.add([self.vna.get_centerfreq()]) self._data_time = self._data_file.add_coordinate('time', unit = 's') self._data_time.add(np.arange(0,self._nop,1)*self.vna.get_sweeptime()/(self._nop-1)) self._data_x = self._data_file.add_coordinate('trace_number', unit = '') self._data_x.add(np.arange(0, self.number_of_timetraces, 1)) self._data_amp = self._data_file.add_value_matrix('amplitude', x = self._data_x, y = self._data_time, unit = 'lin. mag.', save_timestamp = False) self._data_pha = self._data_file.add_value_matrix('phase', x = self._data_x, y = self._data_time, unit = 'rad.', save_timestamp = False) if self.comment: self._data_file.add_comment(self.comment) if self.qviewkit_singleInstance and self.open_qviewkit and self._qvk_process: self._qvk_process.terminate() #terminate an old qviewkit instance def _write_settings_dataset(self): self._settings = self._data_file.add_textlist('settings') settings = waf.get_instrument_settings(self._data_file.get_filepath()) self._settings.append(settings) def measure_1D(self, rescan = True, web_visible = True): ''' measure method to record a single (averaged) VNA trace, S11 or S21 according to the setting on the VNA rescan: If True (default), the averages on the VNA are cleared and a new measurement is started. If False, it will directly take the data from the VNA without waiting. ''' self._scan_1D = True self._scan_2D = False self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_1D' self._measurement_object.x_axis = 'frequency' self._measurement_object.y_axis = '' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = 'IVD_tracedata' self._file_name = self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name # prepare storage self._prepare_measurement_IVD() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['I_0', 'V_0']) print('recording trace...') sys.stdout.flush() qt.mstart() if rescan: if self.averaging_start_ready: self.vna.start_measurement() ti = time() if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime()) qt.msleep(.2) while not self.vna.ready(): if time()-ti > self.vna.get_sweeptime(query=False): if self.progress_bar: self._p.iterate() ti = time() qt.msleep(.2) if self.progress_bar: while self._p.progr < self._p.max_it: self._p.iterate() else: self.vna.avg_clear() if self.vna.get_averages() == 1 or self.vna.get_Average() == False: #no averaging if self.progress_bar:self._p = Progress_Bar(1,self.dirname,self.vna.get_sweeptime()) qt.msleep(self.vna.get_sweeptime()) #wait single sweep if self.progress_bar: self._p.iterate() else: #with averaging if self.progress_bar: self._p = Progress_Bar(self.vna.get_averages(),self.dirname,self.vna.get_sweeptime()) if "avg_status" in self.vna.get_function_names(): for a in range(self.vna.get_averages()): while self.vna.avg_status() <= a: qt.msleep(.2) #maybe one would like to adjust this at a later point if self.progress_bar: self._p.iterate() else: #old style for a in range(self.vna.get_averages()): qt.msleep(self.vna.get_sweeptime()) #wait single sweep time if self.progress_bar: self._p.iterate() data_amp, data_pha = self.vna.get_tracedata() data_real, data_imag = self.vna.get_tracedata('RealImag') self._data_amp.append(data_amp) self._data_pha.append(data_pha) self._data_real.append(data_real) self._data_imag.append(data_imag) if self._fit_resonator: self._do_fit_resonator() qt.mend() self._end_measurement() """ def measure_2D(self, web_visible = True): ''' measure method to record a (averaged) VNA trace, S11 or S21 according to the setting on the VNA for all parameters x_vec in x_obj ''' if not self.x_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = True self._scan_3D = False self._scan_time = False self._measurement_object.measurement_func = 'measure_2D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = 'frequency' self._measurement_object.z_axis = '' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname self._file_name = '2D_' + self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec),'2D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" if self._nop < 10: if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude_midpoint', 'phase_midpoint']) else: if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) self._measure() def measure_3D(self, web_visible = True): ''' measure full window of vna while sweeping x_set_obj and y_set_obj with parameters x_vec/y_vec. sweep over y_set_obj is the inner loop, for every value x_vec[i] all values y_vec are measured. optional: measure method to perform the measurement according to landscape, if set self.span is the range (in units of the vertical plot axis) data is taken around the specified funtion(s) note: make sure to have properly set x,y vectors before generating traces ''' if not self.x_set_obj or not self.y_set_obj: logging.error('axes parameters not properly set...aborting') return self._scan_1D = False self._scan_2D = False self._scan_3D = True self._scan_time = False self._measurement_object.measurement_func = 'measure_3D' self._measurement_object.x_axis = self.x_coordname self._measurement_object.y_axis = self.y_coordname self._measurement_object.z_axis = 'frequency' self._measurement_object.web_visible = web_visible if not self.dirname: self.dirname = self.x_coordname + ', ' + self.y_coordname self._file_name = '3D_' + self.dirname.replace(' ', '').replace(',','_') if self.exp_name: self._file_name += '_' + self.exp_name if self.progress_bar: self._p = Progress_Bar(len(self.x_vec)*len(self.y_vec),'3D VNA sweep '+self.dirname,self.vna.get_sweeptime_averages()) self._prepare_measurement_vna() self._prepare_measurement_file() """opens qviewkit to plot measurement, amp and pha are opened by default""" """only middle point in freq array is plotted vs x and y""" if self.open_qviewkit: self._qvk_process = qviewkit.plot(self._data_file.get_filepath(), datasets=['amplitude', 'phase']) if self._fit_resonator: self._resonator = resonator(self._data_file.get_filepath()) if self.landscape: self.center_freqs = np.array(self.landscape).T else: self.center_freqs = [] #load default sequence for i in range(len(self.x_vec)): self.center_freqs.append([0]) self._measure() """ def _measure(self): ''' measures and plots the data depending on the measurement type. the measurement loops feature the setting of the objects and saving the data in the .h5 file. ''' qt.mstart() try: """ loop: x_obj with parameters from x_vec """ for ix, x in enumerate(self.x_vec): self.x_set_obj(x) sleep(self.tdx) if self.log_function != None: for i,f in enumerate(self.log_function): self._log_value[i].append(float(f())) if self._scan_3D: for y in self.y_vec: """ loop: y_obj with parameters from y_vec (only 3D measurement) """ if (np.min(np.abs(self.center_freqs[ix]-y*np.ones(len(self.center_freqs[ix])))) > self.span/2.) and self.landscape: #if point is not of interest (not close to one of the functions) data_amp = np.zeros(int(self._nop)) data_pha = np.zeros(int(self._nop)) #fill with zeros else: self.y_set_obj(y) sleep(self.tdy) if self.averaging_start_ready: self.vna.start_measurement() qt.msleep(.2) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) #if "avg_status" in self.vna.get_function_names(): # while self.vna.avg_status() < self.vna.get_averages(): # qt.msleep(.2) #maybe one would like to adjust this at a later point """ measurement """ data_amp, data_pha = self.vna.get_tracedata() if self._nop == 0: # this does not work yet. print data_amp[0], data_amp, self._nop self._data_amp.append(data_amp[0]) self._data_pha.append(data_pha[0]) else: self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() """ filling of value-box is done here. after every y-loop the data is stored the next 2d structure """ self._data_amp.next_matrix() self._data_pha.next_matrix() if self._scan_2D: if self.averaging_start_ready: self.vna.start_measurement() qt.msleep(.2) #just to make sure, the ready command does not *still* show ready while not self.vna.ready(): qt.msleep(.2) else: self.vna.avg_clear() qt.msleep(self._sweeptime_averages) """ measurement """ data_amp, data_pha = self.vna.get_tracedata() self._data_amp.append(data_amp) self._data_pha.append(data_pha) if self._nop < 10: #print data_amp[self._nop/2] self._data_amp_mid.append(float(data_amp[self._nop/2])) self._data_pha_mid.append(float(data_pha[self._nop/2])) if self._fit_resonator: self._do_fit_resonator() if self.progress_bar: self._p.iterate() qt.msleep() except Exception as e: print e.__doc__ print e.message finally: self._end_measurement() qt.mend() def _end_measurement(self): ''' the data file is closed and filepath is printed ''' print self._data_file.get_filepath() #qviewkit.save_plots(self._data_file.get_filepath(),comment=self._plot_comment) #old version where we have to wait for the plots t = threading.Thread(target=qviewkit.save_plots,args=[self._data_file.get_filepath(),self._plot_comment]) t.start() self._data_file.close_file() waf.close_log_file(self._log) self.dirname = None def set_span(self, span): self.span = span def get_span(self): return self.span def set_tdx(self, tdx): self.tdx = tdx def set_tdy(self, tdy): self.tdy = tdy def get_tdx(self): return self.tdx def get_tdy(self): return self.tdy def f_parab(self,x,a,b,c): return a*(x-b)**2+c def f_hyp(self,x,a,b,c): "hyperbolic function with the form y = sqrt[ a*(x-b)**2 + c ]" return np.sqrt(a*(x-b)**2+c) def set_plot_comment(self, comment): ''' Small comment to add at the end of plot pics for more information i.e. good for wiki entries. ''' self._plot_comment=comment