From d63b5d4083905da49e716f5e7786074399dccab4 Mon Sep 17 00:00:00 2001 From: gribeill Date: Mon, 21 Oct 2019 13:41:59 -0400 Subject: [PATCH 1/9] First pass at a new network analyzer interface. --- src/auspex/instruments/agilent.py | 319 ++++++++++++++++++++++++------ 1 file changed, 263 insertions(+), 56 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index 6a5280fb..74f9d9b5 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -13,6 +13,7 @@ import copy import re import numpy as np +from itertools import product from .instrument import SCPIInstrument, Command, StringCommand, BoolCommand, FloatCommand, IntCommand, is_valid_ipv4 from auspex.log import logger import pyvisa.util as util @@ -711,28 +712,32 @@ def reference(self): def reference(self, ref=None): pass -class AgilentE8363C(SCPIInstrument): - """Agilent E8363C VNA""" +class _AgilentNetworkAnalyzer(SCPIInstrument): instrument_type = "Vector Network Analyzer" - TIMEOUT = 10. * 1000. #milliseconds - - power_port1 = FloatCommand(scpi_string=":SOURce:POWer1:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) - power_port2 = FloatCommand(scpi_string=":SOURce:POWer2:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) - frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") - frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") - frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") - frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") - sweep_num_points = IntCommand(scpi_string=":SENSe:SWEep:POINts") - averaging_factor = IntCommand(scpi_string=":SENSe1:AVERage:COUNt") - averaging_enable = BoolCommand(get_string=":SENSe1:AVERage:STATe?", set_string=":SENSe1:AVERage:STATe {:s}", value_map={False: "0", True: "1"}) - averaging_complete = StringCommand(get_string=":STATus:OPERation:AVERaging1:CONDition?", value_map={False:"+0", True:"+2"}) - if_bandwidth = FloatCommand(scpi_string=":SENSe1:BANDwidth") - sweep_time = FloatCommand(get_string=":SENSe:SWEep:TIME?") + TIMEOUT = 10 * 1000. + + ports = () + + _format_dict = {"MLIN": "LINEAR", "MLOG": "LOGARITHMIC", "PHAS": "PHASE", "UPH": "UNWRAP PHASE", + "REAL": "REAL", "IMAG": "IMAG", "POL": "POLAR", "SMIT": "SMITH", + "SADM": "SMITH ADMITTANCE", "SWR": "SWR", "GDEL": "GROUP DELAY"} + _format_dict_inv = {v: k for k, v in _format_dict.items()} + + frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") + frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") + frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") + frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") + if_bandwidth = FloatCommand(scpi_string=":SENSe1:BANDwidth") + + averaging_enable = BoolCommand(get_string=":SENSe1:AVERage:STATe?", set_string=":SENSe1:AVERage:STATe {:s}", value_map={False: "0", True: "1"}) + averaging_factor = IntCommand(scpi_string=":SENSe1:AVERage:COUNt") + averaging_complete = StringCommand(get_string=":STATus:OPERation:AVERaging1:CONDition?", value_map={False:"+0", True:"+2"}) + def __init__(self, resource_name=None, *args, **kwargs): - #If we only have an IP address then tack on the raw socket port to the VISA resource string - super(AgilentE8363C, self).__init__(resource_name, *args, **kwargs) + self.valid_meas = tuple(f"S{a}{b}" for a, b in product(self.ports, self.ports)) + super().__init__(resource_name, *args, **kwargs) def connect(self, resource_name=None, interface_type="VISA"): if resource_name is not None: @@ -740,12 +745,11 @@ def connect(self, resource_name=None, interface_type="VISA"): if is_valid_ipv4(self.resource_name): self.resource_name += "::hpib7,16::INSTR" else: - logger.error("The resource name for the Agilent E8363C: {} is " + - "not a valid IPv4 address.".format(self.resource_name)) - super(AgilentE8363C, self).connect(resource_name=None, - interface_type=interface_type) + logger.error("The resource name for the {}: {} is " + + "not a valid IPv4 address.".format(self.__class__.__name__, self.resource_name)) + super().connect(resource_name=None, interface_type=interface_type) self.interface._resource._read_termination = u"\n" - self.interface._resource.write_termination = u"\n" + self.interface._resource._write_termination = u"\n" self.interface._resource.timeout = self.TIMEOUT self.interface._resource.chunk_size = 2 ** 20 # Needed to fix binary transfers (?) @@ -753,9 +757,61 @@ def connect(self, resource_name=None, interface_type="VISA"): self.interface.write("SENSe1:SWEep:TIME:AUTO ON") #automatic sweep time self.interface.write("FORM REAL,32") #return measurement data as 32-bit float - def averaging_restart(self): - """ Restart trace averaging """ - self.interface.write(":SENSe1:AVERage:CLEar") + self.measurements = ["S21"] + self._port_powers = {} + for p in self.ports: + self.interface.write(f'SOURce:POWer{p}:MODE AUTO') + self._port_powers[p] = (self.interface.query(f"SOUR:POW{p}:PORT:STAR MIN"), + self.interface.query(f"SOUR:POW{p}:PORT:STAR MAX")) + + @property + def output_enable(self): + outp = {} + for p in self.ports: + if self.interface.query(f'SOUR:POW{p}:MODE?') == "OFF": + outp[p] = False + else: + outp[p] = True + return outp + + @output_enable.setter + def output_enable(self, outp): + if isinstance(outp, dict): + for k, v in self.outp.items(): + val = "AUTO" if v else "OFF" + self.interface.write(f"SOUR:POW{k}:MODE {val}") + else: + val = "AUTO" if outp else "OFF" + for p in self.ports: + self.interface.write(f"SOUR:POW{p}:MODE {val}") + + def set_port_power(self, port, power): + assert port in self.ports, f"This VNA does not have port {port}!" + minp = self._port_powers[port][0] + maxp = self._port_powers[port][1] + if power < minp or power > maxp: + raise ValueError(f"Power level outside allowable range for port {port}: ({minp} - {maxp}) dBm.") + self.interface.write(f"SOUR:POW{port} {power}") + + def get_port_power(self, port): + assert port in self.ports, f"This VNA does not have port {port}!" + return self.interface.query(f"SOUR:POW{port}?") + + def _get_active_ports(self): + return [int(m[-1] for m in self.measurements.keys())] + + @property + def power(self): + ports = self._get_active_ports() + if len(ports) == 1: + return self.get_port_power(ports[0]) + else: + return [self.get_port_power(p) for p in ports] + + @power.setter + def power(self, level): + for p in self._get_active_ports(): + self.set_port_power(p, level) @property def averaging_enable(self): @@ -769,33 +825,47 @@ def averaging_enable(self, value): else: self.interface.write(":SENSe1:AVERage:STATe OFF") - @property - def power(self): - return self.power_port1 if self.measurement[-1] == '1' else self.power_port2 - - @power.setter - def power(self, value): - if self.measurement[-1] == '1': - self.power_port1 = value - else: - self.power_port2 = value - - @property - def measurement(self): - meas = self.interface.query(":CALC:PAR:CAT?") - return meas.strip('\"').split(",")[-1] - - @measurement.setter - def measurement(self, meas): - meas = meas.upper() - valid_meas = ('S11', 'S12', 'S21', 'S22') - if meas not in valid_meas: - raise ValueError(f"Unknown measurement {meas}; must be one of {valid_meas}") - + @property + def format(self): + meas = self.measurements.values() + self.interface.write(f"CALC:PAR:SEL {meas[0]}") + return self._format_dict_inv[self.interface.query("CALC:FORM?")] + + @format.setter + def format(self, fmt): + if fmt not in self._format_dict.items() and fmt not in self._format_dict.keys(): + raise ValueError(f"Unrecognized VNA measurement format specifier: {fmt}") + for meas in self.measurements.values(): + self.interface.write(f"CALC:PAR:SEL {meas[0]}") + self.interface.query("CALC:FORM? {fmt}") + + @property + def measurements(self): + active_meas = self.interface.query("CALC:PAR:CAT?") + meas = active_meas.strip('\"').split(",")[::2] + spars = active_meas.strip('\"').split(",")[1::2] + return {s: m for m, s in zip(meas,spars)} + + @measurements.setter + def measurements(self, S): + sp = [s.upper() for s in S] + for s in sp: + if s not in self.valid_meas: + raise ValueError(f"Invalid S-parameter measurement request: {s} is not in available measurements: {self.valid_meas}.") + + #Delete all measurements self.interface.write("CALC:PAR:DEL:ALL") - self.interface.write(f":CALC:PAR:DEF:EXT 'R_{meas}',{meas}") - self.interface.write(f"DISP:WIND1:TRAC1:FEED 'R_{meas}'") + #Close window 1 if it exists + if (self.interface.query("DISP:WIND1:STATE?") == "1"): + self.interface.write("DISP:WIND1:STATE OFF") + self.interface.write("DISP:WIND1:STATE ON") + + for j, s in enumerate(sp): + self.interface.write(f'CALC:PAR:DEF:EXT "M_{s}",{s}') + self.interface.write(f'DISP:WIND1:TRAC{j} "M_{s}"') + self.interface.write(f'DISP:WIND1:TRAC{j}:Y:AUTO') + self.interface.write('SENS1:SWE:TIME:AUTO ON') def reaverage(self): """ Restart averaging and block until complete """ @@ -820,12 +890,13 @@ def get_trace(self, measurement=None): """ Return a tupple of the trace frequencies and corrected complex points """ #If the measurement is not passed in just take the first one if measurement is None: - traces = self.interface.query(":CALCulate:PARameter:CATalog?") - #traces come e.g. as u'"CH1_S11_1,S11,CH1_S21_2,S21"' - #so split on comma and avoid first quote - measurement = traces.split(",")[0][1:] + mchan = self.measurements.values()[0] + else: + if measurement not in self.measurements.values(): + raise ValueError(f"Unknown measurement: {measurement}. Available: {self.measurements.keys()}.") + mchan = self.measurements[measurement] #Select the measurment - self.interface.write(":CALCulate:PARameter:SELect '{}'".format(measurement)) + self.interface.write(":CALCulate:PARameter:SELect '{}'".format(mchan)) self.reaverage() self.interface.write(":CALC:DATA? SDATA") block = self.interface.read_raw(size=256) @@ -840,6 +911,142 @@ def get_trace(self, measurement=None): freqs = np.linspace(self.frequency_start, self.frequency_stop, self.sweep_num_points) return (freqs, vals) + +class AgilentN5230A(_AgilentNetworkAnalyzer): + ports = (1, 2, 3, 4) + +class AgilentE8363C(_AgilentNetworkAnalyzer): + ports = (1, 2) + +# class AgilentE8363C(SCPIInstrument): +# """Agilent E8363C VNA""" +# instrument_type = "Vector Network Analyzer" + +# TIMEOUT = 10. * 1000. #milliseconds + +# power_port1 = FloatCommand(scpi_string=":SOURce:POWer1:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) +# power_port2 = FloatCommand(scpi_string=":SOURce:POWer2:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) +# frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") +# frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") +# frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") +# frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") +# sweep_num_points = IntCommand(scpi_string=":SENSe:SWEep:POINts") +# averaging_factor = IntCommand(scpi_string=":SENSe1:AVERage:COUNt") +# averaging_enable = BoolCommand(get_string=":SENSe1:AVERage:STATe?", set_string=":SENSe1:AVERage:STATe {:s}", value_map={False: "0", True: "1"}) +# averaging_complete = StringCommand(get_string=":STATus:OPERation:AVERaging1:CONDition?", value_map={False:"+0", True:"+2"}) +# if_bandwidth = FloatCommand(scpi_string=":SENSe1:BANDwidth") +# sweep_time = FloatCommand(get_string=":SENSe:SWEep:TIME?") + +# def __init__(self, resource_name=None, *args, **kwargs): +# #If we only have an IP address then tack on the raw socket port to the VISA resource string +# super(AgilentE8363C, self).__init__(resource_name, *args, **kwargs) + +# def connect(self, resource_name=None, interface_type="VISA"): +# if resource_name is not None: +# self.resource_name = resource_name +# if is_valid_ipv4(self.resource_name): +# self.resource_name += "::hpib7,16::INSTR" +# else: +# logger.error("The resource name for the Agilent E8363C: {} is " + +# "not a valid IPv4 address.".format(self.resource_name)) +# super(AgilentE8363C, self).connect(resource_name=None, +# interface_type=interface_type) +# self.interface._resource._read_termination = u"\n" +# self.interface._resource.write_termination = u"\n" +# self.interface._resource.timeout = self.TIMEOUT +# self.interface._resource.chunk_size = 2 ** 20 # Needed to fix binary transfers (?) + +# self.interface.OPC() #wait for any previous commands to complete +# self.interface.write("SENSe1:SWEep:TIME:AUTO ON") #automatic sweep time +# self.interface.write("FORM REAL,32") #return measurement data as 32-bit float + +# def averaging_restart(self): +# """ Restart trace averaging """ +# self.interface.write(":SENSe1:AVERage:CLEar") + +# @property +# def averaging_enable(self): +# state = self.interface.query(":SENSe1:AVERage:STATe?") +# return bool(int(state)) + +# @averaging_enable.setter +# def averaging_enable(self, value): +# if value: +# self.interface.write(":SENSe1:AVERage:STATe ON") +# else: +# self.interface.write(":SENSe1:AVERage:STATe OFF") + +# @property +# def power(self): +# return self.power_port1 if self.measurement[-1] == '1' else self.power_port2 + +# @power.setter +# def power(self, value): +# if self.measurement[-1] == '1': +# self.power_port1 = value +# else: +# self.power_port2 = value + +# @property +# def measurement(self): +# meas = self.interface.query(":CALC:PAR:CAT?") +# return meas.strip('\"').split(",")[-1] + +# @measurement.setter +# def measurement(self, meas): +# meas = meas.upper() +# valid_meas = ('S11', 'S12', 'S21', 'S22') +# if meas not in valid_meas: +# raise ValueError(f"Unknown measurement {meas}; must be one of {valid_meas}") + +# self.interface.write("CALC:PAR:DEL:ALL") +# self.interface.write(f":CALC:PAR:DEF:EXT 'R_{meas}',{meas}") +# self.interface.write(f"DISP:WIND1:TRAC1:FEED 'R_{meas}'") + + +# def reaverage(self): +# """ Restart averaging and block until complete """ +# if self.averaging_enable: +# self.averaging_restart() +# #trigger after the requested number of points has been averaged +# self.interface.write("SENSe1:SWEep:GROups:COUNt %d"%self.averaging_factor) +# self.interface.write("ABORT; SENSe1:SWEep:MODE GRO") +# else: +# #restart current sweep and send a trigger +# self.interface.write("ABORT; SENS:SWE:MODE SING") + +# meas_done = False +# self.interface.write('*OPC') +# while not meas_done: +# time.sleep(0.5) +# opc_bit = int(self.interface.ESR()) & 0x1 +# if opc_bit == 1: +# meas_done = True + +# def get_trace(self, measurement=None): +# """ Return a tupple of the trace frequencies and corrected complex points """ +# #If the measurement is not passed in just take the first one +# if measurement is None: +# traces = self.interface.query(":CALCulate:PARameter:CATalog?") +# #traces come e.g. as u'"CH1_S11_1,S11,CH1_S21_2,S21"' +# #so split on comma and avoid first quote +# measurement = traces.split(",")[0][1:] +# #Select the measurment +# self.interface.write(":CALCulate:PARameter:SELect '{}'".format(measurement)) +# self.reaverage() +# self.interface.write(":CALC:DATA? SDATA") +# block = self.interface.read_raw(size=256) +# offset, data_length = util.parse_ieee_block_header(block) +# interleaved_vals = util.from_ieee_block(block, 'f', True, np.array) + +# #Take the data as interleaved complex values +# self.interface.write("SENS:SWE:MODE CONT") + +# vals = interleaved_vals[::2] + 1j*interleaved_vals[1::2] +# #Get the associated frequencies +# freqs = np.linspace(self.frequency_start, self.frequency_stop, self.sweep_num_points) +# return (freqs, vals) + class AgilentE9010A(SCPIInstrument): """Agilent E9010A SA""" instrument_type = "Spectrum Analyzer" From 411a96a52d3463000132882b5c849074c6635a94 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Tue, 22 Oct 2019 14:22:25 -0400 Subject: [PATCH 2/9] WIP --- src/auspex/instruments/agilent.py | 81 +++++++++++++++++-------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index 74f9d9b5..c46a9ae2 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -718,9 +718,10 @@ class _AgilentNetworkAnalyzer(SCPIInstrument): TIMEOUT = 10 * 1000. ports = () + _port_powers = {} - _format_dict = {"MLIN": "LINEAR", "MLOG": "LOGARITHMIC", "PHAS": "PHASE", "UPH": "UNWRAP PHASE", - "REAL": "REAL", "IMAG": "IMAG", "POL": "POLAR", "SMIT": "SMITH", + _format_dict = {"MLIN": "LINEAR", "MLOG": "LOGARITHMIC", "PHAS": "PHASE", "UPH": "UNWRAP PHASE", + "REAL": "REAL", "IMAG": "IMAG", "POL": "POLAR", "SMIT": "SMITH", "SADM": "SMITH ADMITTANCE", "SWR": "SWR", "GDEL": "GROUP DELAY"} _format_dict_inv = {v: k for k, v in _format_dict.items()} @@ -729,6 +730,7 @@ class _AgilentNetworkAnalyzer(SCPIInstrument): frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") if_bandwidth = FloatCommand(scpi_string=":SENSe1:BANDwidth") + num_points = IntCommand(scpi_string=":SENSe:SWEep:POINTS") averaging_enable = BoolCommand(get_string=":SENSe1:AVERage:STATe?", set_string=":SENSe1:AVERage:STATe {:s}", value_map={False: "0", True: "1"}) averaging_factor = IntCommand(scpi_string=":SENSe1:AVERage:COUNt") @@ -748,8 +750,7 @@ def connect(self, resource_name=None, interface_type="VISA"): logger.error("The resource name for the {}: {} is " + "not a valid IPv4 address.".format(self.__class__.__name__, self.resource_name)) super().connect(resource_name=None, interface_type=interface_type) - self.interface._resource._read_termination = u"\n" - self.interface._resource._write_termination = u"\n" + self.interface._resource.read_termination = u"\n" self.interface._resource.timeout = self.TIMEOUT self.interface._resource.chunk_size = 2 ** 20 # Needed to fix binary transfers (?) @@ -758,23 +759,22 @@ def connect(self, resource_name=None, interface_type="VISA"): self.interface.write("FORM REAL,32") #return measurement data as 32-bit float self.measurements = ["S21"] - self._port_powers = {} for p in self.ports: - self.interface.write(f'SOURce:POWer{p}:MODE AUTO') - self._port_powers[p] = (self.interface.query(f"SOUR:POW{p}:PORT:STAR MIN"), - self.interface.query(f"SOUR:POW{p}:PORT:STAR MAX")) + self.interface.write(f'SOUR:POW{p}:MODE AUTO') + self._port_powers[p] = (float(self.interface.query(f"SOUR:POW{p}? MIN")), + float(self.interface.query(f"SOUR:POW{p}? MAX"))) @property def output_enable(self): outp = {} for p in self.ports: if self.interface.query(f'SOUR:POW{p}:MODE?') == "OFF": - outp[p] = False + outp[p] = False else: - outp[p] = True - return outp + outp[p] = True + return outp - @output_enable.setter + @output_enable.setter def output_enable(self, outp): if isinstance(outp, dict): for k, v in self.outp.items(): @@ -786,7 +786,7 @@ def output_enable(self, outp): self.interface.write(f"SOUR:POW{p}:MODE {val}") def set_port_power(self, port, power): - assert port in self.ports, f"This VNA does not have port {port}!" + assert port in self.ports, f"This VNA does not have port {port}!" minp = self._port_powers[port][0] maxp = self._port_powers[port][1] if power < minp or power > maxp: @@ -794,13 +794,13 @@ def set_port_power(self, port, power): self.interface.write(f"SOUR:POW{port} {power}") def get_port_power(self, port): - assert port in self.ports, f"This VNA does not have port {port}!" + assert port in self.ports, f"This VNA does not have port {port}!" return self.interface.query(f"SOUR:POW{port}?") def _get_active_ports(self): - return [int(m[-1] for m in self.measurements.keys())] + return [int(m[-1]) for m in self.measurements.keys()] - @property + @property def power(self): ports = self._get_active_ports() if len(ports) == 1: @@ -808,7 +808,7 @@ def power(self): else: return [self.get_port_power(p) for p in ports] - @power.setter + @power.setter def power(self, level): for p in self._get_active_ports(): self.set_port_power(p, level) @@ -825,30 +825,40 @@ def averaging_enable(self, value): else: self.interface.write(":SENSe1:AVERage:STATe OFF") - @property + @property def format(self): - meas = self.measurements.values() + meas = list(self.measurements.values()) self.interface.write(f"CALC:PAR:SEL {meas[0]}") return self._format_dict_inv[self.interface.query("CALC:FORM?")] - @format.setter + @format.setter def format(self, fmt): - if fmt not in self._format_dict.items() and fmt not in self._format_dict.keys(): + if fmt in self._format_dict.keys(): + pass + elif fmt in self._format_dict.values(): + fmt = self._format_dict_inv["fmt"] + else: raise ValueError(f"Unrecognized VNA measurement format specifier: {fmt}") + for meas in self.measurements.values(): - self.interface.write(f"CALC:PAR:SEL {meas[0]}") - self.interface.query("CALC:FORM? {fmt}") + self.interface.write(f"CALC:PAR:SEL {meas}") + self.interface.query(f"CALC:FORM? {fmt}") + + def autoscale(self): + nm = len(list(self.measurements.values())) + for j in range(nm): + self.interface.write(f'DISP:WIND1:TRAC{j+1}:Y:AUTO') - @property + @property def measurements(self): active_meas = self.interface.query("CALC:PAR:CAT?") meas = active_meas.strip('\"').split(",")[::2] spars = active_meas.strip('\"').split(",")[1::2] return {s: m for m, s in zip(meas,spars)} - @measurements.setter + @measurements.setter def measurements(self, S): - sp = [s.upper() for s in S] + sp = [s.upper() for s in S] for s in sp: if s not in self.valid_meas: raise ValueError(f"Invalid S-parameter measurement request: {s} is not in available measurements: {self.valid_meas}.") @@ -861,9 +871,10 @@ def measurements(self, S): self.interface.write("DISP:WIND1:STATE ON") for j, s in enumerate(sp): - self.interface.write(f'CALC:PAR:DEF:EXT "M_{s}",{s}') - self.interface.write(f'DISP:WIND1:TRAC{j} "M_{s}"') - self.interface.write(f'DISP:WIND1:TRAC{j}:Y:AUTO') + self.interface.write(f'CALC:PAR:DEF "M_{s}",{s}') + self.interface.write(f'DISP:WIND1:TRAC{j+1}:FEED "M_{s}"') + time.sleep(0.1) + self.interface.write(f'DISP:WIND1:TRAC{j+1}:Y:AUTO') self.interface.write('SENS1:SWE:TIME:AUTO ON') @@ -890,7 +901,7 @@ def get_trace(self, measurement=None): """ Return a tupple of the trace frequencies and corrected complex points """ #If the measurement is not passed in just take the first one if measurement is None: - mchan = self.measurements.values()[0] + mchan = list(self.measurements.values())[0] else: if measurement not in self.measurements.values(): raise ValueError(f"Unknown measurement: {measurement}. Available: {self.measurements.keys()}.") @@ -899,7 +910,7 @@ def get_trace(self, measurement=None): self.interface.write(":CALCulate:PARameter:SELect '{}'".format(mchan)) self.reaverage() self.interface.write(":CALC:DATA? SDATA") - block = self.interface.read_raw(size=256) + block = self.interface.read_raw(size=16) offset, data_length = util.parse_ieee_block_header(block) interleaved_vals = util.from_ieee_block(block, 'f', True, np.array) @@ -1105,14 +1116,12 @@ def get_pn_trace(self, num=3): def restart_sweep(self): """ Aborts current sweep and restarts. """ self.interface.write(":INITiate:RESTart") - + def peak_search(self, marker=1): self.interface.write(':CALC:MARK{:d}:MAX'.format(marker)) - + def marker_to_center(self, marker=1): self.interface.write(':CALC:MARK{:d}:CENT'.format(marker)) - + def clear_averaging(self): self.interface.write(':AVER:CLE') - - From 2c4080d116bb16b8d85a4740e32a17a656137704 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Mon, 28 Oct 2019 17:36:21 -0400 Subject: [PATCH 3/9] Fix data trace on new NA. --- src/auspex/instruments/agilent.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index c46a9ae2..9df3bef7 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -717,6 +717,8 @@ class _AgilentNetworkAnalyzer(SCPIInstrument): TIMEOUT = 10 * 1000. + data_query_raw = False + ports = () _port_powers = {} @@ -825,6 +827,10 @@ def averaging_enable(self, value): else: self.interface.write(":SENSe1:AVERage:STATe OFF") + def averaging_restart(self): + """ Restart trace averaging """ + self.interface.write(":SENSe1:AVERage:CLEar") + @property def format(self): meas = list(self.measurements.values()) @@ -877,6 +883,7 @@ def measurements(self, S): self.interface.write(f'DISP:WIND1:TRAC{j+1}:Y:AUTO') self.interface.write('SENS1:SWE:TIME:AUTO ON') + self.interface.write("SENS:SWE:MODE CONT") def reaverage(self): """ Restart averaging and block until complete """ @@ -897,29 +904,35 @@ def reaverage(self): if opc_bit == 1: meas_done = True + def _raw_query(self, string): + self.interface.write(string) + block = self.interface.read_raw(size=16) + offset, data_length = util.parse_ieee_block_header(block) + return util.from_ieee_block(block, 'f', True, np.array) + + def get_trace(self, measurement=None): """ Return a tupple of the trace frequencies and corrected complex points """ #If the measurement is not passed in just take the first one if measurement is None: mchan = list(self.measurements.values())[0] else: - if measurement not in self.measurements.values(): + if measurement not in self.measurements.keys(): raise ValueError(f"Unknown measurement: {measurement}. Available: {self.measurements.keys()}.") mchan = self.measurements[measurement] #Select the measurment self.interface.write(":CALCulate:PARameter:SELect '{}'".format(mchan)) self.reaverage() - self.interface.write(":CALC:DATA? SDATA") - block = self.interface.read_raw(size=16) - offset, data_length = util.parse_ieee_block_header(block) - interleaved_vals = util.from_ieee_block(block, 'f', True, np.array) - #Take the data as interleaved complex values - self.interface.write("SENS:SWE:MODE CONT") + if self.data_query_raw: + interleaved_vals = self._raw_query(":CALC:DATA? SDATA") + else: + interleaved_vals = self.interface.query_binary_values(':CALC:DATA? SDATA', datatype="f", is_big_endian=True) + self.interface.write("SENS:SWE:MODE CONT") vals = interleaved_vals[::2] + 1j*interleaved_vals[1::2] #Get the associated frequencies - freqs = np.linspace(self.frequency_start, self.frequency_stop, self.sweep_num_points) + freqs = np.linspace(self.frequency_start, self.frequency_stop, self.num_points) return (freqs, vals) From 7048eea8a20858eaab1f9ee5c1faa4f5bb4fdb7a Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Mon, 28 Oct 2019 18:03:22 -0400 Subject: [PATCH 4/9] Add docstrings and a few fixes. --- src/auspex/instruments/agilent.py | 189 +++++++----------------------- 1 file changed, 43 insertions(+), 146 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index 9df3bef7..6c3f4f91 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -713,20 +713,25 @@ def reference(self, ref=None): pass class _AgilentNetworkAnalyzer(SCPIInstrument): + """Base class for Agilent Vector network analyzers. + To use, a child class should declare at least a "ports" tuple which represent valid S-paramter ports on the + instrument. + """ + instrument_type = "Vector Network Analyzer" - TIMEOUT = 10 * 1000. + TIMEOUT = 10 * 1000 #Timeout for VISA commands + data_query_raw = False #Use low-level commands to parse block data transfer - data_query_raw = False + ports = () #Set of ports that the NA has. - ports = () _port_powers = {} - _format_dict = {"MLIN": "LINEAR", "MLOG": "LOGARITHMIC", "PHAS": "PHASE", "UPH": "UNWRAP PHASE", "REAL": "REAL", "IMAG": "IMAG", "POL": "POLAR", "SMIT": "SMITH", "SADM": "SMITH ADMITTANCE", "SWR": "SWR", "GDEL": "GROUP DELAY"} _format_dict_inv = {v: k for k, v in _format_dict.items()} + ##Basic SCPI commands. frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") @@ -768,16 +773,17 @@ def connect(self, resource_name=None, interface_type="VISA"): @property def output_enable(self): + """Get output mode of each VNA port.""" outp = {} for p in self.ports: - if self.interface.query(f'SOUR:POW{p}:MODE?') == "OFF": - outp[p] = False - else: - outp[p] = True + outp[p] = self.interface.query(f'SOUR:POW{p}:MODE?') return outp @output_enable.setter def output_enable(self, outp): + """Set output mode of each port. Input is a dictionary mapping port numbers to booleans. `False` corresponds to + the port being in `OFF` mode, while `True` corresponds to the port being in `AUTO` mode. + """ if isinstance(outp, dict): for k, v in self.outp.items(): val = "AUTO" if v else "OFF" @@ -788,6 +794,7 @@ def output_enable(self, outp): self.interface.write(f"SOUR:POW{p}:MODE {val}") def set_port_power(self, port, power): + """Set the output power (in dBm) of a specific port.""" assert port in self.ports, f"This VNA does not have port {port}!" minp = self._port_powers[port][0] maxp = self._port_powers[port][1] @@ -796,14 +803,17 @@ def set_port_power(self, port, power): self.interface.write(f"SOUR:POW{port} {power}") def get_port_power(self, port): + """Get the output power in dBm of a specific port.""" assert port in self.ports, f"This VNA does not have port {port}!" - return self.interface.query(f"SOUR:POW{port}?") + return float(self.interface.query(f"SOUR:POW{port}?")) def _get_active_ports(self): + """Get ports that are used for currently active measurements.""" return [int(m[-1]) for m in self.measurements.keys()] @property def power(self): + """Get the output power in dBm of all currently active mesurements.""" ports = self._get_active_ports() if len(ports) == 1: return self.get_port_power(ports[0]) @@ -812,16 +822,19 @@ def power(self): @power.setter def power(self, level): + """Get the output power in dBm of all currently active mesurements.""" for p in self._get_active_ports(): self.set_port_power(p, level) @property def averaging_enable(self): + """Get the averaging state.""" state = self.interface.query(":SENSe1:AVERage:STATe?") return bool(int(state)) @averaging_enable.setter def averaging_enable(self, value): + """Set the averaging state.""" if value: self.interface.write(":SENSe1:AVERage:STATe ON") else: @@ -833,30 +846,34 @@ def averaging_restart(self): @property def format(self): + """Get the currently active measurement format.""" meas = list(self.measurements.values()) self.interface.write(f"CALC:PAR:SEL {meas[0]}") - return self._format_dict_inv[self.interface.query("CALC:FORM?")] + return self._format_dict[self.interface.query("CALC:FORM?")] @format.setter def format(self, fmt): + """Set the currently active measurement format. See the `_format_dict` property for valid formats.""" if fmt in self._format_dict.keys(): pass elif fmt in self._format_dict.values(): - fmt = self._format_dict_inv["fmt"] + fmt = self._format_dict_inv[fmt] else: raise ValueError(f"Unrecognized VNA measurement format specifier: {fmt}") for meas in self.measurements.values(): self.interface.write(f"CALC:PAR:SEL {meas}") - self.interface.query(f"CALC:FORM? {fmt}") + self.interface.write(f"CALC:FORM {fmt}") def autoscale(self): + """Autoscale all traces.""" nm = len(list(self.measurements.values())) for j in range(nm): self.interface.write(f'DISP:WIND1:TRAC{j+1}:Y:AUTO') @property def measurements(self): + """Get currently active measurements and their trace names.""" active_meas = self.interface.query("CALC:PAR:CAT?") meas = active_meas.strip('\"').split(",")[::2] spars = active_meas.strip('\"').split(",")[1::2] @@ -864,6 +881,8 @@ def measurements(self): @measurements.setter def measurements(self, S): + """Set currently active measurements, passed as a list of S-parameters. This will overwrite measurements that + are currently active on the VNA.""" sp = [s.upper() for s in S] for s in sp: if s not in self.valid_meas: @@ -886,7 +905,7 @@ def measurements(self, S): self.interface.write("SENS:SWE:MODE CONT") def reaverage(self): - """ Restart averaging and block until complete """ + """ Restart averaging and block until complete.""" if self.averaging_enable: self.averaging_restart() #trigger after the requested number of points has been averaged @@ -904,15 +923,19 @@ def reaverage(self): if opc_bit == 1: meas_done = True - def _raw_query(self, string): + def _raw_query(self, string, size=16): + """Some Agilent VNAs are stupid and do not understand VISA query commands for large binary transfers. + Hack around this. The raw read size seems to be safe with 16 bytes per read but this might need to be changed? + """ self.interface.write(string) - block = self.interface.read_raw(size=16) + block = self.interface.read_raw(size=size) offset, data_length = util.parse_ieee_block_header(block) return util.from_ieee_block(block, 'f', True, np.array) def get_trace(self, measurement=None): - """ Return a tupple of the trace frequencies and corrected complex points """ + """ Return a tuple of the trace frequencies and corrected complex points. By default returns the data for the + first acive measurement. Pass an S-parameter (i.e. `S12`) as the `measurement` keyword argument to access others.""" #If the measurement is not passed in just take the first one if measurement is None: mchan = list(self.measurements.values())[0] @@ -935,141 +958,15 @@ def get_trace(self, measurement=None): freqs = np.linspace(self.frequency_start, self.frequency_stop, self.num_points) return (freqs, vals) - class AgilentN5230A(_AgilentNetworkAnalyzer): + """Agilent N5230A 4-port 20GHz VNA.""" ports = (1, 2, 3, 4) + data_query_raw = False class AgilentE8363C(_AgilentNetworkAnalyzer): + """Agilent E8363C 2-port 40GHz VNA.""" ports = (1, 2) - -# class AgilentE8363C(SCPIInstrument): -# """Agilent E8363C VNA""" -# instrument_type = "Vector Network Analyzer" - -# TIMEOUT = 10. * 1000. #milliseconds - -# power_port1 = FloatCommand(scpi_string=":SOURce:POWer1:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) -# power_port2 = FloatCommand(scpi_string=":SOURce:POWer2:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) -# frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") -# frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") -# frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") -# frequency_stop = FloatCommand(scpi_string=":SENSe:FREQuency:STOP") -# sweep_num_points = IntCommand(scpi_string=":SENSe:SWEep:POINts") -# averaging_factor = IntCommand(scpi_string=":SENSe1:AVERage:COUNt") -# averaging_enable = BoolCommand(get_string=":SENSe1:AVERage:STATe?", set_string=":SENSe1:AVERage:STATe {:s}", value_map={False: "0", True: "1"}) -# averaging_complete = StringCommand(get_string=":STATus:OPERation:AVERaging1:CONDition?", value_map={False:"+0", True:"+2"}) -# if_bandwidth = FloatCommand(scpi_string=":SENSe1:BANDwidth") -# sweep_time = FloatCommand(get_string=":SENSe:SWEep:TIME?") - -# def __init__(self, resource_name=None, *args, **kwargs): -# #If we only have an IP address then tack on the raw socket port to the VISA resource string -# super(AgilentE8363C, self).__init__(resource_name, *args, **kwargs) - -# def connect(self, resource_name=None, interface_type="VISA"): -# if resource_name is not None: -# self.resource_name = resource_name -# if is_valid_ipv4(self.resource_name): -# self.resource_name += "::hpib7,16::INSTR" -# else: -# logger.error("The resource name for the Agilent E8363C: {} is " + -# "not a valid IPv4 address.".format(self.resource_name)) -# super(AgilentE8363C, self).connect(resource_name=None, -# interface_type=interface_type) -# self.interface._resource._read_termination = u"\n" -# self.interface._resource.write_termination = u"\n" -# self.interface._resource.timeout = self.TIMEOUT -# self.interface._resource.chunk_size = 2 ** 20 # Needed to fix binary transfers (?) - -# self.interface.OPC() #wait for any previous commands to complete -# self.interface.write("SENSe1:SWEep:TIME:AUTO ON") #automatic sweep time -# self.interface.write("FORM REAL,32") #return measurement data as 32-bit float - -# def averaging_restart(self): -# """ Restart trace averaging """ -# self.interface.write(":SENSe1:AVERage:CLEar") - -# @property -# def averaging_enable(self): -# state = self.interface.query(":SENSe1:AVERage:STATe?") -# return bool(int(state)) - -# @averaging_enable.setter -# def averaging_enable(self, value): -# if value: -# self.interface.write(":SENSe1:AVERage:STATe ON") -# else: -# self.interface.write(":SENSe1:AVERage:STATe OFF") - -# @property -# def power(self): -# return self.power_port1 if self.measurement[-1] == '1' else self.power_port2 - -# @power.setter -# def power(self, value): -# if self.measurement[-1] == '1': -# self.power_port1 = value -# else: -# self.power_port2 = value - -# @property -# def measurement(self): -# meas = self.interface.query(":CALC:PAR:CAT?") -# return meas.strip('\"').split(",")[-1] - -# @measurement.setter -# def measurement(self, meas): -# meas = meas.upper() -# valid_meas = ('S11', 'S12', 'S21', 'S22') -# if meas not in valid_meas: -# raise ValueError(f"Unknown measurement {meas}; must be one of {valid_meas}") - -# self.interface.write("CALC:PAR:DEL:ALL") -# self.interface.write(f":CALC:PAR:DEF:EXT 'R_{meas}',{meas}") -# self.interface.write(f"DISP:WIND1:TRAC1:FEED 'R_{meas}'") - - -# def reaverage(self): -# """ Restart averaging and block until complete """ -# if self.averaging_enable: -# self.averaging_restart() -# #trigger after the requested number of points has been averaged -# self.interface.write("SENSe1:SWEep:GROups:COUNt %d"%self.averaging_factor) -# self.interface.write("ABORT; SENSe1:SWEep:MODE GRO") -# else: -# #restart current sweep and send a trigger -# self.interface.write("ABORT; SENS:SWE:MODE SING") - -# meas_done = False -# self.interface.write('*OPC') -# while not meas_done: -# time.sleep(0.5) -# opc_bit = int(self.interface.ESR()) & 0x1 -# if opc_bit == 1: -# meas_done = True - -# def get_trace(self, measurement=None): -# """ Return a tupple of the trace frequencies and corrected complex points """ -# #If the measurement is not passed in just take the first one -# if measurement is None: -# traces = self.interface.query(":CALCulate:PARameter:CATalog?") -# #traces come e.g. as u'"CH1_S11_1,S11,CH1_S21_2,S21"' -# #so split on comma and avoid first quote -# measurement = traces.split(",")[0][1:] -# #Select the measurment -# self.interface.write(":CALCulate:PARameter:SELect '{}'".format(measurement)) -# self.reaverage() -# self.interface.write(":CALC:DATA? SDATA") -# block = self.interface.read_raw(size=256) -# offset, data_length = util.parse_ieee_block_header(block) -# interleaved_vals = util.from_ieee_block(block, 'f', True, np.array) - -# #Take the data as interleaved complex values -# self.interface.write("SENS:SWE:MODE CONT") - -# vals = interleaved_vals[::2] + 1j*interleaved_vals[1::2] -# #Get the associated frequencies -# freqs = np.linspace(self.frequency_start, self.frequency_stop, self.sweep_num_points) -# return (freqs, vals) + data_query_raw = True class AgilentE9010A(SCPIInstrument): """Agilent E9010A SA""" From 16119a085b971f5fd1f613f16c2e29e5515575e5 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 29 Oct 2019 09:54:14 -0400 Subject: [PATCH 5/9] Only update conda on latest Miniconda for testing --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f28d15bd..fdf5a7c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ language: python python: - "3.6" env: - - CONDA_TYPE=miniconda CONDA_VERS=https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh - - CONDA_TYPE=miniconda CONDA_VERS=https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh + - CONDA_TYPE=miniconda CONDA_VERS=https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh UPDATE_CONDA="true" + - CONDA_TYPE=miniconda CONDA_VERS=https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh UPDATE_CONDA="" addons: apt: @@ -25,7 +25,7 @@ install: - hash -r - conda config --set always_yes yes --set changeps1 no - conda install -c anaconda setuptools - - conda update -q conda + - if [ $UPDATE_CONDA ]; then conda update -q conda; fi # Useful for debugging any issues with conda - conda info -a From adf809f9a1452ba2285e2c613c56c2f08830687c Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 29 Oct 2019 09:58:40 -0400 Subject: [PATCH 6/9] Hold back anaconda package on non-latest version --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fdf5a7c8..7e615b1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,10 @@ install: # - echo "Database file at $BBN_DB" - hash -r - conda config --set always_yes yes --set changeps1 no - - conda install -c anaconda setuptools + + - if [ $UPDATE_CONDA ]; then conda install -c anaconda; fi - if [ $UPDATE_CONDA ]; then conda update -q conda; fi + - conda install setuptools # Useful for debugging any issues with conda - conda info -a From f7691659d1185cbb67fae6779038cfd600abdb38 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 29 Oct 2019 10:07:04 -0400 Subject: [PATCH 7/9] Do not install setuptools --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e615b1d..caee0d22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,10 +24,7 @@ install: # - echo "Database file at $BBN_DB" - hash -r - conda config --set always_yes yes --set changeps1 no - - - if [ $UPDATE_CONDA ]; then conda install -c anaconda; fi - if [ $UPDATE_CONDA ]; then conda update -q conda; fi - - conda install setuptools # Useful for debugging any issues with conda - conda info -a From 739e945cb26b5efc1891c7b48ded3ee26599f47e Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 29 Oct 2019 14:14:39 -0400 Subject: [PATCH 8/9] Version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ccd579a2..002ea3c6 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ setup( name='auspex', - version='2019.1', + version='2019.2', author='Auspex Developers', package_dir={'':'src'}, packages=[ From 3306ee7a14845b7c549cd70e3a7bb0fccebb26c5 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 29 Oct 2019 17:16:24 -0400 Subject: [PATCH 9/9] Doc tweaks for new version and nbsphinx oddity --- doc/conf.py | 6 ++-- ...xample.ipynb => Example-APS2-2Qubit.ipynb} | 34 +++++++++---------- doc/qubits.rst | 1 + 3 files changed, 22 insertions(+), 19 deletions(-) rename doc/examples/{QubitExperiments/APS2-2Q-Example.ipynb => Example-APS2-2Qubit.ipynb} (98%) diff --git a/doc/conf.py b/doc/conf.py index 4a7cbd45..2efd6d6f 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -44,6 +44,8 @@ 'sphinx.ext.napoleon', ] +nbsphinx_execute = 'never' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -70,9 +72,9 @@ # built documents. # # The short X.Y version. -version = '0.8' +version = '2019.2' # The full version, including alpha/beta/rc tags. -release = '0.8' +release = '2019.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/examples/QubitExperiments/APS2-2Q-Example.ipynb b/doc/examples/Example-APS2-2Qubit.ipynb similarity index 98% rename from doc/examples/QubitExperiments/APS2-2Q-Example.ipynb rename to doc/examples/Example-APS2-2Qubit.ipynb index 4aec4797..d62fe145 100755 --- a/doc/examples/QubitExperiments/APS2-2Q-Example.ipynb +++ b/doc/examples/Example-APS2-2Qubit.ipynb @@ -1,5 +1,15 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example Q8: Realistic Two Qubit Tuneup and Experiments\n", + "This example notebook shows how to use APS2/X6 ecosystem to tune up a pair of qubits.\n", + "\n", + "© Raytheon BBN Technologies 2019" + ] + }, { "cell_type": "code", "execution_count": null, @@ -116,9 +126,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "pl = PipelineManager()\n", @@ -241,9 +249,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "pf = PulsedSpec(qb, specOn=True)\n", @@ -466,9 +472,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "pf = RabiWidth(q2, np.arange(20e-9, 0.602e-6, 10e-9))\n", @@ -1073,9 +1077,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "crlens = np.arange(100e-9,2.1e-6,100e-9)\n", @@ -1123,9 +1125,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "phases = np.arange(0,np.pi*2,np.pi/20)\n", @@ -1211,7 +1211,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.3" }, "latex_envs": { "bibliofile": "biblio.bib", @@ -1232,5 +1232,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/doc/qubits.rst b/doc/qubits.rst index 1e03dcf3..d0ad0fb5 100644 --- a/doc/qubits.rst +++ b/doc/qubits.rst @@ -29,6 +29,7 @@ The best way to gain experience is to follow through with these tutorials: Q5 Tutorial: Adding Sweeps to Experiments Q6 Tutorial: Pulse Calibration Q7 Tutorial: Single Shot Fidelity + Q8 Tutorial: Realistic Two Qubit Tuneup and Experiments Instrument Drivers ******************