Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions qcodes/instrument_drivers/AlazarTech/ATS.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import numpy as np
import os
import inspect

from qcodes.instrument.base import Instrument
from qcodes.instrument.parameter import Parameter
Expand Down Expand Up @@ -198,6 +199,10 @@ def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs):

self.buffer_list = []

# Some ATS models do not support a bwlimit. This flag defines if the
# ATS supports a bwlimit or not. True by default.
self._bwlimit_support = True

def get_idn(self):
board_kind = self._board_names[
self._ATS_dll.AlazarGetBoardKind(self._handle)]
Expand Down Expand Up @@ -334,13 +339,14 @@ def config(self, clock_source=None, sample_rate=None, clock_edge=None,

for i in range(1, self.channels + 1):
self._call_dll('AlazarInputControl',
self._handle, i,
self._handle, 2**(i-1), # Channel in binary format
self.parameters['coupling' + str(i)],
self.parameters['channel_range' + str(i)],
self.parameters['impedance' + str(i)])
self._call_dll('AlazarSetBWLimit',
self._handle, i,
self.parameters['bwlimit' + str(i)])
if self._bwlimit_support:
self._call_dll('AlazarSetBWLimit',
self._handle, i,
self.parameters['bwlimit' + str(i)])

self._call_dll('AlazarSetTriggerOperation',
self._handle, self.trigger_operation,
Expand Down Expand Up @@ -796,12 +802,59 @@ def __init__(self, name, alazar_name, **kwargs):
str(alazar_name) +
" was found on this instrument server")

self._acquisitionkwargs = {}
# Obtain a list of all valid ATS acquisition kwargs
# These will be the kwargs that can be added to acquisitionkwargs
self._acquisitionkwargs_names = list(inspect.signature(
self.alazar.acquire).parameters.keys())
# Remove acquisition_controller, because it is not JSON-compatible
self._acquisitionkwargs_names.remove('acquisition_controller')
self.add_parameter(name='acquisitionkwargs',
get_cmd=lambda: self._acquisitionkwargs)

def _get_alazar(self):
return self.alazar

def _set_alazar(self, alazar):
self.alazar = alazar

def get_acquisitionkwarg(self, kwarg):
"""
Obtain an acquisitionkwarg for the ATS.
It first checks if the kwarg is an actual ATS acquisitionkwarg,
and raises an error otherwise.
It then checks if the kwarg is in ATS_controller._acquisitionkwargs.
If not, it will retrieve the ATS latest parameter value

Args:
kwarg: acquisitionkwarg to look for

Returns:
Value of the acquisitionkwarg
"""
assert kwarg in self._acquisitionkwargs_names, \
"Kwarg {} is not a valid ATS acquisitionkwarg".format(kwarg)
if kwarg in self._acquisition_kwargs.keys():
return self._acquisition_kwargs[kwarg]
else:
# Must get latest value, since it will not be updated in ATS
return self.alazar.parameters[kwarg].get_latest()

def update_acquisitionkwargs(self, **kwargs):
"""
Update the ATS_controller acquisitionkwargs, but only if all kwargs are
actually valid kwargs in the function ATS.acquire()
Args:
kwargs: Updated ATS acquisition kwargs
Returns:
None
"""
kwargs_valid = all(map(
lambda kwarg: kwarg in self._acquisitionkwargs_names,
kwargs.keys()))
assert kwargs_valid, 'Not all kwargs are valid ATS acquisitionkwargs'
self._acquisitionkwargs.update(**kwargs)

def pre_start_capture(self):
"""
Use this method to prepare yourself for the data acquisition
Expand Down
2 changes: 1 addition & 1 deletion qcodes/instrument_drivers/AlazarTech/ATS9870.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def __init__(self, name, **kwargs):
parameter_class=AlazarParameter,
label='Trigger Engine ' + i,
unit=None,
value='TRIG_ENGINE_' + ('J' if i == 0 else 'K'),
value='TRIG_ENGINE_'+('J' if i == '1' else 'K'),
byte_to_value_dict={0: 'TRIG_ENGINE_J',
1: 'TRIG_ENGINE_K'})
self.add_parameter(name='trigger_source' + i,
Expand Down
142 changes: 134 additions & 8 deletions qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,146 @@
from .ATS import AcquisitionController
import math
import numpy as np
from qcodes.instrument.parameter import ManualParameter
from qcodes.utils import validators as vals


class Basic_AcquisitionController(AcquisitionController):
"""Basic AcquisitionController tested on ATS9360
returns unprocessed data averaged by record with 2 channels
"""
def __init__(self, name, alazar_name, **kwargs):
self.samples_per_record = None
self.records_per_buffer = None
self.buffers_per_acquisition = None
self.buffer = None
super().__init__(name, alazar_name, **kwargs)
self.alazar = self._get_alazar()

self.add_parameter(name='average_mode',
parameter_class=ManualParameter,
initial_value='trace',
vals=vals.Enum('none', 'trace', 'point'))


self.buffer_idx = 0
# Names and shapes must have initial value, even though they will be
# overwritten in set_acquisitionkwargs. If we don't do this, the
# RemoteInstrument will not recognize that it returns multiple values.
self.add_parameter(name="acquisition",
names=['channel_signal'],
get_cmd=self.do_acquisition,
shapes=((),),
snapshot_value=False)

def setup(self, **kwargs):
"""
This function sets up the ATS for an acquisition.
In particular, it updates the acquisition kwargs, and the attributes
for the parameter acquisition.
These attributes depend on the controller's average_mode.
This function must be performed after setting the acquisitionkwargs,
and before starting an actual Loop
"""

self.update_acquisitionkwargs(**kwargs)

channel_selection = self.get_acquisitionkwarg('channel_selection')
samples_per_record = self.get_acquisitionkwarg('samples_per_record')
records_per_buffer = self.get_acquisitionkwarg('records_per_buffer')
buffers_per_acquisition = self.get_acquisitionkwarg(
'buffers_per_acquisition')

self.acquisition.names = tuple(
['Channel_{}_signal'.format(ch) for ch in channel_selection])

self.acquisition.labels = self.acquisition.names
self.acquisition.units = ['V'*len(channel_selection)]

if self.average_mode() == 'point':
self.acquisition.shapes = tuple([()]*len(channel_selection))
elif self.average_mode() == 'trace':
shape = (samples_per_record,)
self.acquisition.shapes = tuple([shape] * len(channel_selection))
else:
shape = (records_per_buffer * buffers_per_acquisition,
samples_per_record)
self.acquisition.shapes = tuple([shape] * len(channel_selection))

def do_acquisition(self):
value = self._get_alazar().acquire(acquisition_controller=self,
**self.acquisitionkwargs())
return value

def pre_start_capture(self):
alazar = self._get_alazar()
number_of_channels = len(alazar.channel_selection.get())
self.buffer_idx = 0
if self.average_mode() in ['point', 'trace']:
self.buffer = np.zeros(alazar.samples_per_record.get() *
alazar.records_per_buffer.get() *
number_of_channels)
else:
self.buffer = np.zeros((alazar.buffers_per_acquisition.get(),
alazar.samples_per_record.get() *
alazar.records_per_buffer.get() *
number_of_channels))

def pre_acquire(self):
# gets called after 'AlazarStartCapture'
pass

def handle_buffer(self, data):
if self.buffer_idx < self.buffers_per_acquisition:
if self.average_mode() in ['point', 'trace']:
self.buffer += data
else:
self.buffer[self.buffer_idx] = data
else:
print('*'*20+'\nIgnoring extra ATS buffer')
self.buffer_idx += 1

def post_acquire(self):
# Perform averaging over records.
# The averaging mode depends on parameter average_mode
records_per_acquisition = self.buffers_per_acquisition * \
self.records_per_buffer

def channel_offset(ch):
return ch * self.samples_per_record * self.records_per_buffer

if self.average_mode() == 'none':
records = [self.buffer[:,
channel_offset(ch):channel_offset(ch+1)].reshape(
(records_per_acquisition, self.samples_per_record))
for ch in range(self.number_of_channels)]
elif self.average_mode() == 'trace':
records = [np.zeros(self.samples_per_record) for _ in
range(self.number_of_channels)]

for channel in range(self.number_of_channels):
for i in range(self.records_per_buffer):
i0 = channel_offset(channel) + i * self.samples_per_record
i1 = i0 + self.samples_per_record
records[channel] += \
self.buffer[i0:i1] / records_per_acquisition
elif self.average_mode() == 'point':
trace_length = self.samples_per_record * self.records_per_buffer
records = [np.mean(self.buffer[i*trace_length:(i+1)*trace_length]) /
records_per_acquisition
for i in range(self.number_of_channels)]

# Scale datapoints
for i, record in enumerate(records):
channel_range = eval('self.alazar.channel_range{}()'.format(i + 1))
records[i] = 2 * (record / 2 ** 16 - 0.5) * channel_range
return records


# DFT AcquisitionController
class DFT_AcquisitionController(AcquisitionController):
def __init__(self, name, alazar_name, demodulation_frequency, **kwargs):
self.demodulation_frequency = demodulation_frequency
self.acquisitionkwargs = {'acquisition_controller': self}
self.samples_per_record = None
self.records_per_buffer = None
self.buffers_per_acquisition = None
Expand All @@ -21,13 +154,6 @@ def __init__(self, name, alazar_name, demodulation_frequency, **kwargs):
super().__init__(name, alazar_name, **kwargs)
self.add_parameter("acquisition", get_cmd=self.do_acquisition)

def set_acquisitionkwargs(self, **kwargs):
self.acquisitionkwargs.update(**kwargs)

def do_acquisition(self):
value = self._get_alazar().acquire(**self.acquisitionkwargs)
return value

def pre_start_capture(self):
alazar = self._get_alazar()
self.samples_per_record = alazar.samples_per_record.get()
Expand Down