Skip to content
Snippets Groups Projects
Commit b906a8df authored by Stephan Philips's avatar Stephan Philips
Browse files

merge

parent 3a51e5d1
No related branches found
No related tags found
No related merge requests found
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
import numpy as np
import datetime
from copy import deepcopy
import matplotlib.pyplot as plt
import qcodes.instrument_drivers.Keysight.SD_common.SD_AWG as keysight_awg
import qcodes.instrument_drivers.Keysight.SD_common.SD_DIG as keysight_dig
class keysight_AWG():
def __init__(self, segment_bin, channel_locations,channels):
self.awg = dict()
self.awg_memory = dict()
# dict containing the number of the last segment number.
self.segment_count = dict()
self.current_waveform_number = 0
self.channel_locations = channel_locations
self.channels = channels
self.vpp_max = 3 #Volt
# setting for the amount of voltage you can be off from the optimal setting for a channels
# e.g. when you are suppose to input
self.voltage_tolerance = 0.2
# data struct that contains the basic information for where in the memory of the awg which segment can be found.
# Note that this might change when inplementing advanved looping.
self.segmentdata = dict()
for i in self.channels:
self.segmentdata[i] = dict() #name segment + location and date of construction of segment
self.vpp_data = dict()
for i in self.channels:
self.vpp_data[i] = {"v_pp" : None, "v_off" : None}
self.v_min_max_combined = dict()
for i in self.channels:
self.v_min_max_combined[i] = {"v_min" : None, "v_max" : None}
self.segment_bin = segment_bin
self.maxmem = 1e9
@property
def allocatable_mem(self):
alloc = self.maxmem
for i in self.awg_memory.items():
if alloc > i[1]:
allow = i[1]
return alloc
def get_new_segment_number(self, channel):
'''
gets a segment number for the new segment. These numbers just need to be unique.
'''
awg_name = self.channel_locations[channel][0]
self.segment_count[awg_name] += 1
if self.segment_count[awg_name] > 2000 :
print("number of segments on the awg (",self.segment_count[awg_name], ")is greater than 2000, this might cause problems?")
return self.segment_count[awg_name]
def upload(self, sequence_data_raw, sequence_data_processed):
# TODO put at better location later
self.set_channel_properties()
self.flush_queues()
# step 1 collect vmin and vmax data, check if segments are intialized (e.g. have at least one pulse):
for i in sequence_data_raw:
segment_name = i[0]
if self.segment_bin.used(segment_name) == False:
raise ValueError("Empty segment provided .. (segment name: '{}')".format(segment_name))
v_min_max = self.segment_bin.get_segment(segment_name).Vmin_max_data
self.adjust_vmin_vmax_data(v_min_max)
# step 2 calculate Vpp/Voff needed for each channel + assign the voltages to each channel.
self.adjust_vpp_data()
# step 3 check memory allocation (e.g. what can we reuse of the sequences in the memory of the AWG.)
mem_needed = dict()
for i in self.awg:
mem_needed[i] = 0
for chan, sequence_data in sequence_data_processed.items():
# loop trough all elements in the sequence and check how much data is needed.
t = 0
for i in sequence_data:
segment_name = i['segment']
repetitions= i['ntimes']
unique = i['unique']
# Check if stuff in the memory, if present, needs to be updated.
if segment_name in self.segmentdata[chan] and unique == False:
if self.segment_bin.get_segment(segment_name).last_mod <= self.segmentdata[chan][segment_name]['last_edit']:
continue
if unique == True:
mem_needed[self.channel_locations[chan][0]] += self.segment_bin.get_segment(segment_name).total_time * repetitions
else:
mem_needed[self.channel_locations[chan][0]] += self.segment_bin.get_segment(segment_name).total_time
# If memory full, clear (if one is full it is very likely all others are also full, so we will just clear everything.)
for i in self.awg:
if mem_needed[i] > self.allocatable_mem:
print("memory cleared .. upload will take a bit longer.")
self.clear_mem()
# step 4 upload the sequences to the awg.
for chan, sequence_data in sequence_data_processed.items():
# Upload here sequences.
# plt.figure()
# Keep counting time of the segments. This is important for IQ data.
time = 0
for my_segment in sequence_data:
segment_name = my_segment['segment']
repetitions= my_segment['ntimes']
unique = my_segment['unique']
# Check if we need to skip the upload.
if segment_name in self.segmentdata[chan] and unique == False:
if self.segment_bin.get_segment(segment_name).last_mod <= self.segmentdata[chan][segment_name]['last_edit']:
continue
if unique == False:
points = self.get_and_upload_waveform(chan,segment_name, time)
time += points
else:
for uuid in range(repetitions):
# my_segment['identifier'] = list with unique id's
points = self.get_and_upload_waveform(chan,segment_name, time, my_segment['identifier'][uuid])
time += points
<<<<<<< HEAD
# step 5 make the queue in the AWG.
print(sequence_data_processed)
=======
print(self.vpp_data)
# step 5 make the queue in the AWG.
for chan, sequence_data in sequence_data_processed.items():
# get relevant awg
awg_name = self.channel_locations[chan][0]
awg_number = self.channel_locations[chan][1]
# First element needs to have the HVI trigger.
first_element = True
for segmentdata in sequence_data:
if segmentdata['unique'] == True:
for uuid in segmentdata['identifier']:
seg_num = self.segmentdata[chan][uuid]['mem_pointer']
if first_element == True:
trigger_mode = 1
first_element = False
else :
trigger_mode = 0
start_delay = 0
cycles = 1
prescaler = 0
self.awg[awg_name].awg_queue_waveform(awg_number,seg_num,trigger_mode,start_delay,cycles,prescaler)
else:
seg_num = self.segmentdata[chan][segmentdata['segment']]['mem_pointer']
if first_element == True:
trigger_mode = 1
first_element = False
else :
trigger_mode = 0
start_delay = 0
cycles = segmentdata['ntimes']
prescaler = 0
self.awg[awg_name].awg_queue_waveform(awg_number,seg_num,trigger_mode,start_delay,cycles,prescaler)
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
def get_and_upload_waveform(self, channel, segment_name, time, uuid=None):
'''
get the wavform for channel with the name segment_name.
The waveform occurs at time time in the sequence.
This function also adds the waveform to segmentdata variable
'''
seg_number = self.get_new_segment_number(channel)
<<<<<<< HEAD
segment_data = self.segment_bin.get_segment(segment_name).get_waveform(channel, self.vpp_data, time)
=======
segment_data = self.segment_bin.get_segment(segment_name).get_waveform(channel, self.vpp_data, time, np.float32)
wfv = keysight_awg.SD_AWG.new_waveform_from_double(0, segment_data)
awg_name = self.channel_locations[channel][0]
self.awg[awg_name].load_waveform(wfv, seg_number)
# print("plotting {}, {}".format(channel, segment_name))
# plt.plot(segment_data)
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
last_mod = self.segment_bin.get_segment(segment_name).last_mod
# upload data
if uuid is None:
self.segmentdata[channel][segment_name] = dict()
self.segmentdata[channel][segment_name]['mem_pointer'] = seg_number
self.segmentdata[channel][segment_name]['last_edit'] = last_mod
else:
self.segmentdata[channel][uuid] = dict()
self.segmentdata[channel][uuid]['mem_pointer'] = seg_number
self.segmentdata[channel][uuid]['last_edit'] = last_mod
<<<<<<< HEAD
=======
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
return len(segment_data)
def adjust_vmin_vmax_data(self, Vmin_max_data):
'''
Function that updates the values of the minimun and maximun volages needed for each channels.
Input dict with for each channel max and min voltage for al segments that will be played in a sequence.
'''
for i in self.channels:
if self.v_min_max_combined[i]['v_min'] is None:
self.v_min_max_combined[i]['v_min'] = Vmin_max_data[i]['v_min']
self.v_min_max_combined[i]['v_max'] = Vmin_max_data[i]['v_max']
continue
if self.v_min_max_combined[i]['v_min'] > Vmin_max_data[i]['v_min']:
self.v_min_max_combined[i]['v_min'] = Vmin_max_data[i]['v_min']
if self.v_min_max_combined[i]['v_max'] < Vmin_max_data[i]['v_max']:
self.v_min_max_combined[i]['v_max'] = Vmin_max_data[i]['v_max']
def adjust_vpp_data(self):
'''
Function that adjust the settings of the peak to peak voltages of the awg.
Check if the sequence can be made with the current settings, if not, all the memory will be purged.
The reason not only to purge the channels where it is needed is in the case of the use of virtual gates,
since then all channels will need to be reuploaded anyway ..
An option to manually enforce a vpp and voff might also be nice?
'''
# 1) generate vpp needed, and check if it falls in the range allowed.
vpp_test = deepcopy(self.vpp_data)
voltage_range_reset_needed = False
for i in self.channels:
vmin = self.v_min_max_combined[i]['v_min']
vmax = self.v_min_max_combined[i]['v_max']
# Check if voltages are physical -- note that the keysight its offset is kind of a not very proper defined parameter.
if vmax > self.vpp_max/2:
raise ValueError("input range not supported (voltage of {} V detected) (max {} V)".format(vmax, self.vpp_max/2))
if vmin < - self.vpp_max/2:
raise ValueError("input range not supported (voltage of {} V detected) (min {} V)".format(vmin, -self.vpp_max/2))
# check if current settings of the awg are fine.
if self.vpp_data[self.channels[0]]['v_pp'] is not None:
vpp_current = self.vpp_data[i]['v_pp']
voff_current = self.vpp_data[i]['v_off']
vmin_current = voff_current - vpp_current
vmax_current = voff_current + vpp_current
if vmin_current > vmin or vmax_current < vmax:
voltage_range_reset_needed = True
# note if the voltages needed are significantly smaller, we also want to do a voltage reset.
if vmin_current*(1-2*self.voltage_tolerance) < vmin or vmax_current*(1-2*self.voltage_tolerance) > vmax:
voltage_range_reset_needed = True
# convert to peak to peak and offset voltage.
vpp_test[i]['v_pp'] =(vmax - vmin)/2
vpp_test[i]['v_off']= (vmax + vmin)/2
# 2) if vpp fals not in old specs, clear memory and add new ranges.
if self.vpp_data[self.channels[0]]['v_pp'] is None or voltage_range_reset_needed == True:
<<<<<<< HEAD
if self.vpp_data[self.channels[0]]['v_pp'] is not None:
self.clear_mem()
=======
self.clear_mem()
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
for i in self.channels:
self.update_vpp_single(vpp_test[i],self.vpp_data[i], i)
def update_vpp_single(self, new_data, target, channel):
'''
Update the voltages, with the tolerance build in.
'''
new_vpp = new_data['v_pp'] * (1 + self.voltage_tolerance)
if new_vpp > self.vpp_max/2:
new_vpp = self.vpp_max/2
awg_name = self.channel_locations[channel][0]
chan_number = self.channel_locations[channel][1]
self.awg[awg_name].set_channel_amplitude(new_vpp,chan_number)
self.awg[awg_name].set_channel_offset(new_data['v_off'],chan_number)
target['v_pp'] = new_vpp
target['v_off']= new_data['v_off']
def start(self):
'''
Function to apply the set the right triggering for the keysight AWG units.
Triggering is done via the PXI triggers to make sure the that the system works correctly.
'''
# use a awg to send the trigger (does not matter which one)(here the first defined channel)
for chan_name, chan_data in self.channel_locations.items():
self.awg[chan_data[0]].awg_start(chan_data[1])
# Launch the right HVI instance
def add_awg(self, name, awg):
'''
add an awg to tge keysight object. As awg a qcodes object from the keysight driver is expected.
name is name you want to give to your awg object. This needs to be unique and should is to describe which
channel belongs to which awg.
'''
# Make sure you start with a empty memory
# awg.flush_waveform()
self.awg[name] = awg
self.awg_memory[name] =self.maxmem
self.segment_count[name] = 0
def clear_mem(self):
'''
Clears ram of all the AWG's
Clears segment_loc on pc. (TODO)
'''
print("AWG memory is being cleared.")
self.segmentdata = dict()
for i in self.awg.items():
i[1].flush_waveform(True)
for awg, count in self.segment_count.items():
count = 0
self.segmentdata = dict()
for i in self.channels:
self.segmentdata[i] = dict() #name segment + location and date of construction of segment
for i in self.awg_memory:
i = self.maxmem
print("Done.")
def flush_queues(self):
'''
Remove all the queues form the channels in use.
'''
# awg2.awg_stop(1)
print("all queue cleared")
for channel, channel_loc in self.channel_locations.items():
self.awg[channel_loc[0]].awg_stop(channel_loc[1])
self.awg[channel_loc[0]].awg_flush(channel_loc[1])
def set_channel_properties(self):
'''
Sets how the channels should behave e.g., for now only arbitrary wave implemented.
'''
print("channels set.")
for channel, channel_loc in self.channel_locations.items():
# 6 is the magic number of the arbitary waveform shape.
self.awg[channel_loc[0]].set_channel_wave_shape(6,channel_loc[1])
self.awg[channel_loc[0]].awg_queue_config(channel_loc[1], 1)
import numpy as np
import datetime
import matplotlib.pyplot as plt
class segment_container():
'''
Class containing all the single segments for for a series of channels.
This is a organisational class.
Class is capable of checking wheather upload is needed.
Class is capable of termining what volatages are required for each channel.
Class returns vmin/vmax data to awg object
Class returns upload data as a int16 array to awg object.
'''
def __init__(self, name, channels):
self.channels = channels
self.name = name
self.waveform_cache = None
self._Vmin_max_data = dict()
for i in self.channels:
self._Vmin_max_data[i] = {"v_min" : None, "v_max" : None}
self.prev_upload = datetime.datetime.utcfromtimestamp(0)
# self.vpp_data = dict()
# for i in self.channels:
# self.vpp_data[i] = {"V_min" : None, "V_max" : None}
# Not superclean should be in a different namespace.
for i in self.channels:
setattr(self, i, segment_single())
@property
def total_time(self):
time_segment = 0
for i in self.channels:
if time_segment <= getattr(self, i).total_time:
time_segment = getattr(self, i).total_time
return time_segment
@property
def last_mod(self):
time = datetime.datetime.utcfromtimestamp(0)
for i in self.channels:
if getattr(self, i, segment_single()).last_edit > time:
time = getattr(self, i, segment_single()).last_edit
return time
@property
def Vmin_max_data(self):
if self.prev_upload < self.last_mod:
self.prep4upload()
for i in range(len(self.channels)):
self._Vmin_max_data[self.channels[i]]['v_min'] = np.min(self.waveform_cache[i,:])
self._Vmin_max_data[self.channels[i]]['v_max'] = np.max(self.waveform_cache[i,:])
return self._Vmin_max_data
def reset_time(self):
'''
Allings all segments togeter and sets the input time to 0,
e.g. ,
chan1 : waveform until 70 ns
chan2 : waveform until 140ns
-> totaltime will be 140 ns,
when you now as a new pulse (e.g. at time 0, it will actually occur at 140 ns in both blocks)
'''
maxtime = 0
for i in self.channels:
k = getattr(self, i)
t = k.get_total_time()
if t > maxtime:
maxtime = t
for i in self.channels:
getattr(self, i).starttime = maxtime
def prep4upload(self):
# make waveform (in chache) (only if needed)
t_tot = self.total_time
if self.prev_upload < self.last_mod or self.waveform_cache is None:
self.waveform_cache = np.empty([len(self.channels), int(t_tot)])
for i in range(len(self.channels)):
self.waveform_cache[i,:] = getattr(self, self.channels[i]).get_sequence(t_tot)
<<<<<<< HEAD
def get_waveform(self, channel, Vpp_data, sequenc_time, return_type = np.int16):
=======
def get_waveform(self, channel, Vpp_data, sequenc_time, return_type = np.double):
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
# get waforms for required channels. For global Vpp, Voffset settings (per channel) and expected data type
self.prep4upload()
upload_data = np.empty([int(self.total_time)], dtype = return_type)
chan_number = None
for i in range(len(self.channels)):
if self.channels[i] == channel:
chan_number = i
# do not devide by 0 (means channels is not used..)
if Vpp_data[channel]['v_pp'] == 0:
Vpp_data[channel]['v_pp'] = 1
# normalise according to the channel, put as
upload_data = ((self.waveform_cache[chan_number,:] - Vpp_data[channel]['v_off'])/Vpp_data[channel]['v_pp']).astype(return_type)
<<<<<<< HEAD
=======
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
return upload_data
def clear_chache():
return
<<<<<<< HEAD
=======
>>>>>>> 545dc60b07dc323aefd2ac6990fa4f00980df3d0
def last_edited(f):
'''
just a simpe decoter used to say that a certain wavefrom is updaded and therefore a new upload needs to be made to the awg.
'''
def wrapper(*args):
args[0].last_edit = datetime.datetime.now()
return f(*args)
return wrapper
class segment_single():
'''
Class defining single segments for one sequence.
This is at the moment rather basic. Here should be added more fuctions.
'''
def __init__(self):
self.type = 'default'
self.to_swing = False
self.starttime = 0
self.last_edit = datetime.datetime.now()
self.my_pulse_data = np.zeros([1,2])
self.last = None
self.IQ_data = [] #todo later.
self.unique = False
@last_edited
def add_pulse(self,array):
'''
format ::
[[t0, Amp0],[t1, Amp1]]
'''
arr = np.asarray(array)
arr[:,0] = arr[:,0] + self.starttime
self.my_pulse_data = np.append(self.my_pulse_data,arr, axis=0)
@last_edited
def add_block(self,start,stop, amplitude):
amp_0 = self.my_pulse_data[-1,1]
pulse = [ [start, amp_0], [start,amplitude], [stop, amplitude], [stop, amp_0]]
self.add_pulse(pulse)
@last_edited
def wait(self, wait):
amp_0 = self.my_pulse_data[-1,1]
t0 = self.my_pulse_data[-1,0]
pulse = [[wait+t0, amp_0]]
self.add_pulse(pulse)
def repeat(self, number):
return
def add_np(self,start, array):
return
def add_IQ_pair():
return
def add():
return
@property
def total_time(self,):
return self.my_pulse_data[-1,0]
def get_total_time(self):
return self.my_pulse_data[-1,0]
def get_sequence(self, points = None):
'''
Returns a numpy array that contains the points for each ns
points is the expected lenght.
'''
t, wvf = self._generate_sequence(points)
return wvf
@property
def v_max(self):
return np.max(self.my_pulse_data[:,1])
@property
def v_min(self):
return np.min(self.my_pulse_data[:,1])
def _generate_sequence(self, t_tot= None):
# 1 make base sequence:
if t_tot is None:
t_tot = self.total_time
times = np.linspace(0, int(t_tot-1), int(t_tot))
my_sequence = np.zeros([int(t_tot)])
for i in range(0,len(self.my_pulse_data)-1):
my_loc = np.where(times < self.my_pulse_data[i+1,0])[0]
my_loc = np.where(times[my_loc] >= self.my_pulse_data[i,0])[0]
if my_loc.size==0:
continue;
end_voltage = self.my_pulse_data[i,1] + \
(self.my_pulse_data[i+1,1] - self.my_pulse_data[i,1])* \
(times[my_loc[-1]] - self.my_pulse_data[i,0])/ \
(self.my_pulse_data[i+1,0]- self.my_pulse_data[i,0])
start_stop_values = np.linspace(self.my_pulse_data[i,1],end_voltage,my_loc.size);
my_sequence[my_loc] = start_stop_values;
return times, my_sequence
def plot_sequence(self):
x,y = self._generate_sequence()
plt.plot(x,y)
plt.show()
class marker_single():
def __init__(self):
self.type = 'default'
self.swing = False
self.latest_time = 0
self.my_pulse_data = np.zeros([1,2])
def add(self, start, stop):
self.my_pulse_data = np.append(self.my_pulse_data, [[start,0],[start,1],[stop,1],[stop,0]])
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment