Source code for qtt.structures

"""
Contains code for various structures

"""
import numpy as np
import matplotlib.pyplot as plt
import copy
from functools import partial

import qcodes
import qcodes.instrument.parameter

import qtt
import qtt.measurements.scans
from qtt.algorithms.coulomb import peakdataOrientation, coulombPeaks, findSensingDotPosition
from qtt.utilities.tools import freezeclass
from qtt.dataset_processing import process_dataarray


# %%


[docs]@freezeclass class twodot_t(dict): def __init(self, gates, name=None): """ Class to represent a double quantum dot Args: gates (list of str): gate names of barrier left, plunger left, barrier middle, plunger right and barrier right name (str): name for the object """ self['gates'] = gates self['name'] = name
[docs] def name(self): return self['name']
def __repr__(self): s = '%s: %s at 0x%x' % (self.__class__.__name__, self.name(), id(self)) return s def __getstate__(self): """ Helper function to allow pickling of object """ d = {} for k, v in self.__dict__.items(): if k not in ['station']: d[k] = copy.deepcopy(v) return d
[docs]@freezeclass class onedot_t(dict): """ Class representing a single quantum dot """ def __init__(self, gates, name=None, data=None, station=None, transport_instrument=None): """ Class to represent a single quantum dot Args: gates (list of str): names of gates to use for left barrier, plunger and right barrier name (str): name for the object transport_instrument (str or Instrument): instrument to use for transport measurements data (dict or None): data for internal storage station (obj): object with references to instruments """ self['gates'] = gates self.station = station self['transport_instrument'] = transport_instrument self['instrument'] = transport_instrument # legacy code if name is None: name = 'dot-%s' % ('-'.join(gates)) self['name'] = name if data is None: data = {} self.data = data
[docs] def name(self): return self['name']
def __repr__(self): s = '%s: %s at 0x%x' % (self.__class__.__name__, self.name(), id(self)) return s def __getstate__(self): """ Helper function to allow pickling of object """ d = {} import copy for k, v in self.__dict__.items(): if k not in ['station']: d[k] = copy.deepcopy(v) return d
# %% def _scanlabel(ds): """ Helper function """ a = ds.default_parameter_array() lx = a.set_arrays[0].label unit = a.set_arrays[0].unit if unit: lx += '[' + unit + ']' return lx
[docs]@freezeclass class sensingdot_t: def __init__(self, gate_names, gate_values=None, station=None, index=None, minstrument=None, virt_gates=None): """ Class representing a sensing dot We assume the sensing dot can be controlled by two barrier gates and a single plunger gate. An instrument to measure the current through the dot is provided by the minstrument argument. Args: gate_names (list): gates to be used gate_values (array or None): values to be set on the gates station (Qcodes station) minstrument (tuple or str or Parameter): measurement instrument to use. tuple of instrument and channel index index (None or int): deprecated fpga_ch (deprecated, int): index of FPGA channel to use for readout virt_gates (None or object): virtual gates object (optional) boxcar_filter_kernel_size (int): size of boxcar filter kernel to use in post-processing """ self.verbose = 1 self.gg = gate_names if gate_values is None: gate_values = [station.gates.get(g) for g in self.gg] self.sdval = gate_values self.targetvalue = np.NaN self.boxcar_filter_kernel_size = 1 self._selected_peak = None self._detune_axis = np.array([1, -1]) self._detune_axis = self._detune_axis / np.linalg.norm(self._detune_axis) self._debug = {} # store debug data self.name = '-'.join([s for s in self.gg]) self.goodpeaks = None self.station = station self.index = index self.minstrument = minstrument if index is not None: raise Exception('use minstrument argument') self.virt_gates = virt_gates self.data = {} self.plunger = qcodes.Parameter('plunger', get_cmd=self.plungervalue, set_cmd=self._set_plunger) # values for measurement if index is not None: self.valuefunc = station.components[ 'keithley%d' % index].amplitude.get else: self.valuefunc = None def __repr__(self): return 'sd gates: %s, %s, %s' % (self.gg[0], self.gg[1], self.gg[2]) def __getstate__(self): """ Override to make the object pickable.""" if self.verbose: print('sensingdot_t: __getstate__') import copy d = copy.copy(self.__dict__) for name in ['station', 'valuefunc']: if name in d: d[name] = str(d[name]) return d
[docs] def gates(self): """ Return values on the gates used to the define the SD """ return self.sdval
[docs] def gate_names(self): """ Return names of the gates used to the define the SD """ return self.gg
[docs] def show(self): gates = self.station.gates s = 'sensingdot_t: %s: %s: g %.1f, value %.1f/%.1f' % ( self.gg[1], str(self.sdval), gates.get(self.gg[1]), self.value(), self.targetvalue) return s
[docs] def initialize(self, sdval=None, setPlunger=False): gates = self.station.gates if sdval is not None: self.sdval = sdval gg = self.gg for ii in [0, 2]: gates.set(gg[ii], self.sdval[ii]) if setPlunger: ii = 1 gates.set(gg[ii], self.sdval[ii])
[docs] def tunegate(self): """Return the gate used for tuning the potential in the dot """ return self.gg[1]
[docs] def plungervalue(self): """ Return current value of the chemical potential plunger """ gates = self.station.gates return gates.get(self.tunegate())
def _set_plunger(self, value): gates = self.station.gates gates.set(self.tunegate(), value)
[docs] def value(self): """Return current through sensing dot """ if self.valuefunc is not None: return self.valuefunc() raise Exception( 'value function is not defined for this sensing dot object')
[docs] def scan1D(sd, outputdir=None, step=-2., max_wait_time=.75, scanrange=300): """ Make 1D-scan of the sensing dot.""" if sd.verbose: print('### sensing dot scan') minstrument = sd.minstrument if sd.index is not None: minstrument = [sd.index] gg = sd.gg sdval = sd.sdval gates = sd.station.gates for ii in [0, 2]: gates.set(gg[ii], sdval[ii]) startval = sdval[1] + scanrange endval = sdval[1] - scanrange wait_time = 0.8 try: wait_time = sd.station.gate_settle(gg[1]) except BaseException: pass wait_time = np.minimum(wait_time, max_wait_time) scanjob1 = qtt.measurements.scans.scanjob_t() scanjob1['sweepdata'] = dict( {'param': gg[1], 'start': startval, 'end': endval, 'step': step, 'wait_time': wait_time}) scanjob1['wait_time_startscan'] = .2 + 3 * wait_time scanjob1['minstrument'] = minstrument scanjob1['compensateGates'] = [] scanjob1['gate_values_corners'] = [[]] if sd.verbose: print('sensingdot_t: scan1D: gate %s, wait_time %.3f' % (sd.gg[1], wait_time)) alldata = qtt.measurements.scans.scan1D(sd.station, scanjob=scanjob1, verbose=sd.verbose) return alldata
[docs] def detuning_scan(sd, stepsize=2, nsteps=5, verbose=1, fig=None): """ Optimize the sensing dot by making multiple plunger scans for different detunings Args: stepsize (float) nsteps (int) Returns: best (list): list of optimal detuning and sd plunger value results (dict) """ gates = sd.station.gates gv0 = gates.allvalues() detunings = stepsize * np.arange(-(nsteps - 1) / 2, nsteps / 2) dd = [] pp = [] for ii, dt in enumerate(detunings): if verbose: print('detuning_scan: iteration %d: detuning %.3f' % (ii, dt)) gates.resetgates(sd.gate_names(), gv0, verbose=0) sd.detune(dt) p, result = sd.fastTune(fig=None, verbose=verbose >= 2) dd.append(result) pp.append(sd.goodpeaks[0]) gates.resetgates(sd.gate_names(), gv0, verbose=0) scores = [p['score'] for p in pp] bestidx = np.argmax(scores) optimal = [detunings[bestidx], pp[bestidx]['x']] if verbose: print('detuning_scan: best %d: detuning %.3f' % (bestidx, optimal[0])) results = {'detunings': detunings, 'scores': scores, 'bestpeak': pp[bestidx]} if fig: plt.figure(fig) plt.clf() for ii in range(len(detunings)): ds = dd[ii] p = pp[ii] y = ds.default_parameter_array() x = y.set_arrays[0] plt.plot(x, y, '-', label='scan %d: score %.3f' % (ii, p['score'])) xx = np.array([p['x'], p['y']]).reshape((2, 1)) qtt.pgeometry.plotLabels(xx, [scores[ii]]) plt.title('Detuning scans for %s' % sd.__repr__()) plt.xlabel(_scanlabel(result)) plt.legend(numpoints=1) if verbose >= 2: plt.figure(fig + 1) plt.clf() plt.plot(detunings, scores, '.', label='Peak scores') plt.xlabel('Detuning [mV?]') plt.ylabel('Score') plt.title('Best peak scores for different detunings') return optimal, results
[docs] def detune(self, value): """ Detune the sensing dot by the specified amount """ gl = getattr(self.station.gates, self.gg[0]) gr = getattr(self.station.gates, self.gg[2]) gl.increment(self._detune_axis[0] * value) gr.increment(self._detune_axis[1] * value)
[docs] def scan2D(sd, ds=90, stepsize=4, fig=None, verbose=1): """Make 2D-scan of the sensing dot.""" gv = sd.station.gates.allvalues() gg = sd.gg sdval = sd.sdval sd.station.gates.set(gg[1], sdval[1]) scanjob = qtt.measurements.scans.scanjob_t() scanjob['stepdata'] = dict( {'param': gg[0], 'start': sdval[0] + ds, 'end': sdval[0] - ds, 'step': stepsize}) scanjob['sweepdata'] = dict( {'param': gg[2], 'start': sdval[2] + ds, 'end': sdval[2] - ds, 'step': stepsize}) scanjob['minstrument'] = sd.minstrument if sd.verbose >= 1: print('sensing dot %s: performing barrier-barrier scan' % (sd,)) if verbose >= 2: print(scanjob) alldata = qtt.measurements.scans.scan2D(sd.station, scanjob, verbose=verbose >= 2) sd.station.gates.resetgates(gv, gv, verbose=0) if fig is not None: qtt.measurements.scans.plotData(alldata, fig=fig) return alldata
def _select_results(self, goodpeaks): if len(goodpeaks) > 0: self.sdval[1] = float(goodpeaks[0]['xhalfl']) self.targetvalue = float(goodpeaks[0]['yhalfl']) self._selected_peak = goodpeaks[0] else: self._selected_peak = None raise qtt.exceptions.CalibrationException('could not find good peak')
[docs] def autoTune(sd, scanjob=None, fig=200, outputdir=None, step=-2., max_wait_time=1., scanrange=300, add_slopes=False): """ Automatically determine optimal value of plunger """ if scanjob is not None: sd.autoTuneInit(scanjob) if sd.virt_gates is not None: raise Exception('virtual gates for slow scan not supported') alldata = sd.scan1D(outputdir=outputdir, step=step, scanrange=scanrange, max_wait_time=max_wait_time) alldata = sd._measurement_post_processing(alldata) goodpeaks = sd._process_scan(alldata, useslopes=add_slopes, fig=fig) sd._select_results(goodpeaks) if sd.verbose: print('sensingdot_t: autotune complete: value %.1f [mV]' % sd.sdval[1]) return sd.sdval[1], alldata
def _process_scan(self, alldata, useslopes=True, fig=None, invert=False, verbose=0): """ Determine peaks in 1D scan """ scan_sampling_rate = float(np.abs(alldata.metadata['scanjob']['sweepdata']['step'])) x, y = qtt.data.dataset1Ddata(alldata) x, y = peakdataOrientation(x, y) if invert: y = -y if useslopes: goodpeaks = findSensingDotPosition( x, y, useslopes=useslopes, fig=fig, verbose=verbose, sampling_rate=scan_sampling_rate) else: goodpeaks = coulombPeaks( x, y, verbose=verbose, fig=fig, plothalf=True, sampling_rate=scan_sampling_rate) if fig is not None: plt.xlabel('%s' % (self.tunegate(),)) plt.ylabel('%s' % (self.minstrument,)) plt.title('autoTune: %s' % (self.__repr__(),), fontsize=14) self.goodpeaks = goodpeaks self.data['tunex'] = x self.data['tuney'] = y return goodpeaks
[docs] def autoTuneInit(sd, scanjob, mode='center'): stepdata = scanjob.get('stepdata', None) sweepdata = scanjob['sweepdata'] stepparam = sweepdata['param'] sweepparam = sweepdata['param'] # set sweep to center gates = sd.station.gates gates.set( sweepparam, (sweepdata['start'] + sweepdata['end']) / 2) if stepdata is not None: if mode == 'end': # set sweep to center gates.set(stepparam, (stepdata['end'])) elif mode == 'start': # set sweep to center gates.set(stepparam, (stepdata['start'])) else: # set sweep to center gates.set(stepparam, (stepdata['start'] + stepdata['end']) / 2)
def _measurement_post_processing(self, dataset): if self.boxcar_filter_kernel_size > 1: process_dataarray(dataset, dataset.default_parameter_name(), None, partial( qtt.algorithms.generic.boxcar_filter, kernel_size=(self.boxcar_filter_kernel_size,))) return dataset
[docs] def fastTune(self, Naverage=90, sweeprange=79, period=1e-3, location=None, fig=201, sleeptime=2, delete=True, add_slopes=False, invert=False, verbose=1): """ Fast tuning of the sensing dot plunger. If the sensing dot object is initialized with a virtual gates object the virtual plunger will be used for the sweep. Args: Naverage (int): number of averages scanrange (float): Range to be used for scanning period (float): Period to be used in the scan sweep fig (int or None): window for plotting results Returns: plungervalue (float): value of plunger alldata (dataset): measured data """ if self.minstrument is not None: instrument = self.minstrument[0] channel = self.minstrument[1] if not isinstance(channel, list): channel = [channel] scanjob = qtt.measurements.scans.scanjob_t( {'Naverage': Naverage, }) if self.virt_gates is not None: vsensorgate = self.virt_gates.vgates()[self.virt_gates.pgates().index(self.gg[1])] scanjob['sweepdata'] = qtt.measurements.scans.create_vectorscan( self.virt_gates.parameters[vsensorgate], g_range=sweeprange, remove_slow_gates=True, station=self.station) scanjob['sweepdata']['paramname'] = vsensorgate else: gate = self.gg[1] cc = self.station.gates.get(gate) scanjob['sweepdata'] = {'param': gate, 'start': cc - sweeprange / 2, 'end': cc + sweeprange / 2, 'step': 4} scanjob['sweepdata']['period'] = period scanjob['minstrument'] = channel scanjob['minstrumenthandle'] = instrument scanjob['wait_time_startscan'] = sleeptime scanjob['dataset_label'] = 'sensingdot_fast_tune' alldata = qtt.measurements.scans.scan1Dfast(self.station, scanjob) else: raise Exception('legacy code, please do not use') alldata.add_metadata({'scanjob': scanjob, 'scantype': 'fastTune'}) alldata.add_metadata({'snapshot': self.station.snapshot()}) alldata = self._measurement_post_processing(alldata) alldata.write(write_metadata=True) goodpeaks = self._process_scan(alldata, useslopes=add_slopes, fig=fig, invert=invert) self._select_results(goodpeaks) if self.verbose: print('sensingdot_t: autotune complete: value %.1f [mV]' % self.sdval[1]) return self.sdval[1], alldata
[docs]class VectorParameter(qcodes.instrument.parameter.Parameter): """Create parameter which controls linear combinations. Attributes: name (str): the name given to the new parameter comb_map (list): tuples with in the first entry a parameter and in the second a coefficient coeffs_sum (float): the sum of all the coefficients """ def __init__(self, name, comb_map, **kwargs): """Initialize a linear combination parameter.""" super().__init__(name, **kwargs) self.comb_map = comb_map self.unit = self.comb_map[0][0].unit self.coeffs_sum = sum([np.abs(coeff) for (param, coeff) in self.comb_map])
[docs] def get_raw(self): """Return the value of this parameter.""" value = sum([coeff * param.get() for (param, coeff) in self.comb_map]) return value
[docs] def set_raw(self, value): """Set the parameter to value. Note: the set is not unique, i.e. the result of this method depends on the previous value of this parameter. Args: value (float): the value to set the parameter to. """ val_diff = value - self.get() for (param, coeff) in self.comb_map: param.set(param.get() + coeff * val_diff / self.coeffs_sum)
# %%
[docs]class MultiParameter(qcodes.instrument.parameter.Parameter): """ Create a parameter which is a combination of multiple other parameters. Attributes: name (str): name for the parameter params (list): the parameters to combine """ def __init__(self, name, params, label=None, unit=None, **kwargs): super().__init__(name, **kwargs) self.params = params self.vals = qcodes.utils.validators.Anything() self._instrument = 'dummy' if label is None: self.label = self.name if unit is None: self.unit = 'a.u.' else: self.unit = unit self.vals = None
[docs] def get_raw(self): values = [] for p in self.params: values.append(p.get()) return values
[docs] def set_raw(self, values): for idp, p in enumerate(self.params): p.set(values[idp])
[docs]class CombiParameter(qcodes.instrument.parameter.Parameter): """ Create a parameter which is a combination of multiple other parameters, which are always set to the same value. The `get` function returns the mean of the individual parameters. Attributes: name (str): name for the parameter params (list): the parameters to combine """ def __init__(self, name, params, label=None, unit='a.u.', **kwargs): super().__init__(name, vals=qcodes.utils.validators.Anything(), unit=unit, **kwargs) self.params = params if label is None: self.label = self.name else: self.label = label
[docs] def get_raw(self): values = [] for p in self.params: values.append(p.get()) return np.mean(values)
[docs] def set_raw(self, value): for idp, p in enumerate(self.params): p.set(value)