class RF_Switch_Compression(Experiment): #Define Experiment Axis (Sweep Parameters) inputatten = FloatParameter(default=0,unit="dB") inputfreq = FloatParameter(defualt=2e9,unit="Hz") # Setup Output Connectors (Measurements) outputpow = OutputConnector(unit="dBm") #Instrument Resources atten = DigitalAttenuator('COM3') #Serial port must be checked with each CPU reboot source = BNC845('192.168.5.161') #Use BNC GUI to determine IP spec = AgilentE9010A('192.168.5.103') def init_instruments(self): print("Initializing Instruments") self.atten.ch1_attenuation = 30 self.source.output = False self.inputatten.assign_method(self.atten.ch1_attenuation) self.inputfreq.assign_method(self.source.frequency) async def run(self): self.spec.frequency_span = 0 self.spec.frequency_center = self.inputfreq await self.outputpow.push(self.spec.power)
class MeasureLockinVoltage(Procedure): set_field = FloatParameter(name="Set Field", unit="G") field = Quantity(name="Field", unit="G") voltage = Quantity(name="Magnitude", unit="V") bop = BOP2020M("GPIB0::1::INSTR") lock = SR830("GPIB0::9::INSTR") hp = HallProbe("calibration/HallProbe.cal", lock.set_ao1, lock.get_ai1) mag = Electromagnet('calibration/GMW.cal', hp.get_field, bop.set_current, bop.get_current) def init_instruments(self): self.tc_delay = self.lock.measure_delay() self.averages = 10 def lockin_measure(): time.sleep(self.tc_delay) vals = [] for i in range(self.averages): vals.append(self.lock.r) return np.mean(vals) self.set_field.assign_method(self.mag.set_field) self.field.assign_method(self.mag.get_field) self.voltage.assign_method(lockin_measure) for param in self._parameters: self._parameters[param].push() def run(self): """This is run for each step in a sweep.""" for param in self._parameters: self._parameters[param].push() for quant in self._quantities: self._quantities[quant].measure() logging.info("Field, Lockin Magnitude: {:f}, {:g}".format( self.field.value, self.voltage.value)) def shutdown_instruments(self): self.bop.current = 0.0
class nTronSwitchingExperimentFast(Experiment): # Parameters and outputs # channel_bias = FloatParameter(default=100e-6, unit="A") # On the 33220A pulse_duration = FloatParameter(default=5.0e-9, unit="s") pulse_voltage = FloatParameter(default=1, unit="V") channel_bias = FloatParameter(default=500e-6, unit="A") voltage = OutputConnector() # Constants (set with attribute access if you want to change these!) attempts = 1 << 6 # Reference resistances and attenuations matching_ref_res = 50 chan_bias_ref_res = 1e4 pspl_base_attenuation = 10 circuit_attenuation = 0 pulse_polarity = 1 # Min/Max NIDAQ AI Voltage min_daq_voltage = 0 max_daq_voltage = 10 # Measurement Sequence timing, clocks in Hz times in seconds ai_clock = 0.25e6 do_clock = 0.5e6 trig_interval = 0.2e-3 # The repetition rate for switching attempts bias_pulse_width = 40e-6 # This is how long the bias pulse is pspl_trig_time = 4e-6 # When we trigger the gate pulse integration_time = 20e-6 # How long to measure for ai_delay = 2e-6 # How long before the measurement begins # Instrument resources, NIDAQ called via PyDAQmx arb_cb = Agilent33220A("192.168.5.199") # Channel Bias pspl = Picosecond10070A("GPIB0::24::INSTR") # Gate Pulse atten = RFMDAttenuator( "calibration/RFSA2113SB_HPD_20160901.csv") # Gate Amplitude control lock = SR865( "USB0::0xB506::0x2000::002638::INSTR") # Gate Amplitude control def init_instruments(self): # Channel bias arb self.arb_cb.output = False self.arb_cb.load_resistance = (self.chan_bias_ref_res + self.matching_ref_res) self.arb_cb.function = 'Pulse' self.arb_cb.pulse_period = 0.99 * self.trig_interval # Slightly under the trig interval since we are driving with another instrument self.arb_cb.pulse_width = self.bias_pulse_width self.arb_cb.pulse_edge = 100e-9 # Going through the DC port of a bias-tee, no need for fast edges self.arb_cb.low_voltage = 0.0 self.arb_cb.high_voltage = self.channel_bias.value * ( self.chan_bias_ref_res + self.matching_ref_res) self.arb_cb.burst_state = True self.arb_cb.burst_cycles = 1 self.arb_cb.trigger_source = "External" self.arb_cb.burst_mode = "Triggered" self.arb_cb.output = True self.arb_cb.polarity = 1 # Setup the NIDAQ DAQmxResetDevice("Dev1") measure_points = int(self.integration_time * self.ai_clock) self.nidaq = CallbackTask( asyncio.get_event_loop(), measure_points, self.attempts, self.voltage, # chunk_size=measure_points*(self.attempts>>2), min_voltage=self.min_daq_voltage, max_voltage=self.max_daq_voltage, ai_clock=self.ai_clock) self.nidaq.StartTask() # Setup DO Triggers, P1:0 triggers AWG, P1:1 triggers PSPL and DAQ analog input self.digital_output = Task() data_p0 = np.zeros(int(self.do_clock * self.trig_interval), dtype=np.uint8) data_p0[0] = 1 data_p1 = np.zeros(int(self.do_clock * self.trig_interval), dtype=np.uint8) data_p1[int(self.do_clock * (self.pspl_trig_time))] = 1 data = np.append(data_p0, data_p1) self.digital_output.CreateDOChan("/Dev1/port0/line0:1", "", DAQmx_Val_ChanPerLine) self.digital_output.CfgSampClkTiming( "", self.do_clock, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, int(data.size / 2) * self.attempts) self.digital_output.WriteDigitalLines( int(self.do_clock * self.trig_interval), 0, 1, DAQmx_Val_GroupByChannel, data, None, None) # Setup the PSPL` self.pspl.amplitude = 7.5 * np.power( 10, (-self.pspl_base_attenuation) / 20.0) self.pspl.trigger_source = "EXT" self.pspl.trigger_level = 0.5 self.pspl.output = True # Setup PSPL Attenuator Control self.atten.set_supply_method(self.lock.set_ao2) self.atten.set_control_method(self.lock.set_ao3) # Setup bias current method def set_bias(value): self.arb_cb.high_voltage = self.channel_bias.value * ( self.chan_bias_ref_res + self.matching_ref_res) self.arb_cb.low_voltage = 0.0 self.channel_bias.assign_method(set_bias) def set_voltage(voltage, base_atten=self.pspl_base_attenuation): # Calculate the voltage controller attenuator setting amplitude = 7.5 * np.power(10, -base_atten / 20.0) vc_atten = abs(20.0 * np.log10( abs(voltage) / 7.5)) - base_atten - self.circuit_attenuation if vc_atten <= self.atten.minimum_atten(): base_atten -= 1 if base_atten < 0: logger.error( "Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation." .format(vc_atten)) raise ValueError( "Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation." .format(vc_atten)) set_voltage(voltage, base_atten=base_atten) return if self.atten.maximum_atten() < vc_atten: base_atten += 1 if base_atten > 80: logger.error( "Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation." .format(vc_atten)) raise ValueError( "Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation." .format(vc_atten)) set_voltage(voltage, base_atten=base_atten) return #print("PSPL Amplitude: {}, Attenuator: {}".format(amplitude,vc_atten)) self.atten.set_attenuation(vc_atten) self.pspl.amplitude = self.pulse_polarity * amplitude time.sleep(0.04) # Assign Methods #self.channel_bias.assign_method(lambda i: self.arb_cb.set_high_voltage(i*self.chan_bias_ref_res)) self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1)) self.pulse_duration.assign_method(self.pspl.set_duration) self.pulse_voltage.assign_method(set_voltage) def init_streams(self): # Baked in data axes descrip = DataStreamDescriptor() descrip.data_name = 'voltage' descrip.add_axis( DataAxis("sample", range(int(self.integration_time * self.ai_clock)))) descrip.add_axis(DataAxis("attempt", range(self.attempts))) self.voltage.set_descriptor(descrip) async def run(self): self.digital_output.StartTask() self.digital_output.WaitUntilTaskDone(2 * self.attempts * self.trig_interval) self.digital_output.StopTask() await asyncio.sleep(0.05) # print("\t now ", self.nidaq.points_read) logger.debug("Stream has filled {} of {} points".format( self.voltage.points_taken, self.voltage.num_points())) def shutdown_instruments(self): try: # self.analog_input.StopTask() self.nidaq.StopTask() self.nidaq.ClearTask() self.digital_output.StopTask() del self.nidaq except Exception as e: print( "Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger)." ) pass self.arb_cb.output = False self.pspl.output = False for name, instr in self._instruments.items(): instr.disconnect()
def load_parameter_sweeps(experiment, manual_sweep_params=None): """Create parameter sweeps (non-segment sweeps) from the settings. Users can provide either a space-separated pair of *instr_name method_name* (i.e. *Holzworth1 power*) or specify a qubit property that auspex will try to link back to the relevant instrument. (i.e. *q1 measure frequency* or *q2 control amplitude 1*). Auspex will create a *SweepAxis* for each parameter sweep, and add this axis to all output connectors. If a channel number (1 or 2) is specified, it only sets that channel in the pair""" if manual_sweep_params: sweeps = manual_sweep_params order = [list(sweeps.keys())[0]] else: sweeps = experiment.settings['sweeps'] order = experiment.settings['sweepOrder'] channels = experiment.settings['qubits'] if 'edges' in experiment.settings: channels.update(experiment.settings['edges']) for name in order: par = sweeps[name] # Treat segment sweeps separately since they are DataAxes rather than SweepAxes if par['type'] != 'Segment': # Here we create a parameter for experiment and associate it with the # relevant method of the relevant experiment. # Add a parameter to the experiment corresponding to the thing we want to sweep if "unit" in par: param = FloatParameter(unit=par["unit"]) else: param = FloatParameter() param.name = name setattr(experiment, name, param) experiment._parameters[name] = param # We might need to return a custom function rather than just a method_name method = None # Figure our what we are sweeping target_info = par["target"].split() if target_info[0] in channels: # We are sweeping a qubit, so we must lookup the instrument target = par["target"].split() if target_info[0] in experiment.qubits: name, meas_or_control, prop = target[:3] isqubit = True else: name, prop = target[:2] isqubit = False if len(target) > 2 + isqubit: ch_ind = target[2 + isqubit] channel_params = channels[name][ meas_or_control] if isqubit else channels[name] method_name = "set_{}".format(prop.lower()) # If sweeping frequency, we should allow for either mixed up signals or direct synthesis. # Sweeping amplitude is always through the AWG channels. if 'generator' in channel_params and prop.lower( ) == "frequency": name = channel_params['generator'] instr = experiment._instruments[name] else: # Construct a function that sets a per-channel property name, chan = channel_params['AWG'].split() if len(target) > 3: chan = chan[int(ch_ind) - 1] instr = experiment._instruments[name] def method(value, channel=chan, instr=instr, prop=prop.lower()): # e.g. keysight.set_amplitude("ch1", 0.5) getattr(instr, "set_" + prop)(chan, value) elif target_info[0] in experiment._instruments: # We are sweeping an instrument directly # Get the instrument being swept, and find the relevant method name, prop = par["target"].split() instr = experiment._instruments[name] method_name = 'set_' + prop.lower() # If there's a "points" property, use those directly. Otherwise, we # use numPoints or the step interval. if "points" in par: points = par["points"] elif "numPoints" in par: points = np.linspace(par['start'], par['stop'], par['numPoints']) elif "step" in par: points = np.arange(par['start'], par['stop'], par['step']) if method: # Custom method param.assign_method(method) else: # Get method by name if hasattr(instr, method_name): param.assign_method( getattr(instr, method_name) ) # Couple the parameter to the instrument else: raise ValueError( "The instrument {} has no method {}".format( name, method_name)) param.instr_tree = [instr.name, prop] #TODO: extend tree to endpoint experiment.add_sweep( param, points) # Create the requested sweep on this parameter
class ShortProcessSwitchingExperiment(Experiment): # Parameters and outputs pulse_duration = FloatParameter(default=5.0e-9, unit="s") pulse_voltage = FloatParameter(default=1, unit="V") bias_current = FloatParameter(default=60e-6, unit="A") field = FloatParameter(default=0.0, unit="T") voltage_chan = OutputConnector() #voltage_MTJ = OutputConnector() resistance_MTJ = OutputConnector() # Constants (set with attribute access if you want to change these!) attempts = 1 << 4 # MTJ measurements measure_current = 3.0e-6 # Reference resistances and attenuations matching_ref_res = 50 chan_bias_ref_res = 1e4 pspl_base_attenuation = 10 circuit_attenuation = 0 # Min/Max NIDAQ AI Voltage min_daq_voltage = -1 max_daq_voltage = 1 # Measurement Sequence timing, clocks in Hz times in seconds ai_clock = 0.25e6 do_clock = 1e5 run_time = 2e-4 settle_time = 0.5*run_time integration_time = 0.1*run_time pspl_delay = 0.25*0.5*run_time # Instrument resources, NIDAQ called via PyDAQmx arb_cb = Agilent33220A("192.168.5.199") # Channel Bias pspl = Picosecond10070A("GPIB0::24::INSTR") # Gate Pulse atten = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv") # Gate Amplitude control lock = SR865("USB0::0xB506::0x2000::002638::INSTR") # Gate Amplitude control keith = Keithley2400("GPIB0::25::INSTR") mag = AMI430("192.168.5.109") def init_instruments(self): # Channel bias arb self.arb_cb.output = False self.arb_cb.load_resistance = (self.chan_bias_ref_res+self.matching_ref_res) self.arb_cb.function = 'Pulse' self.arb_cb.pulse_period = 0.99*self.run_time self.arb_cb.pulse_width = self.run_time - self.settle_time self.arb_cb.pulse_edge = 100e-9 self.arb_cb.low_voltage = 0.0 self.arb_cb.high_voltage = self.bias_current.value*(self.chan_bias_ref_res+self.matching_ref_res) self.arb_cb.polarity = 1 self.arb_cb.burst_state = True self.arb_cb.burst_cycles = 1 self.arb_cb.trigger_source = "External" self.arb_cb.burst_mode = "Triggered" self.arb_cb.output = True # Setup the Keithley self.keith.triad() self.keith.conf_meas_res(res_range=1e5) self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-5) self.keith.current = self.measure_current # Setup the magnet self.mag.ramp() # Setup the NIDAQ tasks DAQmxResetDevice("Dev1") self.nidaq = CallbackTask(["Dev1/ai0"], asyncio.get_event_loop(), int(self.integration_time*self.ai_clock), self.attempts, [self.voltage_chan], min_voltage=self.min_daq_voltage, max_voltage=self.max_daq_voltage, ai_clock=self.ai_clock) self.nidaq.StartTask() # Setup DO Triggers, P1:0 triggers AWG, P1:1 triggers PSPL and DAQ analog input self.digital_output = Task() data_p0 = np.zeros(int(self.do_clock*self.run_time),dtype=np.uint8) data_p0[0]=1 data_p1 = np.zeros(int(self.do_clock*self.run_time),dtype=np.uint8) data_p1[int(self.do_clock*(self.pspl_delay))]=1 # PSPL delay data = np.hstack([data_p0,data_p1]) self.digital_output.CreateDOChan("/Dev1/port0/line0:1","",DAQmx_Val_ChanPerLine) self.digital_output.CfgSampClkTiming("",self.do_clock, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, int(data.size/2)*self.attempts) self.digital_output.WriteDigitalLines(int(self.do_clock*self.run_time),0,1,DAQmx_Val_GroupByChannel,data,None,None) # Setup the PSPL self.pspl.amplitude = 7.5*np.power(10, (-self.pspl_base_attenuation)/20.0) self.pspl.trigger_source = "EXT" self.pspl.trigger_level = 0.5 self.pspl.output = True # Setup PSPL Attenuator Control self.atten.set_supply_method(self.lock.set_ao2) self.atten.set_control_method(self.lock.set_ao3) def set_pulse_voltage(voltage, base_atten=self.pspl_base_attenuation): # Calculate the voltage controller attenuator setting amplitude = 7.5*np.power(10, -base_atten/20.0) vc_atten = abs(20.0 * np.log10(abs(voltage)/7.5)) - base_atten - self.circuit_attenuation if vc_atten <= self.atten.minimum_atten(): base_atten -= 1 if base_atten < 0: logger.error("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten)) raise ValueError("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten)) set_pulse_voltage(voltage, base_atten=base_atten) return if self.atten.maximum_atten() < vc_atten: base_atten += 1 if base_atten > 80: logger.error("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten)) raise ValueError("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten)) set_pulse_voltage(voltage, base_atten=base_atten) return #print("PSPL Amplitude: {}, Attenuator: {}".format(amplitude,vc_atten)) self.atten.set_attenuation(vc_atten) self.pspl.amplitude = self.arb_cb.polarity*abs(amplitude) time.sleep(0.04) def set_awg_current(current): if 0 <= current: self.arb_cb.polarity = 1 self.arb_cb.low_voltage = 0 self.arb_cb.high_voltage = current*self.chan_bias_ref_res else : self.arb_cb.polarity = -1 self.arb_cb.low_voltage = current*self.chan_bias_ref_res self.arb_cb.high_voltage = 0 self.pspl.amplitude = self.arb_cb.polarity*abs(self.pspl.amplitude) time.sleep(0.04) # Assign Methods self.bias_current.assign_method(set_awg_current) self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1)) self.pulse_duration.assign_method(self.pspl.set_duration) self.pulse_voltage.assign_method(set_pulse_voltage) self.field.assign_method(self.mag.set_field) def init_streams(self): # Baked in data axes descrip = DataStreamDescriptor() descrip.data_name='voltage' descrip.add_axis(DataAxis("sample", range(int(self.integration_time*self.ai_clock)))) descrip.add_axis(DataAxis("attempt", range(self.attempts))) self.voltage_chan.set_descriptor(descrip) # descrip = DataStreamDescriptor() # descrip.data_name='voltage' # descrip.add_axis(DataAxis("sample", range(int(self.integration_time*self.ai_clock)))) # descrip.add_axis(DataAxis("attempt", range(self.attempts))) # self.voltage_MTJ.set_descriptor(descrip) descrip = DataStreamDescriptor() descrip.data_name='resistance' self.resistance_MTJ.set_descriptor(descrip) async def run(self): self.digital_output.StartTask() self.digital_output.WaitUntilTaskDone(2*self.attempts*self.run_time) self.digital_output.StopTask() await self.resistance_MTJ.push(self.keith.resistance) await asyncio.sleep(0.05) # print("\t now ", self.nidaq.points_read) # logger.debug("Stream has filled {} of {} points".format(self.voltage.points_taken, self.voltage.num_points() )) def shutdown_instruments(self): self.keith.current = 0.0e-5 self.mag.zero() try: for ch in [self.nidaq, self.nidaq_MTJ]: ch.StopTask() ch.ClearTask() self.digital_output.StopTask() del ch except Exception as e: print("Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger).") pass self.arb_cb.output = False self.pspl.output = False for name, instr in self._instruments.items(): instr.disconnect()
class ShortProcessSwitchingExperimentReset(Experiment): # Parameters and outputs pulse_duration = FloatParameter(default=5.0e-9, unit="s") pulse_voltage = FloatParameter(default=1, unit="V") bias_current = FloatParameter(default=60e-6, unit="A") field = FloatParameter(default=0.0, unit="T") voltage = OutputConnector() polarity = 1 attempts = 1 << 4 # How many times to try switching per set of conditions measure_current = 10.0e-6 # MTJ sense current reset_current = 0.5e-3 # AWG reset current (remember division) MTJ_res = 100e3 # MTJ Resistance # Reference resistances and attenuations matching_ref_res = 50 chan_bias_ref_res = 1e4 reset_bias_ref_res = 5e3 pspl_base_attenuation = 10 circuit_attenuation = 0 # Reset Pulse Switching Pulse # ___/\____ # \_______/ / \ # A B C D # |-------------------------------------> # MEAS Begins (ignore middle) MEAS Ends # | Integ. time| |integ time| # Measurement Sequence timing, clocks in Hz times in seconds ai_clock = 0.25e6 do_clock = 0.5e6 trig_interval = 200e-6 # The repetition rate for switching attempts bias_pulse_width = 40e-6 # This is how long the bias pulse is reset_pulse_width = 40e-6 # This is how long the reset pulse is meas_duration = 140e-6 # This is how long the meas is reset_trig_time = 0e-6 # (A) When we trigger the reset pulse meas_trig_time = 50e-6 # (B) When the measurement trigger begins switch_trig_time = 4e-6 # (C) When we trigger the reset pulse pspl_trig_time = 4e-6 # (D) When we trigger the gate pulse integration_time = 20e-6 # How long to measure for at the beginning and end # Instrument resources, NIDAQ called via PyDAQmx arb_cb = Agilent33220A("192.168.5.199") # Channel Bias arb_reset = Agilent33220A("192.168.5.198") # Channel reset pulses pspl = Picosecond10070A("GPIB0::24::INSTR") # Gate Pulse atten = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv") # Gate Amplitude control lock = SR865("USB0::0xB506::0x2000::002638::INSTR") # Gate Amplitude control keith = Keithley2400("GPIB0::25::INSTR") mag = AMI430("192.168.5.109") def init_instruments(self): # Channel bias arb self.arb_cb.output = False self.arb_cb.load_resistance = (self.chan_bias_ref_res+self.matching_ref_res) self.arb_cb.function = 'Pulse' self.arb_cb.pulse_period = 0.99*self.trig_interval self.arb_cb.pulse_width = self.bias_pulse_width self.arb_cb.pulse_edge = 100e-9 self.arb_cb.low_voltage = 0.0 self.arb_cb.high_voltage = self.bias_current.value*(self.chan_bias_ref_res+self.matching_ref_res) self.arb_cb.polarity = self.polarity self.arb_cb.burst_state = True self.arb_cb.burst_cycles = 1 self.arb_cb.trigger_source = "External" self.arb_cb.burst_mode = "Triggered" self.arb_cb.output = True # MTJ reset arb self.arb_reset.output = False self.arb_reset.load_resistance = (self.reset_bias_ref_res+self.matching_ref_res) self.arb_reset.function = 'Pulse' self.arb_reset.pulse_period = 0.99*self.trig_interval self.arb_reset.pulse_width = self.reset_pulse_width self.arb_reset.pulse_edge = 100e-9 self.arb_reset.low_voltage = 0.0 self.arb_reset.high_voltage = self.reset_current*(self.reset_bias_ref_res+self.matching_ref_res) self.arb_reset.polarity = -self.polarity self.arb_reset.burst_state = True self.arb_reset.burst_cycles = 1 self.arb_reset.trigger_source = "External" self.arb_reset.burst_mode = "Triggered" self.arb_reset.output = True # Setup the Keithley self.keith.triad() self.keith.conf_meas_res(res_range=1e5) self.keith.conf_src_curr(comp_voltage=0.5, curr_range=1.0e-4) self.keith.current = self.measure_current # Setup the magnet self.mag.ramp() # Setup the NIDAQ tasks DAQmxResetDevice("Dev1") self.nidaq = CallbackTask(["Dev1/ai2"], asyncio.get_event_loop(), int(self.meas_duration*self.ai_clock), self.attempts, [self.voltage], trigger="/Dev1/PFI12", min_voltage=-self.MTJ_res*self.measure_current, max_voltage=self.MTJ_res*self.measure_current, ai_clock=self.ai_clock) self.nidaq.StartTask() self.digital_output = Task() do_reset = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8) do_reset[int(self.do_clock*self.reset_trig_time)]=1 do_bias = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8) do_bias[int(self.do_clock*self.switch_trig_time)]=1 do_pspl = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8) do_pspl[int(self.do_clock*self.pspl_trig_time)]=1 do_meas = np.zeros(int(self.do_clock*self.trig_interval),dtype=np.uint8) do_meas[int(self.do_clock*self.meas_trig_time)]=1 data = np.hstack([do_reset, do_bias, do_pspl, do_meas]) self.digital_output.CreateDOChan("/Dev1/port0/line0:3","",DAQmx_Val_ChanPerLine) self.digital_output.CfgSampClkTiming("",self.do_clock, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, int(data.size/4)*self.attempts) self.digital_output.WriteDigitalLines(int(self.do_clock*self.trig_interval),0,1,DAQmx_Val_GroupByChannel,data,None,None) # Setup the PSPL self.pspl.amplitude = 7.5*np.power(10, (-self.pspl_base_attenuation)/20.0) self.pspl.trigger_source = "EXT" self.pspl.trigger_level = 0.5 self.pspl.output = True # Setup PSPL Attenuator Control self.atten.set_supply_method(self.lock.set_ao2) self.atten.set_control_method(self.lock.set_ao3) def set_pulse_voltage(voltage, base_atten=self.pspl_base_attenuation): # Calculate the voltage controller attenuator setting amplitude = 7.5*np.power(10, -base_atten/20.0) vc_atten = abs(20.0 * np.log10(abs(voltage)/7.5)) - base_atten - self.circuit_attenuation if vc_atten <= self.atten.minimum_atten(): base_atten -= 1 if base_atten < 0: logger.error("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten)) raise ValueError("Voltage controlled attenuation {} under range, PSPL at Max. Decrease circuit attenuation.".format(vc_atten)) set_pulse_voltage(voltage, base_atten=base_atten) return if self.atten.maximum_atten() < vc_atten: base_atten += 1 if base_atten > 80: logger.error("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten)) raise ValueError("Voltage controlled attenuation {} over range, PSPL at Min. Increase circuit attenuation.".format(vc_atten)) set_pulse_voltage(voltage, base_atten=base_atten) return #print("PSPL Amplitude: {}, Attenuator: {}".format(amplitude,vc_atten)) self.atten.set_attenuation(vc_atten) self.pspl.amplitude = self.arb_cb.polarity*abs(amplitude) time.sleep(0.04) def set_awg_current(current): if 0 <= current: self.arb_cb.polarity = 1 self.arb_cb.low_voltage = 0 self.arb_cb.high_voltage = current*self.chan_bias_ref_res else : self.arb_cb.polarity = -1 self.arb_cb.low_voltage = current*self.chan_bias_ref_res self.arb_cb.high_voltage = 0 self.pspl.amplitude = self.arb_cb.polarity*abs(self.pspl.amplitude) time.sleep(0.04) # Assign Methods self.bias_current.assign_method(set_awg_current) self.pulse_duration.assign_method(self.pspl.set_duration) self.pulse_voltage.assign_method(set_pulse_voltage) self.field.assign_method(self.mag.set_field) self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.05)) self.pulse_voltage.add_post_push_hook(lambda: time.sleep(0.05)) self.bias_current.add_post_push_hook(lambda: time.sleep(0.05)) def init_streams(self): descrip = DataStreamDescriptor() descrip.data_name='voltage' descrip.add_axis(DataAxis("sample", range(int(self.meas_duration*self.ai_clock)))) descrip.add_axis(DataAxis("attempt", range(self.attempts))) self.voltage.set_descriptor(descrip) async def run(self): self.digital_output.StartTask() self.digital_output.WaitUntilTaskDone(2*self.attempts*self.trig_interval) self.digital_output.StopTask() await asyncio.sleep(0.05) def shutdown_instruments(self): self.keith.current = 0.0e-5 self.keith.triad(down=True) self.mag.zero() try: for ch in [self.nidaq, self.nidaq_MTJ]: ch.StopTask() ch.ClearTask() self.digital_output.StopTask() del ch except Exception as e: print("Warning: failed to stop task (this normally happens with no consequences when taking multiple samples per trigger).") pass self.arb_cb.output = False self.pspl.output = False for name, instr in self._instruments.items(): instr.disconnect()
class SwitchSearchLockinExperiment(Experiment): voltage = OutputConnector() sample = "CSHE2" comment = "Search PSPL Switch Voltage" field = FloatParameter(default=0.0, unit="T") pulse_voltage = FloatParameter(default=0, unit="V") pulse_duration = FloatParameter(default=5.0e-9, unit="s") # Default values for lockin measurement. These will need to be changed in a notebook to match the MR and switching current of the sample being measured res_reference = 1e3 sample_resistance = 50 measure_current = 10e-6 fdB = 18 tc = 100e-3 circuit_attenuation = 20.0 pspl_base_attenuation = 30.0 attempts = 1 << 8 # Number of attemps samps_per_trig = 10 # Samples per trigger # Instruments arb = KeysightM8190A("192.168.5.108") pspl = Picosecond10070A("GPIB0::24::INSTR") mag = AMI430("192.168.5.109") lock = SR865("USB0::0xB506::0x2000::002638::INSTR") atten = RFMDAttenuator("calibration/RFSA2113SB_HPD_20160901.csv") min_daq_voltage = 0 max_daq_voltage = 10 def init_instruments(self): # =================== # Setup the Lockin # =================== self.lock.tc = self.tc self.lock.filter_slope = self.fdB self.lock.amp = (self.res_reference + self.sample_resistance) * self.measure_current sense_vals = np.array(self.lock.SENSITIVITY_VALUES) self.lock.sensitivity = sense_vals[np.argmin( np.absolute(sense_vals - 2 * self.sample_resistance * self.measure_current * np.ones(sense_vals.size)))] time.sleep(20 * self.lock.measure_delay()) # Rescale lockin analogue output for NIDAQ self.lock.r_offset_enable = True self.lock.r_expand = 100 self.lock.auto_offset("R") self.lock.r_offset = 0.99 * self.lock.r_offset #self.lock.r_offset = 100 * ((self.sample_resistance*self.measure_current/self.lock.sensitivity) - (0.05/self.lock.r_expand)) time.sleep(20 * self.lock.measure_delay()) # Ramp magnet to set point self.mag.ramp() #Set attenuator control methods self.atten.set_supply_method(self.lock.set_ao2) self.atten.set_control_method(self.lock.set_ao3) # =================== # Setup the AWG # =================== self.arb.set_output(True, channel=1) self.arb.set_output(False, channel=2) self.arb.sample_freq = 12.0e9 self.arb.waveform_output_mode = "WSPEED" self.arb.set_output_route("DC", channel=1) self.arb.voltage_amplitude = 1.0 self.arb.set_marker_level_low(0.0, channel=1, marker_type="sync") self.arb.set_marker_level_high(1.5, channel=1, marker_type="sync") self.arb.continuous_mode = False self.arb.gate_mode = False # =================== # Setup the PSPL # =================== # PSPL Max amplitude is 7.5 V self.pspl.amplitude = 7.5 * np.power( 10, -self.pspl_base_attenuation / 20.0) self.pspl.trigger_source = "EXT" self.pspl.trigger_level = 0.1 self.pspl.output = True self.setup_daq() def set_voltage(voltage): # Calculate the voltage controller attenuator setting self.pspl.amplitude = np.sign(voltage) * 7.5 * np.power( 10, -self.pspl_base_attenuation / 20.0) vc_atten = abs( 20.0 * np.log10(abs(voltage) / 7.5) ) - self.pspl_base_attenuation - self.circuit_attenuation if vc_atten <= self.atten.minimum_atten(): logger.error( "Voltage controlled attenuation {} under range.".format( vc_atten)) raise ValueError( "Voltage controlled attenuation {} under range.".format( vc_atten)) if self.atten.maximum_atten() < vc_atten: logger.error( "Voltage controlled attenuation {} over range.".format( vc_atten)) raise ValueError( "Voltage controlled attenuation {} over range.".format( vc_atten)) self.atten.set_attenuation(vc_atten) time.sleep(0.02) # Assign methods self.field.assign_method(self.mag.set_field) self.pulse_duration.assign_method(self.pspl.set_duration) self.pulse_voltage.assign_method(set_voltage) # Create hooks for relevant delays self.pulse_duration.add_post_push_hook(lambda: time.sleep(0.1)) def setup_daq(self): self.arb.abort() self.arb.delete_all_waveforms() self.arb.reset_sequence_table() # Picosecond trigger waveform pspl_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200), samp_mkr=1) pspl_trig_segment_id = self.arb.define_waveform(len(pspl_trig_wf)) self.arb.upload_waveform(pspl_trig_wf, pspl_trig_segment_id) # NIDAQ trigger waveform nidaq_trig_wf = KeysightM8190A.create_binary_wf_data(np.zeros(3200), sync_mkr=1) nidaq_trig_segment_id = self.arb.define_waveform(len(nidaq_trig_wf)) self.arb.upload_waveform(nidaq_trig_wf, nidaq_trig_segment_id) settle_pts = int(640 * np.ceil(self.lock.measure_delay() * 12e9 / 640)) scenario = Scenario() seq = Sequence(sequence_loop_ct=int(self.attempts)) seq.add_waveform(pspl_trig_segment_id) seq.add_idle(settle_pts, 0.0) seq.add_waveform(nidaq_trig_segment_id) seq.add_idle(1 << 16, 0.0) # bonus non-contiguous memory delay scenario.sequences.append(seq) self.arb.upload_scenario(scenario, start_idx=0) self.arb.sequence_mode = "SCENARIO" self.arb.scenario_advance_mode = "REPEAT" self.arb.scenario_start_index = 0 self.arb.run() # =================== # Setup the NIDAQ # =================== self.analog_input = Task() self.read = int32() self.buf_points = self.samps_per_trig * self.attempts self.analog_input.CreateAIVoltageChan("Dev1/ai0", "", DAQmx_Val_Diff, self.min_daq_voltage, self.max_daq_voltage, DAQmx_Val_Volts, None) self.analog_input.CfgSampClkTiming("", 1e6, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, self.samps_per_trig) self.analog_input.CfgInputBuffer(self.buf_points) self.analog_input.CfgDigEdgeStartTrig("/Dev1/PFI0", DAQmx_Val_Rising) self.analog_input.SetStartTrigRetriggerable(1) self.analog_input.StartTask() def init_streams(self): # Baked in data axes descrip = DataStreamDescriptor() descrip.add_axis(DataAxis("sample", range(self.samps_per_trig))) descrip.add_axis(DataAxis("attempts", range(self.attempts))) self.voltage.set_descriptor(descrip) async def run(self): self.arb.advance() self.arb.trigger() buf = np.empty(self.buf_points) self.analog_input.ReadAnalogF64(self.buf_points, -1, DAQmx_Val_GroupByChannel, buf, self.buf_points, byref(self.read), None) logger.debug("Read a buffer of {} points".format(buf.size)) await self.voltage.push(buf) # Seemingly we need to give the filters some time to catch up here... await asyncio.sleep(0.02) logger.debug("Stream has filled {} of {} points".format( self.voltage.points_taken, self.voltage.num_points())) def shutdown_instruments(self): try: self.analog_input.StopTask() except Exception as e: logger.warning( "Warning failed to stop task, which is quite typical (!)") self.arb.stop() self.pspl.output = False self.lock.amp = 0