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)
Exemple #2
0
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
Exemple #3
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()
Exemple #4
0
    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
Exemple #5
0
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()
Exemple #6
0
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