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
#-----------------------------------------------------------------------------
# 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"""
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.
#-----------------------------------------------------------------------------
# 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")
# 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'
# 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 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)
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",**{})
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()