def test_skip_in_loop(task): """Skip testing the field if the task is embedded in a LoopTask. """ task.feval = '' val, res, msg = validators.SkipLoop().check(task, 'feval') assert val is None assert res assert not msg task.feval = '2*{Loop_val}' root = task.root task.parent.task = None root.add_child_task(0, task) val, res, msg = validators.SkipLoop().check(task, 'feval') assert val == 2 assert res assert not msg
class SetPulseModulationTask(InterfaceableTaskMixin, InstrumentTask): """Switch on/off the pulse modulation of the source. """ # Desired state of the output, runtime value can be 0 or 1. switch = Unicode('Off').tag(pref=True, feval=validators.SkipLoop()) database_entries = set_default({'pm_state': 0}) def check(self, *args, **kwargs): """Validate the value of the switch. """ test, traceback = super(SetPulseModulationTask, self).check(*args, **kwargs) if test and self.switch: try: switch = self.format_and_eval_string(self.switch) except Exception: return False, traceback if switch not in ('Off', 'On', 0, 1): test = False traceback[self.get_error_path() + '-switch'] =\ '{} is not an acceptable value.'.format(self.switch) return test, traceback def i_perform(self, switch=None): """Default interface behavior. """ if switch is None: switch = self.format_and_eval_string(self.switch) if switch == 'On' or switch == 1: self.driver.pm_state = 'On' self.write_in_database('pm_state', 1) else: self.driver.pm_state = 'Off' self.write_in_database('pm_state', 0)
class ApplyMagFieldAndDropTask(InstrumentTask): """Use a supraconducting magnet to apply a magnetic field. Parallel task. """ # Target magnetic field (dynamically evaluated) field = Unicode().tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) # Rate at which to sweep the field. rate = Float(0.01).tag(pref=True) parallel = set_default({'activated': True, 'pool': 'instr'}) database_entries = set_default({'field': 0.01}) def check_for_interruption(self): """Check if the user required an interruption. """ return self.root.should_stop.is_set() def perform(self, target_value=None): """Apply the specified magnetic field. """ # make ready if (self.driver.owner != self.name or not self.driver.check_connection()): self.driver.owner = self.name driver = self.driver if driver.heater_state == 'Off': raise ValueError(cleandoc(''' Switch heater must be on''')) if target_value is None: target_value = self.format_and_eval_string(self.field) driver.field_sweep_rate = self.rate driver.target_field = target_value driver.activity = 'To set point' self.write_in_database('field', target_value)
class SetDCFunctionTask(InterfaceableTaskMixin, InstrumentTask): """Set a DC source function to the specified value: VOLT or CURR """ #: Target value for the source (dynamically evaluated) switch = Str('VOLT').tag(pref=True, feval=validators.SkipLoop()) database_entries = set_default({'function': 'VOLT'}) def i_perform(self, switch=None): """Default interface. """ if switch is None: switch = self.format_and_eval_string(self.switch) if switch == 'VOLT': self.driver.function = 'VOLT' self.write_in_database('function', 'VOLT') if switch == 'CURR': self.driver.function = 'CURR' self.write_in_database('function', 'CURR')
class SetDCOutputTask(InterfaceableTaskMixin, InstrumentTask): """Set a DC source output to the specified value: ON or OFF """ #: Target value for the source output switch = Unicode('OFF').tag(pref=True, feval=validators.SkipLoop()) database_entries = set_default({'output': 'OFF'}) def i_perform(self, switch=None): """Default interface. """ if switch is None: switch = self.format_and_eval_string(self.switch) if switch == 'ON': self.driver.output = 'ON' self.write_in_database('output', 'ON') if switch == 'OFF': self.driver.output = 'OFF' self.write_in_database('output', 'OFF')
class RunAWGTask(InstrumentTask): """ Task to set AWG run mode """ #: Switch to choose the AWG run mode: on or off switch = Str('Off').tag(pref=True, feval=validators.SkipLoop()) database_entries = set_default({'output': 0}) def perform(self, switch=None): """Default interface behavior. """ if switch is None: switch = self.format_and_eval_string(self.switch) if switch == 'On' or switch == 1: self.driver.running = 1 self.write_in_database('output', 1) else: self.driver.running = 0 self.write_in_database('output', 0) log = logging.getLogger(__name__) msg = 'AWG running state OK' log.debug(msg)
""" import numbers from atom.api import (Unicode, Bool, set_default, Enum) from exopy.tasks.api import (InstrumentTask, InterfaceableTaskMixin, validators) CONVERSION_FACTORS = {'GHz': {'Hz': 1e9, 'kHz': 1e6, 'MHz': 1e3, 'GHz': 1}, 'MHz': {'Hz': 1e6, 'kHz': 1e3, 'MHz': 1, 'GHz': 1e-3}, 'kHz': {'Hz': 1e3, 'kHz': 1, 'MHz': 1e-3, 'GHz': 1e-6}, 'Hz': {'Hz': 1, 'kHz': 1e-3, 'MHz': 1e-6, 'GHz': 1e-9}} LOOP_REAL = validators.SkipLoop(types=numbers.Real) class SetRFFrequencyTask(InterfaceableTaskMixin, InstrumentTask): """Set the frequency of the signal delivered by a RF source. """ # Target frequency (dynamically evaluated) frequency = Unicode().tag(pref=True, feval=LOOP_REAL) # Unit of the frequency unit = Enum('GHz', 'MHz', 'kHz', 'Hz').tag(pref=True) # Whether to start the source if its output is off. auto_start = Bool(False).tag(pref=True)
class SetDCVoltageTask(InterfaceableTaskMixin, InstrumentTask): """Set a DC voltage to the specified value. The user can choose to limit the rate by choosing an appropriate back step (larger step allowed), and a waiting time between each step. """ #: Target value for the source (dynamically evaluated) target_value = Str().tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) #: Largest allowed step when changing the output of the instr. back_step = Float().tag(pref=True) #: Largest allowed voltage safe_max = Float(0.0).tag(pref=True) #: Largest allowed delta compared to current voltage. 0 = ignored safe_delta = Float(0.0).tag(pref=True) #: Time to wait between changes of the output of the instr. delay = Float(0.01).tag(pref=True) parallel = set_default({'activated': True, 'pool': 'instr'}) database_entries = set_default({'voltage': 0.01}) def i_perform(self, value=None): """Default interface. """ if self.driver.owner != self.name: self.driver.owner = self.name if hasattr(self.driver, 'function') and\ self.driver.function != 'VOLT': msg = ('Instrument assigned to task {} is not configured to ' 'output a voltage') raise ValueError(msg.format(self.name)) setter = lambda value: setattr(self.driver, 'voltage', value) current_value = getattr(self.driver, 'voltage') self.smooth_set(value, setter, current_value) def smooth_set(self, target_value, setter, current_value): """ Smoothly set the voltage. target_value : float Voltage to reach. setter : callable Function to set the voltage, should take as single argument the value. current_value: float Current voltage. """ if target_value is not None: value = target_value else: value = self.format_and_eval_string(self.target_value) if self.safe_delta and abs(current_value - value) > self.safe_delta: msg = ( 'Requested voltage {} is too far away from the current voltage {}!' ) raise ValueError(msg.format(value, current_value)) if self.safe_max and self.safe_max < abs(value): msg = 'Requested voltage {} exceeds safe max : {}' raise ValueError(msg.format(value, self.safe_max)) last_value = current_value if abs(last_value - value) < 1e-12: self.write_in_database('voltage', value) return elif self.back_step == 0: self.write_in_database('voltage', value) setter(value) return else: if (value - last_value) / self.back_step > 0: step = self.back_step else: step = -self.back_step if abs(value - last_value) > abs(step): while not self.root.should_stop.is_set(): # Avoid the accumulation of rounding errors last_value = round(last_value + step, 9) setter(last_value) if abs(value - last_value) > abs(step): time.sleep(self.delay) else: break if not self.root.should_stop.is_set(): setter(value) self.write_in_database('voltage', value) return self.write_in_database('voltage', last_value)
class TuneIQMixerTask(InstrumentTask): """ Task to tune an IQ mixer in SSB Implicit use of a SignalHound spectrum analyzer Tunes channels I and Q DC offset, relative delay and voltage to suppress LO leakage and unwanted sideband TODO: handle task with two instruments: AWG AND Spectrum analyzer TODO: implement realtime sweep for better SNR """ # Get user inputs channelI = Enum('Ch1', 'Ch2', 'Ch3', 'Ch4').tag(pref=True) channelQ = Enum('Ch1', 'Ch2', 'Ch3', 'Ch4').tag(pref=True) # LO frequency freq = Str('0.0').tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) # modulation frequency det = Str('0.0').tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) # LO power frequency power = Str('0.0').tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) # Desired sideband, e.g. if Lower, suppress freq and freq+det SB = Enum('Lower', 'Upper').tag(pref=True) my_sa = Value() # signal analyzer chI = Value() chQ = Value() freq_Hz = Value() det_Hz = Value() SB_sgn = Value() LO_pow = Value() def check(self, *args, **kwargs): ''' Default checks and check different AWG channels ''' test, traceback = super(TuneIQMixerTask, self).check(*args, **kwargs) if not test: return test, traceback if self.channelI == self.channelQ: test = False msg = 'I and Q channels need to be different !' traceback[self.get_error_path()] = msg return test, traceback def perform(self): """Default interface behavior. """ # open signal analyzer Rhode&Schwarz visa_address = 'TCPIP0::192.168.0.52::inst0::INSTR' connection_infos = {'resource_name': visa_address} self.my_sa = sa.RohdeAndSchwarzPSA(connection_infos)#, mode=SA_SWEEPING) # AWG channels awg = self.driver awg.run_mode = 'CONT' awg.output_mode = 'FIX' self.chI = awg.get_channel(int(self.channelI[-1])) self.chQ = awg.get_channel(int(self.channelQ[-1])) # convert user inputs into adequate units self.LO_pow = self.format_and_eval_string(self.power) self.freq_Hz = self.format_and_eval_string(self.freq)*1e9 self.det_Hz = self.format_and_eval_string(self.det)*1e6 #modulation freq self.SB_sgn = 1 if self.SB == 'Lower' else -1 # setting the modulation frequency for each channel self.chI.set_frequency(self.det_Hz) self.chQ.set_frequency(self.det_Hz) # Initialize AWG params # set parameters to minima or centers everywhere # initialisation self.chI_vpp(0.15) self.chQ_vpp(0.15) self.chI_offset(0) self.chQ_offset(0) self.chQ_delay(0) # perform optimization twice self.tune_ssb('lo') self.tune_ssb('sb') pos_lo, cost = self.tune_ssb('lo') pos_sb, cost = self.tune_ssb('sb') # get power for optimal parameters at sig, leakage and sideband # get_single_freq(self,freq,reflevel,rbw,vbw,avrg_num) sig = self.my_sa.get_single_freq(self.freq_Hz-self.SB_sgn*self.det_Hz, self.LO_pow,int(1e3),int(1e3),10) lo = self.my_sa.get_single_freq(self.freq_Hz,self.LO_pow,1e3,1e3,10) sb = self.my_sa.get_single_freq(self.freq_Hz+self.SB_sgn*self.det_Hz, self.LO_pow,int(1e3),int(1e3),10) # close signal analyzer self.my_sa._close() # log values log = logging.getLogger(__name__) msg1 = 'Tuned IQ mixer at LO = %s GHz, IF = %s MHz, \ Signal: %s dBm, LO: %s dBm, SB: %s dBm' % \ (1e-9*self.freq_Hz, 1e-6*self.det_Hz, sig, lo, sb) log.info(msg1) msg2 = 'chI offset: %s V, chQ offset: %s V, chQvpp: %s V, \ chQphase: %s °' % \ (pos_lo[0], pos_lo[1], pos_sb[0], pos_sb[1]) log.info(msg2) # optimization procedure def tune_ssb(self, mode): # suppress lo leakage params if mode == 'lo': param1 = self.chI_offset param2 = self.chQ_offset f = self.freq_Hz minvals = np.array([-1,-1]) maxvals = np.array([1,1]) precision = np.array([0.001,0.001]) pos0 = np.array([self.get_chI_offset(), self.get_chQ_offset()]) # suppress other sideband params elif mode == 'sb': param1 = self.chQ_vpp param2 = self.chQ_delay f = self.freq_Hz + self.SB_sgn*self.det_Hz minvals = np.array([0.05,0]) maxvals = np.array([0.15,360]) precision = np.array([0.01,0.1]) pos0 = np.array([self.get_chQ_vpp(), self.get_chQ_delay()]) else: msg = '''param has wrong value, should be lo or sb, received %s''' % mode raise ValueError(msg) # 4 directions in parameter search space sens = [np.array([1, 0]), np.array([0, 1]), np.array([-1, 0]), np.array([0, -1])] # initial cost (cost = power of sa at f) cost0 = self.cost(param1, param2, pos0[0], pos0[1], f) ### Qcircuits STARTS HERE ### dec = 0.1 s = 0 c = 0 counter = 0 poslist = [pos0] precision_reached = False # stop search when step_size < AWG resolution while dec >= 0.0001: step_sizes = dec*(maxvals-minvals) #check that we aren't lower than instrument precision if ((step_sizes[0] - precision[0]) < 0) and \ ((step_sizes[1] - precision[1]) < 0): precision_reached = True step_sizes = precision elif (step_sizes[0] - precision[0]) < 0: step_sizes[0] = precision[0] elif (step_sizes[1] - precision[1]) < 0: step_sizes[1] = precision[1] # break when max eval count has reach or # all 4 directions have been explored while c < 4 and counter < 1000: # probe cost at new pos: pos1 pos1 = pos0 + step_sizes*sens[s] # check that we aren't out of bounds if (not (minvals[0] <= pos1[0] <= maxvals[0])) and \ (not (minvals[1] <= pos1[1] <= maxvals[1])): boundaries = np.array([minvals, np.array([minvals[0],maxvals[1]]), np.array([minvals[1],maxvals[0]]), maxvals]) # find bounardy closest to current value pos1 = boundaries[np.argmin(list(map(abs, boundaries-pos1)))] elif not (minvals[0] <= pos1[0] <= maxvals[0]): boundaries = np.array([minvals[0],maxvals[0]]) # find bounardy closest to current value pos1[0] = boundaries[np.argmin(list(map(abs, boundaries-pos1[0])))] elif not (minvals[1] <= pos1[1] <= maxvals[1]): boundaries = np.array([minvals[1],maxvals[1]]) # find bounardy closest to current value pos1[1] = boundaries[np.argmin(list(map(abs, boundaries-pos1[1])))] # evaluate cost of new position cost1 = self.cost(param1, param2, pos1[0], pos1[1], f) counter += 1 # if lower cost, update pos if cost1 < cost0: cost0 = cost1 pos0 = pos1 c = 0 poslist.append(pos0) else: c += 1 s = np.mod(s+1, 4) c = 0 # decrease dec if all explored directions give higher cost dec /= 10 if precision_reached: break return pos0, cost0 # optimization cost function: get power in dBm at f from signal_analyzer def cost(self, param1, param2, val1, val2, f): param1(val1) param2(val2) return self.my_sa.get_single_freq(f,self.LO_pow,1e3,1e3,10) # define AWG getter and setter functions to pass into cost function def chI_offset(self, value): self.chI.set_DC_offset(value) def chQ_offset(self, value): self.chQ.set_DC_offset(value) def get_chI_offset(self): return self.chI.DC_offset() def get_chQ_offset(self): return self.chQ.DC_offset() def chI_vpp(self, value): self.chI.set_Vpp(value) def chQ_vpp(self, value): self.chQ.set_Vpp(value) def get_chI_vpp(self): return self.chI.Vpp() def get_chQ_vpp(self): return self.chQ.Vpp() def chQ_delay(self, value): self.chQ.set_phase(value) def get_chQ_delay(self): return self.chQ.phase()
class ApplyMagFieldTask(InstrumentTask): """Use a supraconducting magnet to apply a magnetic field. Parallel task. """ # Target magnetic field (dynamically evaluated) field = Unicode().tag(pref=True, feval=validators.SkipLoop(types=numbers.Real)) # Rate at which to sweep the field. rate = Float(0.01).tag(pref=True) # Whether to stop the switch heater after setting the field. auto_stop_heater = Bool(True).tag(pref=True) # Time to wait before bringing the field to zero after closing the switch # heater. post_switch_wait = Float(30.0).tag(pref=True) parallel = set_default({'activated': True, 'pool': 'instr'}) database_entries = set_default({'field': 0.01}) def check_for_interruption(self): """Check if the user required an interruption. """ return self.root.should_stop.is_set() def perform(self, target_value=None): """Apply the specified magnetic field. """ # make ready if (self.driver.owner != self.name or not self.driver.check_connection()): self.driver.owner = self.name if target_value is None: target_value = self.format_and_eval_string(self.field) driver = self.driver normal_end = True if (abs(driver.read_persistent_field() - target_value) > driver.output_fluctuations): job = driver.sweep_to_persistent_field() if job.wait_for_completion(self.check_for_interruption, timeout=60, refresh_time=1): driver.heater_state = 'On' sleep(self.post_switch_wait) else: return False # set the magnetic field job = driver.sweep_to_field(target_value, self.rate) normal_end = job.wait_for_completion(self.check_for_interruption, timeout=60, refresh_time=10) # Always close the switch heater when the ramp was interrupted. if not normal_end: job.cancel() driver.heater_state = 'Off' sleep(self.post_switch_wait) self.write_in_database('field', driver.read_persistent_field()) return False # turn off heater if self.auto_stop_heater: driver.heater_state = 'Off' sleep(self.post_switch_wait) job = driver.sweep_to_field(0) job.wait_for_completion(self.check_for_interruption, timeout=60, refresh_time=1) self.write_in_database('field', target_value)