An example of refactoring code to make it shareable and ready to attach to a FrontEnd

This notebook is a set of thoughts and steps to refactor code to make it more shareable. It is based on Aaron Hagerstom's FreqSweepVNA8.py module. The process of refactoring is essentially one of renaming and clarifying logic so that the file can easily be used in multiple contexts.Written by Aric Sanders, 09/2016

Table of Contents


  1. Module Header and Documentation

  2. Refactoring Import Statements

  3. Constants

  4. Defining Functions For Reuse

  5. Class Definition

  6. Script Definition

  7. Module Runner

  8. Orignal Module

  9. Refactored Module

  10. More Information

  11. </ol>


back to top


Module Header and Documentation

Purpose: To clarify to the reader of your code who wrote it and why and provide an overview for automaticly generated help. I typically put 2 elements here, a commented header for readers of code and a module doc string (just the first string in the module). The orginal had nothing here, so I will "add it".

In [2]:
#-----------------------------------------------------------------------------
# Name:        VNAExperiments
# Purpose:     To measure frequency swept data using a spectrum analyzer, vector network
# analyzer and a signal generator
# Author:      Aaron Hagerstrom
# Created:     9/13/2016
# License:     MIT License* Aaron you should check which license you want
#-----------------------------------------------------------------------------
""" VNAExperiments holds classes and functions important for a frequency swept measurement using a 
Vector Network Analyzer and a Signal Generator"""
Out[2]:
' VNAExperiments holds classes and functions important for a frequency swept measurement using a \nVector Network Analyzer and a Signal Generator'

Notes: Using 3 quotation marks """ allows you make multiline comments. The header block is really only for people who actually read the code, not ones that would import your module. By adding a license, you make it legal for others to use your code, I have started using MIT because it is closest to what I want. Look here to decide https://opensource.org/licenses if you want an open source license.

back to top


Refactoring Import Statements

Purpose: To import libraries needed in the body of the module. If a module is in the standard library and should work then just import lib, but if the library requires installation use a try: except: clause and put a message there that tells the user at least what library or module failed. This will keep people from being stuck if they have a different distribution or none at all

In [3]:
#-----------------------------------------------------------------------------
# Standard Imports
import os,os.path
import random
import struct
#-----------------------------------------------------------------------------
# Third Party Imports
try:
    import pylab as pl
except:
    print("The package pylab failed to load, please resolve the error or put it on the python path (pip install pylab) or"
          " http://scipy.org/")
try:
    import numpy as np
except:
    print("The package numpy failed to load, please resolve the error or put it on the python path (pip install numpy) or"
          " http://scipy.org/")
try:
    import visa
except:
    print("The package visa failed to load, please resolve the error or put it on the python path (pip install pyvisa) or"
          "https://pyvisa.readthedocs.io/en/stable/getting.html")

back to top


Constants

Purpose: To define global constants for the library, these should not be something that a user would normally interact with. If it will probaly be changed down the road then don't put it here. I have the naming convention of making them all UPPERCASE_WITH_UNDERSCORES (this is the official python reccomendation)

In [4]:
# These probably change once in 10 years so it is okay to put them here. Note they can change so this is just the default
VNA_DEFAULT_ADDRESS=u'GPIB0::24::INSTR'
SPECTRUM_ANALYZER_DEFAULT_ADDRESS=u'GPIB0::18::INSTR'
SIGNAL_GENERATOR_DEFAULT_ADDRESS=u'GPIB0::17::INSTR'

back to top


Defining functions for reuse.

Purpose: To create a set of functions that are usefull in multiple places in your code or to users. Functions should be stateless, that is they should take a set number of inputs and return the output only based on those inputs. If a function requires extra information (such as the state of the instruments) it should be a method ( a function attached to a class)

In [ ]:
# Create a function to do the organization currently in the script
def parse_measurements(measurement_list):
    """Given a list of measurements
    returns a dictionary of the form {measurement.name:{frequency_list:[],peak_value_list:[]}}"""
    output_dictionary={}
    for index,measurement in enumerate(measurement_list):
        if measurement.name in output_dictionary.keys():
            output_dictionary[measurement.name]["frequency_list"].append(measurement.frequency)
            output_dictionary[measurement.name]["peak_value_list"].append(measurement.peak_value)
        else:
            output_dictionary[measurement.name]={"frequency_list":[],"peak_value_list":[]}
            output_dictionary[measurement.name]["frequency_list"].append(measurement.frequency)
            output_dictionary[measurement.name]["peak_value_list"].append(measurement.peak_value)
    return output_dictionary

def plot_measurements(parsed_measurement_dictionary,**options):
    """Plots all measurements that have been parsed using parse_measurements, passing **options to the plot"""
    # magic that will pass plot options in case you want to change them, it takes out the options we want 
    defaults={"marker":'o',"linestyle":" ","display_legend":True,"save_figure":True,"directory":None}
    plot_options={}
    for key,value in defaults.iteritems():
        if key not in ["display_legend","save_figure","directory"]:
            plot_options[key]=value
    for key,value in options.iteritems():
        if key not in ["display_legend","save_figure","directory"]:
            plot_options[key]
    # this will plot everything 
    pl.clf()
    for name,measurement_dictionary in parsed_measurement_dictionary.iteritems():
        pl.plot(measurement_dictionary["frequency_list"],measurement_dictionary["peak_value_list"],label=name,**plot_options)
    if plot_options["display_legend"]:
        pl.legend()
    pl.ylabel("Power (dBm)")
    pl.xlabel("Frequency (GHz)")
    if plot_options["directory"] is None:
        plot_options["directory"]=os.getcwd()
    try: 
        if plot_options["save_figure"]:
            pl.savefig(os.path.join(plot_options["directory"],"peaks.png"))
    except IOError as e:
        print(" Windows is stupid, {0} ".format(e))

def build_measurement_list(measurement_frequencies,base_name="",**measurement_settings):
    """Returns a list of measurements given a list of frequencies [[VNA_frequency,signal_generator_frequency,
    measurement_frequency] ...], a base name and the other measurement settings"""
    out_list=[]
    for index,row in measurement_frequencies:
        new_measurement=VNAMeasurementData(**measurement_settings)
        new_measurement.VNA_frequency=row[0]
        new_measurement.signal_generator_frequency=row[1]  
        new_measurement.signal_generator_frequency=row[1]
        new_measurement.name="sumone"+str(index)
        new_measurement.text_file_name=base_name+str(index)+".txt"
        new_measurement.plot_file_name=base_name+str(index)+".png"
        out_list.append(new_measurement)
    return out_list
def save_summary(measurement_list):
    """Saves a summary of all the measurements in measurement_list"""
    summary_file=open(measurement_list[0].directory+"summary.txt",'w')
    for measurement in measurement_list:
        text=self.perform_measurement(measurement)
        summary_file.write(text)
    summary_file.close()
            

back to top


Class Definition

Purpose: To create a set of classes that are usefull in multiple places in your code or to users.Classes are persistent objects that have a state. The state is affected by the methods, and is represented by the attributes (self.attributes). They can be inherited and are often very useful in UI design, as most UI's have a state that is changed by the user during the course of using the program. I have split the classes in this module into a data class (VNAMeasurementData) and a control class (VNAFrequencySweepExperiment) to keep the logic between data and control a little more separated.

In [9]:
class VNAMeasurementData():
    """VNAMeasurementData holds the data for a single measurement """
    def __init__(self,**measurement_data):
        """Intializes the VNAMeasurementData class. When created, data can be passed as a dictionary
        VNAMeasurementData(**{"VNA_power":0})
        or as keyword arguments VNAMeasurementData(VNA_power=0)"""
        defaults={"directory":None,"record_name":"measurement","text_file_name":"out.txt",
                 "plot_file_name":"out.png","VNA_power":0,"signal_generator_power":-10,"power_units":"dBm",
                 "VNA_frequency":10,"signal_generator_frequency":10,"measurement_frequency":10,"frequency_units":"GHz",
                  "frequency_span_units":"Hz","save_plot":True,
                  "save_text":True,"reference_level":0,"peak_value":"Not Measured","frequency_span":100}
        self.initial_values={}
        for key,value in defaults.iteritems():
            self.initial_values[key]=value
        for key,value in measurement_data.iteritems():
            self.initial_values[key]=value
        # now make the values in self.intial_values attributes
        for key,value in self.initial_values.iteritems():
            self.__dict__[key]=value
        try:
            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)
        except:
            print("could not calculate resonant bandwidth from frequency span")
            
    # in general methods and functions should have verbs as names so instead of summary we write get_summary
    def get_summary(self,summary_elements=None):
        """Returns a text summary of the measurement data, if you need them in a different order pass a list of 
        attribute names ["name","text_file_name",...]"""
        if summary_elements==None:
            summary_order=["name","test_file_name","plot_file_name","VNA_frequency",
                           "signal_generator_frequency","measurement_frequency","resonator_bandwidth",
                          "frequency_span","signal_generator_power","VNA_power","reference_level","peak_value"]
        output_string=""
        for index,element in enumerate(summary_elements):
            if index == 0:
                output_string+="{1} : {0}".format(element,self.__dict__[element])
            else:
                output_string+=",{1} : {0}".format(element,self.__dict__[element])
        return output_string
    def __str__(self):
        """Controls the behavior when str(VNAMeasurmentData) or print is called"""
        return self.get_summary()
    def reset_measurement_data(self):
        """Restores to the initialized value"""
        for key,value in self.initial_values.iteritems():
            self.__dict__[key]=value
        try:
            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)
        except:
            print("could not calculate resonant bandwidth from frequency span")
            
class FakeInstrument():
    """FakeInstrument is a class for testing purposes"""
    def __init__(self,instrument_address):
        self.instrument_address=instrument_address
        pass
    def write(self,command):
        """Prints a write statement"""
        print("Writing the command {0} to {1}".format(command,self.instrument))
    def read(self):
        return 0
    def query(self,command):
        """Prints a write statement and returns a fake value"""
        print("Writing the command {0} to {1}".format(command,self.instrument))
        return 0
    def close(self):
        print("Writing the command {0} to {1}".format("close()",self.instrument))
    def clear(self):
        print("The instrument {0} was cleared".format(self.instrument))
        
class VNAFrequencySweepExperiment():
    """VNAFrequencySweepExperiment controls the VNA, signal generator and spectrum analyzer"""
    def __init__(self,**set_up):
        """Intializes the VNAFrequencySweepExperimentClass, if the gpib address of the instruments are not the default
        pass them as VNA_address=u'GPIB0::24::INSTR',spectrum_analyzer_address=u'GPIB0::18::INSTR',
        signal_generator_address=u'GPIB0::17::INSTR' or 
        **{'VNA_address':u'GPIB0::24::INSTR','spectrum_analyzer_address':u'GPIB0::18::INSTR',
        'signal_generator_address':u'GPIB0::17::INSTR'} """
        defaults={'VNA_address':VNA_DEFAULT_ADDRESS,'spectrum_analyzer_address':SPECTRUM_ANALYZER_DEFAULT_ADDRESS,
        'signal_generator_address':SIGNAL_GENERATOR_DEFAULT_ADDRESS}
        self.instrument_addresses={}
        for key,value in defaults.iteritems():
            self.instrument_addresses[key]=value
        for key,value in setup.iteritems():
            self.instrument_addresses[key]=value
        self.measurement_list=[]
        try:
            self.resource_manager=visa.ResourceManager()       
            self.open_instruments()
        except:
            print("Could not open the resources, entering testing mode all data is fake")
            self.VNA=self.resource_manager.open_resource(self_instrument_addresses["VNA_Address"])
            self.signal_generator=FakeInstrument(self_instrument_addresses["signal_generator_address"])
            self.spectrum_analyzer=FakeInstrument(self_instrument_addresses["spectrum_analyzer_address"])
            
        
    def open_instruments(self):
        """Opens the instruments and creates the attributes VNA, signal_generator and spectrum_analyzer"""
        self.VNA=self.resource_manager.open_resource(self_instrument_addresses["VNA_Address"])
        self.signal_generator=self.resource_manager.open_resource(self_instrument_addresses["signal_generator_address"])
        self.spectrum_analyzer=self.resource_manager.open_resource(self_instrument_addresses["spectrum_analyzer_address"])
        
        
    def intialize_frequency_sweep(self):
        #clear input and output buffers
        self.spectrum_analyzer.clear()
        self.signal_generator.clear()
        self.VNA.clear()
        #initialize to preset state
        self.spectrum_analyzer.write("IP")
        self.signal_generator.write("*RST")
        self.VNA.write("SYST:PRESET")
        #single sweep mode on spectrum analyzer
        self.spectrum_analyzer.write("SNGLS;")
        #set no timeout
        self.spectrum_analyzer.timeout=float('+inf')
        #set binary trace output
        self.spectrum_analyzer.write("TDF B;")
        #set VNA to CW mode
        self.VNA.write('SENS1:SWE:TYPE CW')
        #set VNA sweep time to 1 hour to make sure source is on during measurement
        self.VNA.write("SENS1:SWE:TIME 3600")
        
    def perform_measurement(self,measurement):
        """Performs a single measurement"""
        #sets source to power dBm and frequency cw in GHz.
        #saves a textfile with txtfname
        #if enabled,saves a plot with plot_file_name
        #returns peak val
        #set span
        self.spectrum_analyzer.write("SP "+str(measurement.frequency_span)+"HZ;")
        #set resolution bandwidth
        self.spectrum_analyzer.write("RB "+str(measurement.resonant_bandwidth))
        #set reference level
        self.spectrum_analyzer.write("RL "+str(measurement.reference_level))
        #measurement center frequency
        self.spectrum_analyzer.write("CF "+str(measurement.measurement_frequency)+"GHZ;")
        #set up generator
        self.signal_generator.write("POW:AMPL "+str(measurement.signal_generator_power)+";")
        self.signal_generator.write("FREQ:CW "+str(measurement.signal_generator_frequency)+"E+9;") 
        self.signal_generator.write("OUTP 1;")
        #set up VNA
        self.VNA.write("SOUR1:POW "+str(measurement.VNA_power))
        self.VNA.write("SENS1:FREQ "+str(measurement.VNA_frequency)+"e9")
        #measure a sweep
        #instruments.SA.write("VAVG 10;")
        #meastime=float(instruments.SA.query("ST?"))+2
        self.spectrum_analyzer.write("TS;")
        #aquire a trace
        self.spectrum_analyzer.write("TRA?;")
        data=self.spectrum_analyzer.read_raw()
        dataint= np.array(struct.unpack('>' + 'H'*601, data))
        #specify vertical scale
        RL=float(self.spectrum_analyzer.query("RL?"))
        dBperDev=float(self.spectrum_analyzer.query("LG?"))
        #rescale binary data
        datadB=RL+dBperDev*(dataint/60.0-10)
        #find span 
        FA=float(self.spectrum_analyzer.query('FA?'))
        FB=float(self.spectrum_analyzer.query('FB?'))
        frequency=np.linspace(FA,FB,601)
        if measurement.save_text:
            f=open(measurement.directory+measurement.text_file_name,'w')
            for i in range(len(datadB)):
                f.write(str(frequency[i])+" "+str(dataint[i])+" "+str(datadB[i])+"\n")
            f.close()
        if measurement.save_plot:
            pl.clf()
            pl.plot(frequency-measurement.measurement_frequency*1e9,datadB)
            pl.xlim([FA-measurement.measurement_frequency*1e9,FB-measurement.measurement_frequency*1e9])
            pl.ylim([RL-120,RL])
            pl.ylabel("power (dBm)")
            pl.xlabel("Frequency offset (Hz)")
            pl.savefig(measurement.directory+measurement.plot_file_name)
        measurement.peak_value=np.max(datadB)          
        #return measurement
                                    
    def shuffle_measurement_list(self):
        """Randomly shuffles current measurement list"""
        self.measurement_list=random.shuffle(self.measurement_list)

    def perform_all_measurements(self,save_all_text=True,plot_all=True):
        """Performs all measurements in measurement list"""
        for measurement in self.measurement_list:
            self.perform_measurement(measurement)
            
    def close(self):
        """Close all"""
        self.VNA.close()
        self.signal_generator.close()
        self.spectrum_analyzer.close()
        
    def save_summary(self):
        """Saves a summary of all the measurements in measurement_list"""
        save_summary(self.measurement_list)

                                    
                                    
    

back to top


Script Definition

Purpose: To create a set of functions that perform an action line by line. If you define it as a function, then it must be added in the module runner.

In [ ]:
def measure_two_frequency_script(signal_frequency=13.5,directory=None):
    """Sets the signal_frequency to 13.5 and measures"""
    if directory is None:
        directory="C:\\Users\\hightc\\Desktop\\20160422 tuning 150k\\0p3125VG\\"
    if os.path.isfile(directory+"summary.txt"):
        print "file summary.txt exists."
        raise SystemExit
    #Open the experiment
    Experiment=VNAFrequencySweepExperiment()
    #Intitalize the instruments
    Experiment.initialize_instruments()
    #create tunning and second frequencies 
    tuning_frequencies=np.linspace(0,13.25,54)[1::]
    second_frequencies=np.hstack(np.linspace(0,6.5,27)[1::])
    #create measurement lists
    sum_one=[[frequency,signal_frequency,frequency+signal_frequency] index,frequency in enumerate(tuning_frequencies)]
    measurement_sum_one=build_measurement_list(sum_one,base_name="sumone",**{})

    
    

back to top


Module Runner

Purpose: To create a module runner that calls the script of our choice.

In [ ]:
if __name__ == '__main__':
    measure_two_frequency_script()
import visa
import pylab as pl
import numpy as np
import struct
import os.path
import random

class measurement:
    def __init__(self,VNA=None,SA=None,GEN=None):            
        self.VNA=VNA
        self.SA=SA
        self.GEN=GEN

        self.savepath=None
        self.recordname="measurement"
        self.txtfname="out.txt"
        self.plotfname="out.png"

        self.VNApower=0 #dBm
        self.GENpower=-10 #dBm

        self.VNAf=10.0 #frequency of source in GHz
        self.GENf=10.0  #frequency to measure in GHz
        self.measf=10.0 #measuement frequency in GHz

        self.saveplot=True
        self.savetxt=True

        self.refLVL=0
        self.peakval="not measured"
        self.fspan=100 #Hz
        self.resBW=np.ceil(self.fspan/600.0) #Hz

    #sets source to power dBm and frequency cw in GHz.
    #saves a textfile with txtfname
    #if enabled,saves a plot with plotfname
    #returns peak val
    def perform(self):
        #set span
        self.SA.write("SP "+str(self.fspan)+"HZ;")
        #set resolution bandwidth
        self.SA.write("RB "+str(self.resBW))
        #set reference level
        self.SA.write("RL "+str(self.refLVL))
        #measurement center frequency
        self.SA.write("CF "+str(self.measf)+"GHZ;")

        #set up generator
        self.GEN.write("POW:AMPL "+str(self.GENpower)+";")
        self.GEN.write("FREQ:CW "+str(self.GENf)+"E+9;") 
        self.GEN.write("OUTP 1;")

        #set up VNA
        VNA.write("SOUR1:POW "+str(self.VNApower))
        VNA.write("SENS1:FREQ "+str(self.VNAf)+"e9")

        #measure a sweep
        #instruments.SA.write("VAVG 10;")
        #meastime=float(instruments.SA.query("ST?"))+2
        self.SA.write("TS;")
        #aquire a trace
        self.SA.write("TRA?;")

        data=SA.read_raw()
        dataint= np.array(struct.unpack('>' + 'H'*601, data))

        #specify vertical scale
        RL=float(SA.query("RL?"))
        dBperDev=float(SA.query("LG?"))

        #rescale binary data
        datadB=RL+dBperDev*(dataint/60.0-10)

        #find span 
        FA=float(SA.query('FA?'))
        FB=float(SA.query('FB?'))

        frequency=np.linspace(FA,FB,601)
        if self.savetxt:
            f=open(self.savepath+self.txtfname,'w')
            for i in range(len(datadB)):
                f.write(str(frequency[i])+" "+str(dataint[i])+" "+str(datadB[i])+"\n")
            f.close()

        if self.saveplot:
            pl.clf()
            pl.plot(frequency-self.measf*1e9,datadB)
            pl.xlim([FA-self.measf*1e9,FB-self.measf*1e9])
            pl.ylim([RL-120,RL])
            pl.ylabel("power (dBm)")
            pl.xlabel("Frequency offset (Hz)")
            pl.savefig(self.savepath+self.plotfname)

        self.peakval=np.max(datadB)  

    def summary(self):
        retstr="name "+str(self.name)
        retstr+=",datafile "+str(self.txtfname)
        retstr+=",plotname "+ str(self.plotfname)
        retstr+=",VNAFreq "+  str(self.VNAf)+" GHz"
        retstr+=",GENfreq "+  str(self.GENf)+" GHz"
        retstr+=",measfreq "+str(self.measf)+" GHz"
        retstr+=",resBw "+str(self.resBW)+" Hz"
        retstr+=",span "+str(self.fspan)+" Hz"
        retstr+=",gen pow: "+str(self.GENpower)+" dBm" 
        retstr+=",VNA pow: "+str(self.VNApower)+" dBm"         
        retstr+=",ref lvl: "+str(self.refLVL)+" dBm" 
        retstr+=",peak val: "+str(self.peakval)+" dBm\n"
        return retstr

savepath="C:\\Users\\hightc\\Desktop\\20160422 tuning 150k\\0p3125VG\\"
if os.path.isfile(savepath+"summary.txt"):
    print "file summary.txt exists."
    raise SystemExit

# set up instruments
rm=visa.ResourceManager()
SA=rm.open_resource(u'GPIB0::24::INSTR')
GEN=rm.open_resource(u'GPIB0::18::INSTR')
VNA=rm.open_resource(u'GPIB0::17::INSTR')

#clear input and output buffers
SA.clear()
GEN.clear()
VNA.clear()

#initialize to preset state
SA.write("IP")
GEN.write("*RST")
VNA.write("SYST:PRESET")

#single sweep mode on spectrum analyzer
SA.write("SNGLS;")

#set no timeout
SA.timeout=float('+inf')

#set binary trace output
SA.write("TDF B;")

#set VNA to CW mode
VNA.write('SENS1:SWE:TYPE CW')

#set VNA sweep time to 1 hour to make sure source is on during measurement
VNA.write("SENS1:SWE:TIME 3600")

measurements=[]

tuningFreqs=np.linspace(0,13.25,54)[1::]
SecondFreqs=np.hstack(np.linspace(0,6.5,27)[1::])

sigf=13.25
for i,tf in enumerate(tuningFreqs):
    if sigf+tf<50:
        mdummy=measurement(VNA,SA,GEN)      
        mdummy.savepath=savepath
        mdummy.refLVL=-50
        mdummy.VNApower=-10 #dBm
        mdummy.GENpower=8  #dBm
        mdummy.saveplot=False
        mdummy.name="sumone"+str(i)
        mdummy.txtfname="sumone"+str(i)+".txt"
        mdummy.plotfname="sumone"+str(i)+".png"
        mdummy.measf=tf+sigf
        mdummy.VNAf=tf
        mdummy.GENf=sigf
        measurements.append(mdummy)

for i,tf in enumerate(tuningFreqs):
    mdummy=measurement(VNA,SA,GEN)      
    mdummy.savepath=savepath
    mdummy.refLVL=-50
    mdummy.VNApower=-10 #dBm
    mdummy.GENpower=8  #dBm
    mdummy.saveplot=False
    mdummy.name="diffone"+str(i)
    mdummy.txtfname="diffone"+str(i)+".txt"
    mdummy.plotfname="diffone"+str(i)+".png"
    mdummy.measf=np.abs(tf-sigf)
    mdummy.VNAf=tf
    mdummy.GENf=sigf
    measurements.append(mdummy)

for i,tf in enumerate(SecondFreqs):
    mdummy=measurement(VNA,SA,GEN)      
    mdummy.savepath=savepath
    mdummy.refLVL=-80
    mdummy.VNApower=-10 #dBm
    mdummy.GENpower=8  #dBm
    mdummy.saveplot=False
    mdummy.name="difftwo"+str(i)
    mdummy.txtfname="difftwo"+str(i)+".txt"
    mdummy.plotfname="difftwo"+str(i)+".png"
    mdummy.measf=np.abs(-2.0*tf+sigf)
    mdummy.VNAf=tf
    mdummy.GENf=sigf
    measurements.append(mdummy)

for i,tf in enumerate(SecondFreqs):
    mdummy=measurement(VNA,SA,GEN)      
    mdummy.savepath=savepath
    mdummy.refLVL=-80
    mdummy.VNApower=-10 #dBm
    mdummy.GENpower=8  #dBm
    mdummy.saveplot=False
    mdummy.name="sumtwo"+str(i)
    mdummy.txtfname="sumtwo"+str(i)+".txt"
    mdummy.plotfname="sumtwo"+str(i)+".png"
    mdummy.measf=np.abs(2.0*tf+sigf)
    mdummy.VNAf=tf
    mdummy.GENf=sigf
    measurements.append(mdummy)    

diff1data=[]
diff1fdata=[]

diff2data=[]
diff2fdata=[]

sum1data=[]
sum1fdata=[]

sum2data=[]
sum2fdata=[]

random.shuffle(measurements)
f=open(savepath+"summary.txt",'w')
for i, meas in enumerate(measurements):
    meas.perform()
    text=meas.summary()
    if "diffone" in meas.name:
        diff1data.append(meas.peakval)
        diff1fdata.append(meas.measf)

    if "sumone" in meas.name:
        sum1data.append(meas.peakval)
        sum1fdata.append(meas.measf)

    if "difftwo" in meas.name:
        diff2data.append(meas.peakval)
        diff2fdata.append(meas.measf)

    if "sumtwo" in meas.name:
        sum2data.append(meas.peakval)
        sum2fdata.append(meas.measf)

    pl.clf()
    pl.plot(sum1fdata,sum1data,marker='o',linestyle=" ",label="sum 1")
    pl.plot(diff1fdata,diff1data,marker='o',linestyle=" ",label="diff 1")
    pl.plot(sum2fdata,sum2data,marker='o',linestyle=" ",label="sum 2")
    pl.plot(diff2fdata,diff2data,marker='o',linestyle=" ",label="diff 2")

    pl.legend()
    pl.xlabel("Power (dBm)")
    pl.xlabel("Frequency (GHz)")
    try:
        pl.savefig(savepath+"peaks.png")
    except IOError as e:
        print "windows is stupid", e

    print "measurement ", i, " of ",len(measurements)
    #print text
    f.write(text)

f.close()
SA.close()
GEN.close()
VNA.close()
#-----------------------------------------------------------------------------
# Name:        VNAExperiments
# Purpose:     To measure frequency swept data using a spectrum analyzer, vector network
# analyzer and a signal generator
# Author:      Aaron Hagerstrom
# Created:     9/13/2016
# License:     MIT License* Aaron you should check which license you want
#-----------------------------------------------------------------------------
""" VNAExperiments holds classes and functions important for a frequency swept measurement using a 
Vector Network Analyzer and a Signal Generator"""
#-----------------------------------------------------------------------------
# Standard Imports
import os,os.path
import random
import struct
#-----------------------------------------------------------------------------
# Third Party Imports
try:
    import pylab as pl
except:
    print("The package pylab failed to load, please resolve the error or put it on the python path (pip install pylab) or"
          " http://scipy.org/")
try:
    import numpy as np
except:
    print("The package numpy failed to load, please resolve the error or put it on the python path (pip install numpy) or"
          " http://scipy.org/")
try:
    import visa
except:
    print("The package vise failed to load, please resolve the error or put it on the python path (pip install pyvisa) or"
          "https://pyvisa.readthedocs.io/en/stable/getting.html")
#-----------------------------------------------------------------------------
# Function Definitions

# Create a function to do the organization currently in the script
def parse_measurements(measurement_list):
    """Given a list of measurements
    returns a dictionary of the form {measurement.name:{frequency_list:[],peak_value_list:[]}}"""
    output_dictionary={}
    for index,measurement in enumerate(measurement_list):
        if measurement.name in output_dictionary.keys():
            output_dictionary[measurement.name]["frequency_list"].append(measurement.frequency)
            output_dictionary[measurement.name]["peak_value_list"].append(measurement.peak_value)
        else:
            output_dictionary[measurement.name]={"frequency_list":[],"peak_value_list":[]}
            output_dictionary[measurement.name]["frequency_list"].append(measurement.frequency)
            output_dictionary[measurement.name]["peak_value_list"].append(measurement.peak_value)
    return output_dictionary

def plot_measurements(parsed_measurement_dictionary,**options):
    """Plots all measurements that have been parsed using parse_measurements, passing **options to the plot"""
    # magic that will pass plot options in case you want to change them, it takes out the options we want 
    defaults={"marker":'o',"linestyle":" ","display_legend":True,"save_figure":True,"directory":None}
    plot_options={}
    for key,value in defaults.iteritems():
        if key not in ["display_legend","save_figure","directory"]:
            plot_options[key]=value
    for key,value in options.iteritems():
        if key not in ["display_legend","save_figure","directory"]:
            plot_options[key]
    # this will plot everything 
    pl.clf()
    for name,measurement_dictionary in parsed_measurement_dictionary.iteritems():
        pl.plot(measurement_dictionary["frequency_list"],measurement_dictionary["peak_value_list"],label=name,**plot_options)
    if plot_options["display_legend"]:
        pl.legend()
    pl.ylabel("Power (dBm)")
    pl.xlabel("Frequency (GHz)")
    if plot_options["directory"] is None:
        plot_options["directory"]=os.getcwd()
    try: 
        if plot_options["save_figure"]:
            pl.savefig(os.path.join(plot_options["directory"],"peaks.png"))
    except IOError as e:
        print(" Windows is stupid, {0} ".format(e))

def build_measurement_list(measurement_frequencies,base_name="",**measurement_settings):
    """Returns a list of measurements given a list of frequencies [[VNA_frequency,signal_generator_frequency,
    measurement_frequency] ...], a base name and the other measurement settings"""
    out_list=[]
    for index,row in measurement_frequencies:
        new_measurement=VNAMeasurementData(**measurement_settings)
        new_measurement.VNA_frequency=row[0]
        new_measurement.signal_generator_frequency=row[1]  
        new_measurement.signal_generator_frequency=row[1]
        new_measurement.name="sumone"+str(index)
        new_measurement.text_file_name=base_name+str(index)+".txt"
        new_measurement.plot_file_name=base_name+str(index)+".png"
        out_list.append(new_measurement)
    return out_list
def save_summary(measurement_list):
    """Saves a summary of all the measurements in measurement_list"""
    summary_file=open(measurement_list[0].directory+"summary.txt",'w')
    for measurement in measurement_list:
        text=self.perform_measurement(measurement)
        summary_file.write(text)
    summary_file.close()
#-----------------------------------------------------------------------------
# Class Definitions    

class VNAMeasurementData():
    """VNAMeasurementData holds the data for a single measurement """
    def __init__(self,**measurement_data):
        """Intializes the VNAMeasurementData class. When created, data can be passed as a dictionary
        VNAMeasurementData(**{"VNA_power":0})
        or as keyword arguments VNAMeasurementData(VNA_power=0)"""
        defaults={"directory":None,"record_name":"measurement","text_file_name":"out.txt",
                 "plot_file_name":"out.png","VNA_power":0,"signal_generator_power":-10,"power_units":"dBm",
                 "VNA_frequency":10,"signal_generator_frequency":10,"measurement_frequency":10,"frequency_units":"GHz",
                  "frequency_span_units":"Hz","save_plot":True,
                  "save_text":True,"reference_level":0,"peak_value":"Not Measured","frequency_span":100}
        self.initial_values={}
        for key,value in defaults.iteritems():
            self.initial_values[key]=value
        for key,value in measurement_data.iteritems():
            self.initial_values[key]=value
        # now make the values in self.intial_values attributes
        for key,value in self.initial_values.iteritems():
            self.__dict__[key]=value
        try:
            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)
        except:
            print("could not calculate resonant bandwidth from frequency span")

    # in general methods and functions should have verbs as names so instead of summary we write get_summary
    def get_summary(self,summary_elements=None):
        """Returns a text summary of the measurement data, if you need them in a different order pass a list of 
        attribute names ["name","text_file_name",...]"""
        if summary_elements==None:
            summary_order=["name","test_file_name","plot_file_name","VNA_frequency",
                           "signal_generator_frequency","measurement_frequency","resonator_bandwidth",
                          "frequency_span","signal_generator_power","VNA_power","reference_level","peak_value"]
        output_string=""
        for index,element in enumerate(summary_elements):
            if index == 0:
                output_string+="{1} : {0}".format(element,self.__dict__[element])
            else:
                output_string+=",{1} : {0}".format(element,self.__dict__[element])
        return output_string
    def __str__(self):
        """Controls the behavior when str(VNAMeasurmentData) or print is called"""
        return self.get_summary()
    def reset_measurement_data(self):
        """Restores to the initialized value"""
        for key,value in self.initial_values.iteritems():
            self.__dict__[key]=value
        try:
            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)
        except:
            print("could not calculate resonant bandwidth from frequency span")

class FakeInstrument():
    """FakeInstrument is a class for testing purposes"""
    def __init__(self,instrument_address):
        self.instrument_address=instrument_address
        pass
    def write(self,command):
        """Prints a write statement"""
        print("Writing the command {0} to {1}".format(command,self.instrument))
    def read(self):
        return 0
    def query(self,command):
        """Prints a write statement and returns a fake value"""
        print("Writing the command {0} to {1}".format(command,self.instrument))
        return 0
    def close(self):
        print("Writing the command {0} to {1}".format("close()",self.instrument))
    def clear(self):
        print("The instrument {0} was cleared".format(self.instrument))

class VNAFrequencySweepExperiment():
    """VNAFrequencySweepExperiment controls the VNA, signal generator and spectrum analyzer"""
    def __init__(self,**set_up):
        """Intializes the VNAFrequencySweepExperimentClass, if the gpib address of the instruments are not the default
        pass them as VNA_address=u'GPIB0::24::INSTR',spectrum_analyzer_address=u'GPIB0::18::INSTR',
        signal_generator_address=u'GPIB0::17::INSTR' or 
        **{'VNA_address':u'GPIB0::24::INSTR','spectrum_analyzer_address':u'GPIB0::18::INSTR',
        'signal_generator_address':u'GPIB0::17::INSTR'} """
        defaults={'VNA_address':VNA_DEFAULT_ADDRESS,'spectrum_analyzer_address':SPECTRUM_ANALYZER_DEFAULT_ADDRESS,
        'signal_generator_address':SIGNAL_GENERATOR_DEFAULT_ADDRESS}
        self.instrument_addresses={}
        for key,value in defaults.iteritems():
            self.instrument_addresses[key]=value
        for key,value in setup.iteritems():
            self.instrument_addresses[key]=value
        self.measurement_list=[]
        try:
            self.resource_manager=visa.ResourceManager()       
            self.open_instruments()
        except:
            print("Could not open the resources, entering testing mode all data is fake")
            self.VNA=self.resource_manager.open_resource(self_instrument_addresses["VNA_Address"])
            self.signal_generator=FakeInstrument(self_instrument_addresses["signal_generator_address"])
            self.spectrum_analyzer=FakeInstrument(self_instrument_addresses["spectrum_analyzer_address"])


    def open_instruments(self):
        """Opens the instruments and creates the attributes VNA, signal_generator and spectrum_analyzer"""
        self.VNA=self.resource_manager.open_resource(self_instrument_addresses["VNA_Address"])
        self.signal_generator=self.resource_manager.open_resource(self_instrument_addresses["signal_generator_address"])
        self.spectrum_analyzer=self.resource_manager.open_resource(self_instrument_addresses["spectrum_analyzer_address"])


    def intialize_frequency_sweep(self):
        #clear input and output buffers
        self.spectrum_analyzer.clear()
        self.signal_generator.clear()
        self.VNA.clear()
        #initialize to preset state
        self.spectrum_analyzer.write("IP")
        self.signal_generator.write("*RST")
        self.VNA.write("SYST:PRESET")
        #single sweep mode on spectrum analyzer
        self.spectrum_analyzer.write("SNGLS;")
        #set no timeout
        self.spectrum_analyzer.timeout=float('+inf')
        #set binary trace output
        self.spectrum_analyzer.write("TDF B;")
        #set VNA to CW mode
        self.VNA.write('SENS1:SWE:TYPE CW')
        #set VNA sweep time to 1 hour to make sure source is on during measurement
        self.VNA.write("SENS1:SWE:TIME 3600")

    def perform_measurement(self,measurement):
        """Performs a single measurement"""
        #sets source to power dBm and frequency cw in GHz.
        #saves a textfile with txtfname
        #if enabled,saves a plot with plot_file_name
        #returns peak val
        #set span
        self.spectrum_analyzer.write("SP "+str(measurement.frequency_span)+"HZ;")
        #set resolution bandwidth
        self.spectrum_analyzer.write("RB "+str(measurement.resonant_bandwidth))
        #set reference level
        self.spectrum_analyzer.write("RL "+str(measurement.reference_level))
        #measurement center frequency
        self.spectrum_analyzer.write("CF "+str(measurement.measurement_frequency)+"GHZ;")
        #set up generator
        self.signal_generator.write("POW:AMPL "+str(measurement.signal_generator_power)+";")
        self.signal_generator.write("FREQ:CW "+str(measurement.signal_generator_frequency)+"E+9;") 
        self.signal_generator.write("OUTP 1;")
        #set up VNA
        self.VNA.write("SOUR1:POW "+str(measurement.VNA_power))
        self.VNA.write("SENS1:FREQ "+str(measurement.VNA_frequency)+"e9")
        #measure a sweep
        #instruments.SA.write("VAVG 10;")
        #meastime=float(instruments.SA.query("ST?"))+2
        self.spectrum_analyzer.write("TS;")
        #aquire a trace
        self.spectrum_analyzer.write("TRA?;")
        data=self.spectrum_analyzer.read_raw()
        dataint= np.array(struct.unpack('>' + 'H'*601, data))
        #specify vertical scale
        RL=float(self.spectrum_analyzer.query("RL?"))
        dBperDev=float(self.spectrum_analyzer.query("LG?"))
        #rescale binary data
        datadB=RL+dBperDev*(dataint/60.0-10)
        #find span 
        FA=float(self.spectrum_analyzer.query('FA?'))
        FB=float(self.spectrum_analyzer.query('FB?'))
        frequency=np.linspace(FA,FB,601)
        if measurement.save_text:
            f=open(measurement.directory+measurement.text_file_name,'w')
            for i in range(len(datadB)):
                f.write(str(frequency[i])+" "+str(dataint[i])+" "+str(datadB[i])+"\n")
            f.close()
        if measurement.save_plot:
            pl.clf()
            pl.plot(frequency-measurement.measurement_frequency*1e9,datadB)
            pl.xlim([FA-measurement.measurement_frequency*1e9,FB-measurement.measurement_frequency*1e9])
            pl.ylim([RL-120,RL])
            pl.ylabel("power (dBm)")
            pl.xlabel("Frequency offset (Hz)")
            pl.savefig(measurement.directory+measurement.plot_file_name)
        measurement.peak_value=np.max(datadB)          
        #return measurement

    def shuffle_measurement_list(self):
        """Randomly shuffles current measurement list"""
        self.measurement_list=random.shuffle(self.measurement_list)

    def perform_all_measurements(self,save_all_text=True,plot_all=True):
        """Performs all measurements in measurement list"""
        for measurement in self.measurement_list:
            self.perform_measurement(measurement)

    def close(self):
        """Close all"""
        self.VNA.close()
        self.signal_generator.close()
        self.spectrum_analyzer.close()

    def save_summary(self):
        """Saves a summary of all the measurements in measurement_list"""
        save_summary(self.measurement_list)

#-----------------------------------------------------------------------------
# Script Definitions    



#-----------------------------------------------------------------------------
# Module Runner   

 if __name__ == '__main__':
    measure_two_frequency_script()