def __init__( self, awgparam='Z:/Tweezer/Experimental/AOD/2D AOD/Array normalisation/6x1array.txt', image_dir='Z:/Tweezer/Experimental/AOD/2D AOD/Array normalisation/Normalised', cam_roi=None, fit_roi_size=50, freq_amp_max=[1, 1]): ### set up AWG self.awg = AWG([0, 1], sample_rate=int(1024e6)) fdir = 'Z:/Tweezer/Experimental/Setup and characterisation/Settings and calibrations/tweezer calibrations/AWG calibrations' self.awg.setCalibration(0, fdir + '/814_H_calFile_17.02.2022.txt', freqs=np.linspace(85, 110, 100), powers=np.linspace(0, 1, 200)) self.awg.setCalibration(1, fdir + '/814_V_calFile_17.02.2022.txt', freqs=np.linspace(85, 115, 100), powers=np.linspace(0, 1, 200)) self.awg.load(awgparam) self.awg.param_file = awgparam self.awg.setTrigger(0) # 0 software, 1 ext0 seg = self.awg.filedata["segments"]["segment_0"] self.f0 = eval(seg["channel_0"]["freqs_input_[MHz]"]) self.f1 = eval(seg["channel_1"]["freqs_input_[MHz]"]) self.ncols = len(self.f0) self.nrows = len(self.f1) self.a0 = np.array(eval(seg["channel_0"]["freq_amp"]), dtype=float) self.a1 = np.array(eval(seg["channel_1"]["freq_amp"]), dtype=float) self.ulim = freq_amp_max self.amp = int(seg["channel_0"]["tot_amp_[mV]"]) #### set up camera self.cam = Camera(exposure=7, gain=1, roi=cam_roi) self.awg.start() time.sleep(0.5) self.cam.auto_gain_exposure() self.cam.update_exposure(self.cam.exposure * 0.6) # saturating is bad #### image handler saves images self.imhand = ImageHandler(image_dir=image_dir, measure_params={ 'rows': self.nrows, 'columns': self.ncols, 'AWGparam_file': awgparam }) self.imhand.create_dirs() #### fitr extracts trap positions and intensities from an image self.fitr = imageArray(dims=(self.ncols, self.nrows), roi_size=fit_roi_size, fitmode='sum') self.fitr.setRef(self.get_image(-1, -1, auto_exposure=False))
def __init__(self, f0=135, f1=185, nfreqs=50, fset=166, pwr=1, tol=1e-3, sleep=0.5): self.status = 'checking' # parameters self.f0 = f0 # lower bound self.f1 = f1 # upper bound self.nfreqs = nfreqs # number of frequencies self.fset = fset # setpoint self.pwr = pwr # amplitude self.tol = tol # tolerance self.sleep = sleep # sleep duration # self.fs = np.linspace(f0, f1, nfreqs) # frequencies to test self.vs = np.ones(nfreqs) * 200 # amplitude mV at those freqs self.v = 200 # current amplitude being tried self.i = 0 # current index being set self.setpoint = 1 # DAQ measurement to match to self.n = 0 # counter for number of measurements # setup self.s = PyServer(host='', port=8622) # server for DAQ self.s.textin.connect(self.respond) self.s.start() self.dxs = PyServer(host='', port=8620) # server for DExTer # self.dxs.textin.connect(self.respond) self.dxs.start() self.t = AWG([0, 1]) self.t.setNumSegments(128) self.t.setTrigger(0) # software trigger self.t.setSegDur(0.002) # segment, action, duration, freqs, numTraps, separation, freqAdjust, ampAdjust # self.t.setSegment(0, 1, 0.02, [fset], 1, 9, amp, [1], [0], False, False) # single trap # # step, segment, numLoops, nextStep, triggerCondition # self.t.setStep(0,0,1,0,1) # infinite loop # self.t.start() # self.t.setSegment(0, self.t.dataGen(0,0,'static',1,[fset],1,9, amp,[1],[0],False,False)) # self.t.setStep(0,0,1,0,1) self.t.load( r'Z:\Tweezer\Code\Python 3.5\PyDex\awg\AWG template sequences\2channel_swap_static.txt' )
def __init__(self, AWG_channels=[0]): # Rearrangement variables self.awg = AWG(AWG_channels) # opens AWG card and initiates self.awg.setNumSegments(32) self.activate_rearr(False) self.movesDict = { } # dictionary will be populated when segments are calculated self.segmentCounter = 0 # Rearranging: increments by 1 each time calculateAllMoves uploaded a new segment self.rr_config = r'Z:\Tweezer\Code\Python 3.5\PyDex\awg\rearr_config_files\rearr_config.txt' # default location of rearrange config file self.loadRearrParams( ) # Load rearrangment parameters from a config file self.lastRearrStep = 0 # Tells AWG what segment to go to at end of rearrangement self.OGfile = None self.set_functions()
def renewAWG(self, cmd="chans=[0]"): try: eval(cmd.split('=')[1]) except Exception as e: self.set_status('Invalid renew command: ' + cmd) logger.error('Could not renew AWG.\n' + str(e)) return 0 self.rr.awg.restart() self.rr.awg.newCard() self.rr.awg = None self.rr.awg = AWG(eval(cmd.split('=')[1])) # self.rr.awg.setNumSegments(8) # self.awg.setTrigger(0) # 0 software, 1 ext0 self.rr.awg.setSegDur(0.005) self.set_status('New instance of AWG created.')
class rearrange(): ### Rearrangement ### def __init__(self, AWG_channels=[0]): # Rearrangement variables self.awg = AWG(AWG_channels) # opens AWG card and initiates self.awg.setNumSegments(32) self.activate_rearr(False) self.movesDict = { } # dictionary will be populated when segments are calculated self.segmentCounter = 0 # Rearranging: increments by 1 each time calculateAllMoves uploaded a new segment self.rr_config = r'Z:\Tweezer\Code\Python 3.5\PyDex\awg\rearr_config_files\rearr_config.txt' # default location of rearrange config file self.loadRearrParams( ) # Load rearrangment parameters from a config file self.lastRearrStep = 0 # Tells AWG what segment to go to at end of rearrangement self.OGfile = None self.set_functions() def activate_rearr(self, toggle=False): """Turn rearranging ON or OFF. Calls set_functions whenever rearrToggle is changed so that there is no mixup. Args: - toggle: True or False. """ self.rearrToggle = toggle self.set_functions() def calculateAllMoves(self): """Given the initial and target frequencies, calculate all the possible moves from start array to target array There are 2 modes: if self.rearrMode = - use_exact: segments are for a fixed number (e.g. 5) initial sites sweeping to a fixed number of target sites (e.g. 2). If not enough atoms, do nothing, if too many atoms, throw away extra - use_all : segements are for a fixed number of of initial sites. The number of target sites depends on how many atoms were loaded. Target array fills up as many sites as there are atoms. If self.rParam["power_ramp"] is True, then after rearrangment, traps will ramp up to new freq_amp. """ t0 = time.time() # reinitialise values self.segmentCounter = 0 # RESET the segment counter when recalculating segments self.movesDict = {} self.loadRearrParams() self.lastRearrStep = 0 req_n_segs = self.rParam[ 'headroom_segs'] # Add 10 to required num of rearr segs for appending auxilliary moves afterwards #self.awg.setNumSegments(req_n_segs) start_key = self.fstring( self.initial_freqs) # Static array at initial trap freqs self.createRearrSegment(start_key + 'si', seg=0) # rearrMode = use_exact: rearrangement only occurs if AT LEAST the target number of atoms is loaded if self.rearrMode == 'use_exact': end_key = self.fstring( self.target_freqs) # Static array at target trap freqs if self.rParam['power_ramp'] == False: self.createRearrSegment(end_key + 'st', seg=2) self.segmentCounter = 3 if self.rParam['power_ramp'] == True: self.createRearrSegment( end_key + 'r', seg=2) # ramp target sites up to a freq_amp value. self.createRearrSegment(end_key + 'st', seg=3) self.segmentCounter = 4 if len(self.initial_freqs) < len(self.target_freqs): print( 'WARNING: more target frequencies than initial frequencies! \n ' 'Moves not calculated.') else: # proceed if fewer target traps than initial traps for m in range( len(self.target_freqs) ): # loop over m means we can deal with cases nLoaded < nTarget for x in combinations(start_key, len(self.target_freqs) - m): nloaded = "".join(x) self.createRearrSegment( nloaded + 'm' + ''.join( self.fstring( self.target_freqs[:len(nloaded)])), seg=1 ) # dont supply seg arg here so that data is not set #self.r_setStep(0,0,1,0,1) # rearrMode = use_all: ANY atom which is loaded will be rearranged to make as large a complete array as possible. elif self.rearrMode == 'use_all': # Generate each segment for j in range(len(start_key)): nloaded = len(start_key) - j end_key = self.fstring( [1] * nloaded ) # Creat segment: static array at target trap freqs self.createRearrSegment(end_key + 'st', seg=2) self.segmentCounter = 3 for x in combinations( start_key, nloaded ): # for each of the possible number of traps being loaded self.createRearrSegment( ''.join(x) + 'm' + ''.join(self.fstring([1] * nloaded)), seg=1) self.setBaseRearrangeSteps( ) # Once all moves calculated, set the base segments which are constant during rearrangement t1 = time.time() print('All move data calculated in ' + str(round(t1 - t0, 3)) + ' seconds.') def createRearrSegment(self, key, seg=None): """ Pass a key to this function which will: 1. Parse the key to determine if static or moving or ramping 2. Using default inputs from rParams dictionary, generate data 3. Assign the data to movesDict with the given key. Args: key - of the form: - 0123si (static, use initial array freqs) - 0123st (static, use target array freqs) - 0134m012 (moving, from initial array (sites 0134) -> target array (sites01) - 012r (power ramping, use target array freqs) """ if seg == None: # specify the exact segment in the card, else it will default to 1 (the rearranging moving seg is 1) seg = 1 # STATIC TRAP if 's' in key: fa = self.rearr_freq_amp duration = self.rParam['static_duration_[ms]'] if 'si' in key: # Initial array of static traps f1 = self.flist(key.partition('s')[0], self.initial_freqs) elif 'st' in key: # Target array of static traps if self.rearrMode == 'use_exact': f1 = self.flist(key.partition('s')[0], self.target_freqs) elif self.rearrMode == 'use_all': f1 = self.flist(key.partition('s')[0], self.initial_freqs) if self.rParam['power_ramp'] == True: fa = self.rParam['final_freq_amp'] if self.rParam['phase_adjust'] == True and len(f1) > 1: phase = list( phase_minimise(freqs=f1, dur=duration, sampleRate=self.awg.sample_rate.value / 1e6, freqAmps=[fa] * len(f1))) else: phase = [0] * len(f1) data = self.awg.dataGen( seg, self.rParam['channel'], 'static', # action self.rParam['static_duration_[ms]'], f1, 1, 9, # pointless legacy arguments self.rParam['tot_amp_[mV]'], [fa] * len(f1), # tone freq. amps phase, # tone phases self.rParam['freq_adjust'], self.rParam['amp_adjust']) # MOVING TRAP elif 'm' in key: # Move from initial array to target array of static traps f1 = self.flist(key.partition('m')[0], self.initial_freqs) if self.rearrMode == 'use_exact': f2 = self.flist(key.partition('m')[2], self.target_freqs) fa = self.rearr_freq_amp elif self.rearrMode == 'use_all': f2 = self.flist(key.partition('m')[2], self.initial_freqs) data = self.awg.dataGen( seg, self.rParam['channel'], 'moving', self.rParam['moving_duration_[ms]'], f1, f2, self.rParam['hybridicity'], self.rParam['tot_amp_[mV]'], [self.rearr_freq_amp] * len( f1 ), # start freq amps divide by n initial traps for consistent trap depth [self.rearr_freq_amp] * len(f1), # end freq amps [0] * len(f1), # freq phases self.rParam['freq_adjust'], self.rParam['amp_adjust']) duration = self.rParam['moving_duration_[ms]'] # RAMPING TRAP elif 'r' in key: # Ramp target array frequency amplitudes up to make use of freed-up RF power. f2 = self.flist(key.partition('r')[0], self.target_freqs) if self.rParam[ 'final_freq_amp'] == 'default': # If you say final freq amp is default it will divide through by n target sites ffa = 1 / len( f2) # else it will go to the value you have specified. else: ffa = self.rParam['final_freq_amp'] data = self.awg.dataGen( seg, self.rParam['channel'], 'ramp', self.rParam['ramp_duration_[ms]'], f2, 1, 9, # pointless legacy arguments self.rParam['tot_amp_[mV]'], [self.rearr_freq_amp] * len(f2), # start freq amps [ffa] * len(f2), # end freq amps [0] * len(f2), # freq phases self.rParam['freq_adjust'], self.rParam['amp_adjust']) duration = self.rParam['ramp_duration_[ms]'] self.movesDict[key] = [ data ] # List of data saves to movesDict, can be inserted to setSegment during rearrangement. if len(self.awg.channel_enable) == 2: # assume active channels are either 0 or 1 chan2 = 1 - self.rParam['channel'] f3 = self.rParam['alt_freqs'] if self.rParam['phase_adjust'] == True and len(f3) > 1: phase = list( phase_minimise(freqs=f3, dur=duration, sampleRate=self.awg.sample_rate.value / 1e6, freqAmps=[1] * len(f3))) else: phase = [0] * len(f3) # has to be moving so that the duration of data is right (static does loops) data2 = self.awg.dataGen( seg, chan2, 'moving', duration, f3, f3, 1, # frequencies self.rParam['alt_amp_[mV]'], [1] * len(f3), [1] * len(f3), # amps phase, #phase self.rParam['freq_adjust'], self.rParam['amp_adjust']) self.movesDict[key].insert(chan2, data2) if seg is not None or 1: # If you have specified the segment argument, it will set segment (used ininitial setup of rearr) self.awg.setSegment( seg, *self.movesDict[key] ) # because of garbage awgHandler code, need to call setSegment immediately after datagen def r_setStep(self, *args): """Calls the AWG set step function and also updates the filedata dictionary. Args same as setStep. # NOTE this function might actually be unecessary... (regular setStep might already update filedata dictionary) """ self.awg.setStep(*args) #keys = ['step_value','segment_value','num_of_loops','next_step','condition'] # order arguments correctly for i in range(len(self.awg.stepOrder)): self.awg.filedata['steps']['step_' + str(args[0])][ self.awg.stepOrder[i]] = args[i] def setBaseRearrangeSteps(self): """ Set the steps to follow during the rearrangement (only segment 1 will be changed during routine) """ # Setting the steps: (probably a cleaner way to do this) self.r_setStep(0, 0, 1, 1, 1) # Static traps until TTL received self.r_setStep( 1, 1, 1, 2, 2 ) # Moving traps for fixed duration, automatically moves to next step if self.rParam['power_ramp'] == False: self.r_setStep( 2, 2, 1, self.lastRearrStep, 1) # Static traps on at target site until triggered. elif self.rParam['power_ramp'] == True: self.r_setStep(2, 2, 1, 3, 2) self.r_setStep( 3, 3, 1, self.lastRearrStep, 1) # Static traps on at target site until triggered. def setRearrSeg(self, occupancyStr): """Calculate the rearrangement step required. Args: - occupancyStr = string of 0's & 1's e.g. '0101010' Basically then converts this to a key, which is used to look in movesDict for the correct data, which is then sent to card via awg.setSegment. """ keyStr = self.convertBinaryOccupancy(occupancyStr) if len(keyStr) < len( self.target_freqs) and self.rearrMode == 'use_exact': moveKey = keyStr + 'm' + ''.join(self.fstring(keyStr)) self.awg.setSegment(1, *self.movesDict[moveKey], verbosity=False) else: # WARNINGS to notify you there's a user error in setting # ROIs. if len(occupancyStr) > len(self.initial_freqs): print('WARNING: There are ' + str(np.abs(len(occupancyStr) - len(self.initial_freqs))) + ' fewer traps than PyDex ROIs') print(occupancyStr, self.initial_freqs) if len(occupancyStr) < len(self.initial_freqs): print('WARNING: There are ' + str(np.abs(len(occupancyStr) - len(self.initial_freqs))) + ' more traps than PyDex ROIs') if self.rearrMode == 'use_exact': moveKey = keyStr[-len(self.target_freqs):] + 'm' + ''.join( self.fstring(self.target_freqs)) self.awg.setSegment( 1, *self.movesDict[moveKey], verbosity=False ) # segment 1 is always the move segment (0 static, 1 move, 2 static //OR// 2 ramp, 3 static) elif self.rearrMode == 'use_all': moveKey = keyStr + 'm' + ''.join( self.fstring([1] * len(keyStr))) self.awg.setSegment( 1, *self.movesDict[moveKey], verbosity=False ) # segment 1 is always the move segment (0 static, 1 move, 2 static //OR// 2 ramp, 3 static) endKey = self.fstring(['1'] * len(keyStr)) + 'st' self.awg.setSegment( 2, *self.movesDict[moveKey], verbosity=False ) # segment 1 is always the move segment (0 static, 1 move, 2 static //OR// 2 ramp, 3 static) def loadRearrParams(self): """Load rearrangement parameters from a config file, i.e. params like Amp adjust, phases, duration etc. For the moment just set the manually""" # self.rParam={"amp_adjust":True, # "freq_adjust":False, # "tot_amp_[mV]":280, # "channel":0, # "static_duration_[ms]":1, "moving_duration_[ms]":1, "ramp_duration_[ms]":5, # "hybridicity":0, # "initial_freqs":[190.,177.5,165.,152.5,140.], # "target_freqs":[190.], # "headroom_segs":10, # "rearrMode":"use_all", # "rearr_freq_amps":0.13, # If default, rearr freq amps are 1/(n traps). else they are float # "power_ramp":True, # "final_freq_amp": 0.5, # "phase_adjust" : False, # } with open(self.rr_config) as json_file: self.rParam = json.load(json_file) self.rearrMode = self.rParam["rearrMode"] self.initial_freqs = self.rParam['initial_freqs'] self.target_freqs = self.rParam['target_freqs'] self.setRearrFreqAmps( self.rParam['rearr_freq_amps'] ) # Initialises frequency amplitudes during rearrangment to default 1/len(initial_freqs) try: self.awg.setSegDur(self.rParam['static_duration_[ms]']) except AttributeError: print("Loading rearr params but couldn't set static trap duration") # self.saveRearrParams() def printRearrInfo(self): """Print out the current status of the card, after changes have been applied by rearrangement functions.""" if self.rearrToggle == True: print('Rearranging is ON') print('Rearrange mode is: ' + self.rearrMode) elif self.rearrToggle == False: print('Rearranging is OFF') print(' - Config file used is: ' + self.rr_config) print(' - Current active channels =' + str(self.awg.channel_enable)) print(' - Card is partitioned into ' + str(self.awg.num_segment) + ' segments') print(' - Sample rate is = ' + str(self.awg.sample_rate.value)) print( ' - Max duration / segment = ', 4e9 / (2 * self.awg.num_segment * self.awg.sample_rate.value * len(self.awg.channel_enable)) * 1e3, ' ms') print(' - Initial frequencies = ' + str(self.initial_freqs)) print(' - Target frequencies = ' + str(self.target_freqs)) #print(' - Segment keys = '+str(self.movesDict)) print(' - Rearranging freq_amps are = ', self.rearr_freq_amp) print('') def setRearrFreqAmps(self, value='default'): """Set the frequency amplitudes during rearrangment either to default or to some fixed amplitude. When rearr is initialised, default freq amps are 1/(# initial traps) From python command terminal can set value to something fixed, applied globally across all rearr freq amps. e.g. set freq_amp = 0.2 and in all steps it will be 2. """ if value == 'default': self.rearr_freq_amp = round(1 / len(self.initial_freqs), 3) else: self.rearr_freq_amp = float(value) def rearr_load( self, file_dir='Z:\Tweezer\Experimental\AOD\m4i.6622 - python codes\Sequence Replay tests\metadata_bin\\20200819\\20200819_165335.txt' ): """ A method that receives as a single input a metadata file as generated by the self.save() method. It assumes no user input other than the full path to the file, so no checks are performed. Potential errors will be flagged as the dataGen and setSegment methods Rearrangment: this is a modified version of the load function copied for awgHandler. If rearrangment is active, loaded files segments will be appended to rearrangmeent segments & re-indexed Steps will be reindexed to start after rearrangement is complete. """ if self.OGfile is None: self.OGfile = file_dir # save the file directory when we load so that we can copy the untampered file with open(file_dir) as json_file: filedata = json.load( json_file ) # rearr: this and following used to be self.filedata, but that i think was wrong. lsegments = filedata['segments'] # segments to be loaded lsteps = filedata['steps'] # steps to be loaded lprop = filedata['properties'][ 'card_settings'] # card properties to be loaded lchannels = eval(lprop["active_channels"]) lchannels.sort() # Ensuring that channels are read in ascending order. segNumber = len(lsegments) # number of segments to be loaded stepNumber = len(lsteps) # number of steps to be loaded for i in range(segNumber): """ For each segment stored, go through all available channels and generate the data. Then, send the buffer to the card. """ tempData = [] for j in lchannels: # Finds what action_val was used for this segment and channel actionUsed = lsegments['segment_' + str(i)]['channel_' + str(j)]['action_val'] # Load the relevant parameters in the given order arguments = [ lsegments['segment_' + str(i)]['channel_' + str(j)][x] for x in AWG.loadOrder[actionUsed] ] # Generate the data and append them to the tempData variable. if self.rearrToggle == True: # if rearranging is ON, then add segmentCounter to segment, arguments[0], to arguments[0] = arguments[0] + self.segmentCounter tempData.append(self.awg.dataGen(*arguments)) # If rearranging on, then index loaded segments starting from index of last rearr segment to avoid overwriting. self.awg.setSegment(i + self.segmentCounter, *tempData) if self.rParam[ 'power_ramp'] == False: # this if statement is duplicated in setBaseREarrangeSteps() self.lastRearrStep = 3 else: self.lastRearrStep = 4 self.setBaseRearrangeSteps( ) # Call this again to reset the base card segments, (to update lastRearrStep) for i in range(stepNumber): # If rearrToggle is true, then here we want last rearr step to move onto 1st loaded step. stepArguments = [ lsteps['step_' + str(i)][x] for x in AWG.stepOrder ] stepArguments[ 0] += self.lastRearrStep # reindex step number starting from last rearrange step. stepArguments[ 1] += self.lastRearrStep # reindex segments starting from last segment if stepArguments[ 3] != 0: # reindex NEXT step number starting from last rearrange step stepArguments[3] += self.lastRearrStep # unless next step is 0 # stepArguments[4]=2 # set all trigs to 2 --- don't change this! if i == stepNumber - 1: stepArguments[4] = 1 # last trigger should be 1 #print(stepArguments) self.awg.setStep(*stepArguments) #self.calculateSteps('1'*len(self.initial_freqs)) # after load, run calculateSteps to set triggers correctly. def printMovesDict(self): for key in self.movesDict: #if 'ru' in key: print(key) def rearr_loadSeg(self, cmd): """If rearrangement is active, and we're multirunning, we need to reindex the multirun set_data commands starting from segment counter so that we change the right steps. awgHandler.loadSeg(listChanges) listChanges expects a list of lists in the following format: [[channel,segment,key_word1,new_value1,index],[channel,segment,key_word2,new_value2,index], ...] loop though and add self.segmentCounter to the segment in each command. """ #set_data=[[0,1,"freqs_input_[MHz]",160.0,0]] for i in range(len(cmd)): cmd[i][1] += self.segmentCounter self.awg.loadSeg(cmd) def saveRearrParams( self, savedir=r'Z:\Tweezer\Code\Python 3.5\PyDex\awg\rearr_config_files' ): """Save the rearrangement parameters used to a metadata file. """ with open(savedir + r'\rearr_config.txt', 'w') as fp: json.dump(self.rParam, fp, indent=1, separators=(',', ':')) def rearr_saveData(self, path): """If rearranging is ON, replace awgHandler.save method with THIS method. - We no longer save the curent filedata, instead a COPY of the original file loaded in. """ #print('save path = ' + path) self.saveRearrParams( path.rpartition('\\')[0]) # saves rearr_config.txt to measure file if self.OGfile is not None: # avoid error if you haven't loaded in a file after rearranging. self.copyOriginal( path ) # saves AWGparams_base (the base AWG file without rearr segs) def copyOriginal(self, save_path): """This function serves to COPY the loaded in file, which gets saved to the relevant Measure folder. It copies the unmodified AWGparam file and saves it (to avoid saving all the rearrangement steps too)""" shutil.copy(self.OGfile, save_path) def fstring(self, freqs): """Convert a list [150, 160, 170]~MHz to '012' """ return ("".join(str(i) for i in range(len(freqs)))) def flist(self, fstring, freq_list): """Given a string of e.g. '0123' and an array (initial/target), convert this to a list of freqs Args: fstring - string of integer numbers from 0 to 9 in ascending order. freq_list - array of freqs (either initial or target) which get sliced depending on fstring supplied e.g. if fstring = 0134 and freq_list = [190.,180.,170.,160.,150.], will return [190.,180.,160.,150.] """ return [freq_list[int(k)] for k in list(fstring)] # returns list of frequencies def convertBinaryOccupancy(self, occupancyStr='11010'): """Convert the string of e.g 010101 received from pyDex image analysis to a string of occupied sites """ occupied = '' j = 0 for _ in range( len(occupancyStr) ): # unless they're all occupied, we won't need every iteration try: i = occupancyStr.index('1', j) occupied += str(i) j = i + 1 except ValueError: break if occupied == '': # deal with the case of zero atoms being loaded occupied = '0' return occupied def set_functions(self): """ WARNING: ISSUES WITH THIS WAY OF DOING IT. ALTHOUGH SEEMS LIKE IT SHOULD BE FIND, WE HAVE SEEN THAT IT CAUSES ISSUES FOR SOME REASON AND DOESN'T WORK Depending if rearrangement is on or off, redefine certain functions to behave differently. By setting the function as soon as rearrangement is ON/OFF, avoids lots of IF statements in other functions which is cleaner and makes faster. """ if self.rearrToggle == False: # If rearrangement is OFF pass # self.load = self.awg.load # self.loadSeg = self.awg.loadSeg # self.save = self.awg.saveData # print('using regular functions') elif self.rearrToggle == True: # If rearrangement is ON pass # self.load = self.rearr_load #self.loadSeg = self.rearr_loadSeg # self.save = self.rearr_saveData #print('using rearr version of functions') def phase_adjust(self, N): """Analytic expression (Schroeder paper) to adjust phases to give a lower crest factor - Args = N : number of traps Returns array of phases in degrees """ phi = np.zeros(N) for i in range(N): phi[i] = -np.pi / 2 - np.pi * (i + 1)**2 / N phi = phi / np.pi * 180 return (phi)
class Optimiser(): """Take measurements from the DAQ of the output optical power at different frequencies and use them to flatten the diffraction efficiency curve of the AWG. Communicate with the DAQ by TCP. Take a measurement of the setpoint between every trial since the setpoint will probably vary over time. Arguments: f0 : bottom of frequency range, MHz f1 : top of frequency range, MHz nfreqs: number of frequencies to test in the range fset : setpoint frequency, match the diffraction efficiency at this point, MHz pwr : output power desired as a fraction of the setpoint tol : tolerance to match to setpoint sleep : time to sleep betewen setting AWG freq and taking measurement, seconds """ def __init__(self, f0=135, f1=185, nfreqs=50, fset=166, pwr=1, tol=1e-3, sleep=0.5): self.status = 'checking' # parameters self.f0 = f0 # lower bound self.f1 = f1 # upper bound self.nfreqs = nfreqs # number of frequencies self.fset = fset # setpoint self.pwr = pwr # amplitude self.tol = tol # tolerance self.sleep = sleep # sleep duration # self.fs = np.linspace(f0, f1, nfreqs) # frequencies to test self.vs = np.ones(nfreqs) * 200 # amplitude mV at those freqs self.v = 200 # current amplitude being tried self.i = 0 # current index being set self.setpoint = 1 # DAQ measurement to match to self.n = 0 # counter for number of measurements # setup self.s = PyServer(host='', port=8622) # server for DAQ self.s.textin.connect(self.respond) self.s.start() self.dxs = PyServer(host='', port=8620) # server for DExTer # self.dxs.textin.connect(self.respond) self.dxs.start() self.t = AWG([0, 1]) self.t.setNumSegments(128) self.t.setTrigger(0) # software trigger self.t.setSegDur(0.002) # segment, action, duration, freqs, numTraps, separation, freqAdjust, ampAdjust # self.t.setSegment(0, 1, 0.02, [fset], 1, 9, amp, [1], [0], False, False) # single trap # # step, segment, numLoops, nextStep, triggerCondition # self.t.setStep(0,0,1,0,1) # infinite loop # self.t.start() # self.t.setSegment(0, self.t.dataGen(0,0,'static',1,[fset],1,9, amp,[1],[0],False,False)) # self.t.setStep(0,0,1,0,1) self.t.load( r'Z:\Tweezer\Code\Python 3.5\PyDex\awg\AWG template sequences\2channel_swap_static.txt' ) # self.t.start() def respond(self, msg=''): """TCP message can contain the measurement from the DAQ""" try: val = float(msg) if self.status == 'checking': self.setpoint = val self.status = 'comparing' f, v = self.fs[self.i], self.v elif self.status == 'comparing': self.status = 'checking' self.modify(val) f, v = self.fset, 1 elif self.status == 'finished': return 0 print('f:%.4g, v:%.4g' % (f, v), val, self.setpoint) self.t.setSegment( 0, self.t.dataGen(0, 0, 'static', 1, [f], 1, 9, v, [self.pwr], [0], False, False)) self.measure() self.n += 1 except Exception as e: pass # print(msg, '\n', str(e)) # the command was probably 'start' def modify(self, newval): """Compare newval to setpoint. If within tolerance, move on to the next frequency. If not, try a new amplitude""" v = (newval - self.setpoint) / self.setpoint if abs(v) < self.tol: # store value self.vs[self.i] = self.v self.i += 1 if self.i == self.nfreqs: self.status = 'finished' self.plot() else: # try new amplitude print(self.fs[self.i], v, -0.4 * v) self.v -= 0.4 * v if self.v < 0 or self.v > 1: self.v = 0.8 def measure(self): """Request a measurement from the DAQ""" time.sleep(self.sleep) self.dxs.add_message(TCPENUM['Run sequence'], 'run the sequence\n' + '0' * 1600) time.sleep(self.sleep) # self.s.add_message(self.n, 'measure') # tells DAQ to add the measurement to the next message # self.s.add_message(self.n, 'readout') # reads the measurement def restart(self): self.i = 0 self.status = 'checking' self.measure() def check(self, i=0): try: self.status = 'finished' self.t.setSegment( 0, self.t.dataGen(0, 0, 'static', 1, [self.fset], 1, 9, 220, [self.pwr], [0], False, False)) self.measure() time.sleep(self.sleep) self.t.setSegment( 0, self.t.dataGen(0, 0, 'static', 1, [self.fs[i]], 1, 9, self.vs[i], [self.pwr], [0], False, False)) self.measure() except IndexError as e: print(e) def plot(self): plt.figure() plt.plot(self.fs, self.vs) plt.xlabel('Frequency (MHz)') plt.ylabel('RF amplitude to flatten (mV)') plt.show()
class normaliser: """Class to iteratively normalise an array of trap intensities using a CCD. awgparam: str path to file to load awg segment data from image_dir: str directory to save images in cam_roi: list roi in pixel coordinates: [xmin,ymin,xmax,ymax] freq_amp_max: [CH0,CH1] cap the fractional optical power to avoid saturation """ def __init__( self, awgparam='Z:/Tweezer/Experimental/AOD/2D AOD/Array normalisation/6x1array.txt', image_dir='Z:/Tweezer/Experimental/AOD/2D AOD/Array normalisation/Normalised', cam_roi=None, fit_roi_size=50, freq_amp_max=[1, 1]): ### set up AWG self.awg = AWG([0, 1], sample_rate=int(1024e6)) fdir = 'Z:/Tweezer/Experimental/Setup and characterisation/Settings and calibrations/tweezer calibrations/AWG calibrations' self.awg.setCalibration(0, fdir + '/814_H_calFile_17.02.2022.txt', freqs=np.linspace(85, 110, 100), powers=np.linspace(0, 1, 200)) self.awg.setCalibration(1, fdir + '/814_V_calFile_17.02.2022.txt', freqs=np.linspace(85, 115, 100), powers=np.linspace(0, 1, 200)) self.awg.load(awgparam) self.awg.param_file = awgparam self.awg.setTrigger(0) # 0 software, 1 ext0 seg = self.awg.filedata["segments"]["segment_0"] self.f0 = eval(seg["channel_0"]["freqs_input_[MHz]"]) self.f1 = eval(seg["channel_1"]["freqs_input_[MHz]"]) self.ncols = len(self.f0) self.nrows = len(self.f1) self.a0 = np.array(eval(seg["channel_0"]["freq_amp"]), dtype=float) self.a1 = np.array(eval(seg["channel_1"]["freq_amp"]), dtype=float) self.ulim = freq_amp_max self.amp = int(seg["channel_0"]["tot_amp_[mV]"]) #### set up camera self.cam = Camera(exposure=7, gain=1, roi=cam_roi) self.awg.start() time.sleep(0.5) self.cam.auto_gain_exposure() self.cam.update_exposure(self.cam.exposure * 0.6) # saturating is bad #### image handler saves images self.imhand = ImageHandler(image_dir=image_dir, measure_params={ 'rows': self.nrows, 'columns': self.ncols, 'AWGparam_file': awgparam }) self.imhand.create_dirs() #### fitr extracts trap positions and intensities from an image self.fitr = imageArray(dims=(self.ncols, self.nrows), roi_size=fit_roi_size, fitmode='sum') self.fitr.setRef(self.get_image(-1, -1, auto_exposure=False)) def process(self, arr): self.fitr._imvals = arr self.fitr.fitImage() return self.fitr.getScaleFactors() def get_image(self, repetition, iteration=0, sleep=0.3, auto_exposure=False): """Take and save an image and a background image""" self.awg.start() # show array time.sleep(sleep) if auto_exposure: self.cam.auto_gain_exposure( ) # adjust exposure to avoid saturation image = self.cam.take_image() self.awg.stop() # awg off gives background image time.sleep(sleep) bgnd = self.cam.take_image() image.add_background(bgnd) array = image.get_bgnd_corrected_array() image.add_property('intensity_correction_iteration', iteration) image.add_property('rep', repetition) self.imhand.save(image) return array def get_images(self, reps=100, iteration=0, sleep=0.3): """Take images in a loop""" ave_im = np.zeros(self.cam.acquire().shape) self.awg.stop() # awg might crash if it's started twice time.sleep(sleep) for i in range(reps): ave_im += self.get_image(i, iteration, sleep) return ave_im / reps def normalise(self, num_ims=24, num_ave=3, precision=0.005, max_iter=7): """Take images and then produce corrections factors until desired precision or max iterations are reached. num_ims: int number of images to take for each iteration of normalisation num_ave: int number of sets to split the images into to take averages max_iter: int stop the normalisation after this many iterations precision: float stop the normalisation when stdv/mean is this value""" base_dir = self.imhand.image_dir history = [[1, self.a0, self.a1]] for i in range(max_iter): # take images and calculate correction factors self.imhand.image_dir = os.path.join(base_dir, 'Iteration' + str(i)) self.imhand.create_dirs() c0 = np.zeros(self.ncols) c1 = np.zeros(self.nrows) for j in range(num_ave): ave_im = self.get_images(round(num_ims / num_ave), iteration=i) c = self.process(ave_im) c0 += c[1] c1 += c[0] # take new scale factors but don't let them go above the original self.a0 = limit(self.a0 * c0 / num_ave, ulim=self.ulim[0]) self.a1 = limit(self.a1 * c1 / num_ave, ulim=self.ulim[1]) self.awg.arrayGen(self.ncols, self.nrows, 0, freqs=[self.f0, self.f1], amps=[self.a0, self.a1], AmV=self.amp, duration=1, freqAdjust=True, ampAdjust=True, phaseAdjust=True) c = self.fitr.df['I0'] if any([I0 < 0 for I0 in c]): print('\nCamera saturated, aborting optimisation...') return history pack = [c.std() / c.mean(), self.a0, self.a1] history.append(pack) print( i, *[ x + str(y) for x, y in zip( ['\n Relative Error: ', '\nCH1: ', '\nCH2: '], pack) ]) if pack[0] < precision: break # find min relative error and set values h = history[np.argmin([vals[0] for vals in history])] self.awg.arrayGen(self.ncols, self.nrows, 0, freqs=[self.f0, self.f1], amps=[h[1], h[2]], AmV=self.amp, duration=1, freqAdjust=True, ampAdjust=True, phaseAdjust=True) self.awg.filedata = eval(str(self.awg.filedata)) self.awg.saveData( os.path.splitext(self.awg.param_file)[0] + '_normalised.txt') return history