{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"top\"></a>\n",
    "# An example of refactoring code to make it shareable and ready to attach to a FrontEnd\n",
    "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\n",
    "\n",
    "## Table of Contents\n",
    "<hr style=\"border-top: 3px double #8c8b8b;\"/>\n",
    "<ol>\n",
    "<li> <h3><a href=\"#module-doc\">Module Header and Documentation</a> </h3></li>\n",
    "<li> <h3><a href=\"#import\">Refactoring Import Statements</a> </h3></li>\n",
    "<li> <h3><a href=\"#constants\">Constants</a> </h3></li>\n",
    "<li> <h3><a href=\"#functions\">Defining Functions For Reuse</a> </h3></li>\n",
    "<li> <h3><a href=\"#classes\">Class Definition</a> </h3></li>\n",
    "<li> <h3><a href=\"#scripts\">Script Definition</a> </h3></li>\n",
    "<li> <h3><a href=\"#runner\">Module Runner</a> </h3></li>\n",
    "<li> <h3><a href=\"#original\">Orignal Module</a> </h3></li>\n",
    "<li> <h3><a href=\"#refactored\">Refactored Module</a> </h3></li>\n",
    "<li> <h3> <a href=\"#more-information\">More Information</a></h3> </li>\n",
    "</ol>\n",
    "<hr style=\"border-top: 3px double #8c8b8b;\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"module-doc\">Module Header and Documentation</a> </h3>\n",
    " Purpose:\n",
    " 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\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "' VNAExperiments holds classes and functions important for a frequency swept measurement using a \\nVector Network Analyzer and a Signal Generator'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#-----------------------------------------------------------------------------\n",
    "# Name:        VNAExperiments\n",
    "# Purpose:     To measure frequency swept data using a spectrum analyzer, vector network\n",
    "# analyzer and a signal generator\n",
    "# Author:      Aaron Hagerstrom\n",
    "# Created:     9/13/2016\n",
    "# License:     MIT License* Aaron you should check which license you want\n",
    "#-----------------------------------------------------------------------------\n",
    "\"\"\" VNAExperiments holds classes and functions important for a frequency swept measurement using a \n",
    "Vector Network Analyzer and a Signal Generator\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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\n",
    "if you want an open source license."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"import\">Refactoring Import Statements</a> </h3>\n",
    " Purpose:\n",
    "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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#-----------------------------------------------------------------------------\n",
    "# Standard Imports\n",
    "import os,os.path\n",
    "import random\n",
    "import struct\n",
    "#-----------------------------------------------------------------------------\n",
    "# Third Party Imports\n",
    "try:\n",
    "    import pylab as pl\n",
    "except:\n",
    "    print(\"The package pylab failed to load, please resolve the error or put it on the python path (pip install pylab) or\"\n",
    "          \" http://scipy.org/\")\n",
    "try:\n",
    "    import numpy as np\n",
    "except:\n",
    "    print(\"The package numpy failed to load, please resolve the error or put it on the python path (pip install numpy) or\"\n",
    "          \" http://scipy.org/\")\n",
    "try:\n",
    "    import visa\n",
    "except:\n",
    "    print(\"The package visa failed to load, please resolve the error or put it on the python path (pip install pyvisa) or\"\n",
    "          \"https://pyvisa.readthedocs.io/en/stable/getting.html\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"constants\">Constants</a> </h3>\n",
    " Purpose:\n",
    "   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)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 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\n",
    "VNA_DEFAULT_ADDRESS=u'GPIB0::24::INSTR'\n",
    "SPECTRUM_ANALYZER_DEFAULT_ADDRESS=u'GPIB0::18::INSTR'\n",
    "SIGNAL_GENERATOR_DEFAULT_ADDRESS=u'GPIB0::17::INSTR'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"functions\">Defining functions for reuse.</a> </h3>\n",
    " Purpose:\n",
    " 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)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Create a function to do the organization currently in the script\n",
    "def parse_measurements(measurement_list):\n",
    "    \"\"\"Given a list of measurements\n",
    "    returns a dictionary of the form {measurement.name:{frequency_list:[],peak_value_list:[]}}\"\"\"\n",
    "    output_dictionary={}\n",
    "    for index,measurement in enumerate(measurement_list):\n",
    "        if measurement.name in output_dictionary.keys():\n",
    "            output_dictionary[measurement.name][\"frequency_list\"].append(measurement.frequency)\n",
    "            output_dictionary[measurement.name][\"peak_value_list\"].append(measurement.peak_value)\n",
    "        else:\n",
    "            output_dictionary[measurement.name]={\"frequency_list\":[],\"peak_value_list\":[]}\n",
    "            output_dictionary[measurement.name][\"frequency_list\"].append(measurement.frequency)\n",
    "            output_dictionary[measurement.name][\"peak_value_list\"].append(measurement.peak_value)\n",
    "    return output_dictionary\n",
    "\n",
    "def plot_measurements(parsed_measurement_dictionary,**options):\n",
    "    \"\"\"Plots all measurements that have been parsed using parse_measurements, passing **options to the plot\"\"\"\n",
    "    # magic that will pass plot options in case you want to change them, it takes out the options we want \n",
    "    defaults={\"marker\":'o',\"linestyle\":\" \",\"display_legend\":True,\"save_figure\":True,\"directory\":None}\n",
    "    plot_options={}\n",
    "    for key,value in defaults.iteritems():\n",
    "        if key not in [\"display_legend\",\"save_figure\",\"directory\"]:\n",
    "            plot_options[key]=value\n",
    "    for key,value in options.iteritems():\n",
    "        if key not in [\"display_legend\",\"save_figure\",\"directory\"]:\n",
    "            plot_options[key]\n",
    "    # this will plot everything \n",
    "    pl.clf()\n",
    "    for name,measurement_dictionary in parsed_measurement_dictionary.iteritems():\n",
    "        pl.plot(measurement_dictionary[\"frequency_list\"],measurement_dictionary[\"peak_value_list\"],label=name,**plot_options)\n",
    "    if plot_options[\"display_legend\"]:\n",
    "        pl.legend()\n",
    "    pl.ylabel(\"Power (dBm)\")\n",
    "    pl.xlabel(\"Frequency (GHz)\")\n",
    "    if plot_options[\"directory\"] is None:\n",
    "        plot_options[\"directory\"]=os.getcwd()\n",
    "    try: \n",
    "        if plot_options[\"save_figure\"]:\n",
    "            pl.savefig(os.path.join(plot_options[\"directory\"],\"peaks.png\"))\n",
    "    except IOError as e:\n",
    "        print(\" Windows is stupid, {0} \".format(e))\n",
    "\n",
    "def build_measurement_list(measurement_frequencies,base_name=\"\",**measurement_settings):\n",
    "    \"\"\"Returns a list of measurements given a list of frequencies [[VNA_frequency,signal_generator_frequency,\n",
    "    measurement_frequency] ...], a base name and the other measurement settings\"\"\"\n",
    "    out_list=[]\n",
    "    for index,row in measurement_frequencies:\n",
    "        new_measurement=VNAMeasurementData(**measurement_settings)\n",
    "        new_measurement.VNA_frequency=row[0]\n",
    "        new_measurement.signal_generator_frequency=row[1]  \n",
    "        new_measurement.signal_generator_frequency=row[1]\n",
    "        new_measurement.name=\"sumone\"+str(index)\n",
    "        new_measurement.text_file_name=base_name+str(index)+\".txt\"\n",
    "        new_measurement.plot_file_name=base_name+str(index)+\".png\"\n",
    "        out_list.append(new_measurement)\n",
    "    return out_list\n",
    "def save_summary(measurement_list):\n",
    "    \"\"\"Saves a summary of all the measurements in measurement_list\"\"\"\n",
    "    summary_file=open(measurement_list[0].directory+\"summary.txt\",'w')\n",
    "    for measurement in measurement_list:\n",
    "        text=self.perform_measurement(measurement)\n",
    "        summary_file.write(text)\n",
    "    summary_file.close()\n",
    "            "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"classes\">Class Definition</a> </h3>\n",
    " Purpose:\n",
    " 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. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class VNAMeasurementData():\n",
    "    \"\"\"VNAMeasurementData holds the data for a single measurement \"\"\"\n",
    "    def __init__(self,**measurement_data):\n",
    "        \"\"\"Intializes the VNAMeasurementData class. When created, data can be passed as a dictionary\n",
    "        VNAMeasurementData(**{\"VNA_power\":0})\n",
    "        or as keyword arguments VNAMeasurementData(VNA_power=0)\"\"\"\n",
    "        defaults={\"directory\":None,\"record_name\":\"measurement\",\"text_file_name\":\"out.txt\",\n",
    "                 \"plot_file_name\":\"out.png\",\"VNA_power\":0,\"signal_generator_power\":-10,\"power_units\":\"dBm\",\n",
    "                 \"VNA_frequency\":10,\"signal_generator_frequency\":10,\"measurement_frequency\":10,\"frequency_units\":\"GHz\",\n",
    "                  \"frequency_span_units\":\"Hz\",\"save_plot\":True,\n",
    "                  \"save_text\":True,\"reference_level\":0,\"peak_value\":\"Not Measured\",\"frequency_span\":100}\n",
    "        self.initial_values={}\n",
    "        for key,value in defaults.iteritems():\n",
    "            self.initial_values[key]=value\n",
    "        for key,value in measurement_data.iteritems():\n",
    "            self.initial_values[key]=value\n",
    "        # now make the values in self.intial_values attributes\n",
    "        for key,value in self.initial_values.iteritems():\n",
    "            self.__dict__[key]=value\n",
    "        try:\n",
    "            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)\n",
    "        except:\n",
    "            print(\"could not calculate resonant bandwidth from frequency span\")\n",
    "            \n",
    "    # in general methods and functions should have verbs as names so instead of summary we write get_summary\n",
    "    def get_summary(self,summary_elements=None):\n",
    "        \"\"\"Returns a text summary of the measurement data, if you need them in a different order pass a list of \n",
    "        attribute names [\"name\",\"text_file_name\",...]\"\"\"\n",
    "        if summary_elements==None:\n",
    "            summary_order=[\"name\",\"test_file_name\",\"plot_file_name\",\"VNA_frequency\",\n",
    "                           \"signal_generator_frequency\",\"measurement_frequency\",\"resonator_bandwidth\",\n",
    "                          \"frequency_span\",\"signal_generator_power\",\"VNA_power\",\"reference_level\",\"peak_value\"]\n",
    "        output_string=\"\"\n",
    "        for index,element in enumerate(summary_elements):\n",
    "            if index == 0:\n",
    "                output_string+=\"{1} : {0}\".format(element,self.__dict__[element])\n",
    "            else:\n",
    "                output_string+=\",{1} : {0}\".format(element,self.__dict__[element])\n",
    "        return output_string\n",
    "    def __str__(self):\n",
    "        \"\"\"Controls the behavior when str(VNAMeasurmentData) or print is called\"\"\"\n",
    "        return self.get_summary()\n",
    "    def reset_measurement_data(self):\n",
    "        \"\"\"Restores to the initialized value\"\"\"\n",
    "        for key,value in self.initial_values.iteritems():\n",
    "            self.__dict__[key]=value\n",
    "        try:\n",
    "            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)\n",
    "        except:\n",
    "            print(\"could not calculate resonant bandwidth from frequency span\")\n",
    "            \n",
    "class FakeInstrument():\n",
    "    \"\"\"FakeInstrument is a class for testing purposes\"\"\"\n",
    "    def __init__(self,instrument_address):\n",
    "        self.instrument_address=instrument_address\n",
    "        pass\n",
    "    def write(self,command):\n",
    "        \"\"\"Prints a write statement\"\"\"\n",
    "        print(\"Writing the command {0} to {1}\".format(command,self.instrument))\n",
    "    def read(self):\n",
    "        return 0\n",
    "    def query(self,command):\n",
    "        \"\"\"Prints a write statement and returns a fake value\"\"\"\n",
    "        print(\"Writing the command {0} to {1}\".format(command,self.instrument))\n",
    "        return 0\n",
    "    def close(self):\n",
    "        print(\"Writing the command {0} to {1}\".format(\"close()\",self.instrument))\n",
    "    def clear(self):\n",
    "        print(\"The instrument {0} was cleared\".format(self.instrument))\n",
    "        \n",
    "class VNAFrequencySweepExperiment():\n",
    "    \"\"\"VNAFrequencySweepExperiment controls the VNA, signal generator and spectrum analyzer\"\"\"\n",
    "    def __init__(self,**set_up):\n",
    "        \"\"\"Intializes the VNAFrequencySweepExperimentClass, if the gpib address of the instruments are not the default\n",
    "        pass them as VNA_address=u'GPIB0::24::INSTR',spectrum_analyzer_address=u'GPIB0::18::INSTR',\n",
    "        signal_generator_address=u'GPIB0::17::INSTR' or \n",
    "        **{'VNA_address':u'GPIB0::24::INSTR','spectrum_analyzer_address':u'GPIB0::18::INSTR',\n",
    "        'signal_generator_address':u'GPIB0::17::INSTR'} \"\"\"\n",
    "        defaults={'VNA_address':VNA_DEFAULT_ADDRESS,'spectrum_analyzer_address':SPECTRUM_ANALYZER_DEFAULT_ADDRESS,\n",
    "        'signal_generator_address':SIGNAL_GENERATOR_DEFAULT_ADDRESS}\n",
    "        self.instrument_addresses={}\n",
    "        for key,value in defaults.iteritems():\n",
    "            self.instrument_addresses[key]=value\n",
    "        for key,value in setup.iteritems():\n",
    "            self.instrument_addresses[key]=value\n",
    "        self.measurement_list=[]\n",
    "        try:\n",
    "            self.resource_manager=visa.ResourceManager()       \n",
    "            self.open_instruments()\n",
    "        except:\n",
    "            print(\"Could not open the resources, entering testing mode all data is fake\")\n",
    "            self.VNA=self.resource_manager.open_resource(self_instrument_addresses[\"VNA_Address\"])\n",
    "            self.signal_generator=FakeInstrument(self_instrument_addresses[\"signal_generator_address\"])\n",
    "            self.spectrum_analyzer=FakeInstrument(self_instrument_addresses[\"spectrum_analyzer_address\"])\n",
    "            \n",
    "        \n",
    "    def open_instruments(self):\n",
    "        \"\"\"Opens the instruments and creates the attributes VNA, signal_generator and spectrum_analyzer\"\"\"\n",
    "        self.VNA=self.resource_manager.open_resource(self_instrument_addresses[\"VNA_Address\"])\n",
    "        self.signal_generator=self.resource_manager.open_resource(self_instrument_addresses[\"signal_generator_address\"])\n",
    "        self.spectrum_analyzer=self.resource_manager.open_resource(self_instrument_addresses[\"spectrum_analyzer_address\"])\n",
    "        \n",
    "        \n",
    "    def intialize_frequency_sweep(self):\n",
    "        #clear input and output buffers\n",
    "        self.spectrum_analyzer.clear()\n",
    "        self.signal_generator.clear()\n",
    "        self.VNA.clear()\n",
    "        #initialize to preset state\n",
    "        self.spectrum_analyzer.write(\"IP\")\n",
    "        self.signal_generator.write(\"*RST\")\n",
    "        self.VNA.write(\"SYST:PRESET\")\n",
    "        #single sweep mode on spectrum analyzer\n",
    "        self.spectrum_analyzer.write(\"SNGLS;\")\n",
    "        #set no timeout\n",
    "        self.spectrum_analyzer.timeout=float('+inf')\n",
    "        #set binary trace output\n",
    "        self.spectrum_analyzer.write(\"TDF B;\")\n",
    "        #set VNA to CW mode\n",
    "        self.VNA.write('SENS1:SWE:TYPE CW')\n",
    "        #set VNA sweep time to 1 hour to make sure source is on during measurement\n",
    "        self.VNA.write(\"SENS1:SWE:TIME 3600\")\n",
    "        \n",
    "    def perform_measurement(self,measurement):\n",
    "        \"\"\"Performs a single measurement\"\"\"\n",
    "        #sets source to power dBm and frequency cw in GHz.\n",
    "        #saves a textfile with txtfname\n",
    "        #if enabled,saves a plot with plot_file_name\n",
    "        #returns peak val\n",
    "        #set span\n",
    "        self.spectrum_analyzer.write(\"SP \"+str(measurement.frequency_span)+\"HZ;\")\n",
    "        #set resolution bandwidth\n",
    "        self.spectrum_analyzer.write(\"RB \"+str(measurement.resonant_bandwidth))\n",
    "        #set reference level\n",
    "        self.spectrum_analyzer.write(\"RL \"+str(measurement.reference_level))\n",
    "        #measurement center frequency\n",
    "        self.spectrum_analyzer.write(\"CF \"+str(measurement.measurement_frequency)+\"GHZ;\")\n",
    "        #set up generator\n",
    "        self.signal_generator.write(\"POW:AMPL \"+str(measurement.signal_generator_power)+\";\")\n",
    "        self.signal_generator.write(\"FREQ:CW \"+str(measurement.signal_generator_frequency)+\"E+9;\") \n",
    "        self.signal_generator.write(\"OUTP 1;\")\n",
    "        #set up VNA\n",
    "        self.VNA.write(\"SOUR1:POW \"+str(measurement.VNA_power))\n",
    "        self.VNA.write(\"SENS1:FREQ \"+str(measurement.VNA_frequency)+\"e9\")\n",
    "        #measure a sweep\n",
    "        #instruments.SA.write(\"VAVG 10;\")\n",
    "        #meastime=float(instruments.SA.query(\"ST?\"))+2\n",
    "        self.spectrum_analyzer.write(\"TS;\")\n",
    "        #aquire a trace\n",
    "        self.spectrum_analyzer.write(\"TRA?;\")\n",
    "        data=self.spectrum_analyzer.read_raw()\n",
    "        dataint= np.array(struct.unpack('>' + 'H'*601, data))\n",
    "        #specify vertical scale\n",
    "        RL=float(self.spectrum_analyzer.query(\"RL?\"))\n",
    "        dBperDev=float(self.spectrum_analyzer.query(\"LG?\"))\n",
    "        #rescale binary data\n",
    "        datadB=RL+dBperDev*(dataint/60.0-10)\n",
    "        #find span \n",
    "        FA=float(self.spectrum_analyzer.query('FA?'))\n",
    "        FB=float(self.spectrum_analyzer.query('FB?'))\n",
    "        frequency=np.linspace(FA,FB,601)\n",
    "        if measurement.save_text:\n",
    "            f=open(measurement.directory+measurement.text_file_name,'w')\n",
    "            for i in range(len(datadB)):\n",
    "                f.write(str(frequency[i])+\" \"+str(dataint[i])+\" \"+str(datadB[i])+\"\\n\")\n",
    "            f.close()\n",
    "        if measurement.save_plot:\n",
    "            pl.clf()\n",
    "            pl.plot(frequency-measurement.measurement_frequency*1e9,datadB)\n",
    "            pl.xlim([FA-measurement.measurement_frequency*1e9,FB-measurement.measurement_frequency*1e9])\n",
    "            pl.ylim([RL-120,RL])\n",
    "            pl.ylabel(\"power (dBm)\")\n",
    "            pl.xlabel(\"Frequency offset (Hz)\")\n",
    "            pl.savefig(measurement.directory+measurement.plot_file_name)\n",
    "        measurement.peak_value=np.max(datadB)          \n",
    "        #return measurement\n",
    "                                    \n",
    "    def shuffle_measurement_list(self):\n",
    "        \"\"\"Randomly shuffles current measurement list\"\"\"\n",
    "        self.measurement_list=random.shuffle(self.measurement_list)\n",
    "\n",
    "    def perform_all_measurements(self,save_all_text=True,plot_all=True):\n",
    "        \"\"\"Performs all measurements in measurement list\"\"\"\n",
    "        for measurement in self.measurement_list:\n",
    "            self.perform_measurement(measurement)\n",
    "            \n",
    "    def close(self):\n",
    "        \"\"\"Close all\"\"\"\n",
    "        self.VNA.close()\n",
    "        self.signal_generator.close()\n",
    "        self.spectrum_analyzer.close()\n",
    "        \n",
    "    def save_summary(self):\n",
    "        \"\"\"Saves a summary of all the measurements in measurement_list\"\"\"\n",
    "        save_summary(self.measurement_list)\n",
    "\n",
    "                                    \n",
    "                                    \n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"scripts\">Script Definition</a> </h3>\n",
    " Purpose:\n",
    " 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. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def measure_two_frequency_script(signal_frequency=13.5,directory=None):\n",
    "    \"\"\"Sets the signal_frequency to 13.5 and measures\"\"\"\n",
    "    if directory is None:\n",
    "        directory=\"C:\\\\Users\\\\hightc\\\\Desktop\\\\20160422 tuning 150k\\\\0p3125VG\\\\\"\n",
    "    if os.path.isfile(directory+\"summary.txt\"):\n",
    "        print \"file summary.txt exists.\"\n",
    "        raise SystemExit\n",
    "    #Open the experiment\n",
    "    Experiment=VNAFrequencySweepExperiment()\n",
    "    #Intitalize the instruments\n",
    "    Experiment.initialize_instruments()\n",
    "    #create tunning and second frequencies \n",
    "    tuning_frequencies=np.linspace(0,13.25,54)[1::]\n",
    "    second_frequencies=np.hstack(np.linspace(0,6.5,27)[1::])\n",
    "    #create measurement lists\n",
    "    sum_one=[[frequency,signal_frequency,frequency+signal_frequency] index,frequency in enumerate(tuning_frequencies)]\n",
    "    measurement_sum_one=build_measurement_list(sum_one,base_name=\"sumone\",**{})\n",
    "\n",
    "    \n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"runner\">Module Runner</a> </h3>\n",
    " Purpose:\n",
    " To create a module runner that calls the script of our choice. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "if __name__ == '__main__':\n",
    "    measure_two_frequency_script()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"original\">Original Module</a> </h3> "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "import visa\n",
    "import pylab as pl\n",
    "import numpy as np\n",
    "import struct\n",
    "import os.path\n",
    "import random\n",
    "\n",
    "class measurement:\n",
    "    def __init__(self,VNA=None,SA=None,GEN=None):            \n",
    "        self.VNA=VNA\n",
    "        self.SA=SA\n",
    "        self.GEN=GEN\n",
    "        \n",
    "        self.savepath=None\n",
    "        self.recordname=\"measurement\"\n",
    "        self.txtfname=\"out.txt\"\n",
    "        self.plotfname=\"out.png\"\n",
    "        \n",
    "        self.VNApower=0 #dBm\n",
    "        self.GENpower=-10 #dBm\n",
    "        \n",
    "        self.VNAf=10.0 #frequency of source in GHz\n",
    "        self.GENf=10.0  #frequency to measure in GHz\n",
    "        self.measf=10.0 #measuement frequency in GHz\n",
    "        \n",
    "        self.saveplot=True\n",
    "        self.savetxt=True\n",
    "        \n",
    "        self.refLVL=0\n",
    "        self.peakval=\"not measured\"\n",
    "        self.fspan=100 #Hz\n",
    "        self.resBW=np.ceil(self.fspan/600.0) #Hz\n",
    "                      \n",
    "    #sets source to power dBm and frequency cw in GHz.\n",
    "    #saves a textfile with txtfname\n",
    "    #if enabled,saves a plot with plotfname\n",
    "    #returns peak val\n",
    "    def perform(self):\n",
    "        #set span\n",
    "        self.SA.write(\"SP \"+str(self.fspan)+\"HZ;\")\n",
    "        #set resolution bandwidth\n",
    "        self.SA.write(\"RB \"+str(self.resBW))\n",
    "        #set reference level\n",
    "        self.SA.write(\"RL \"+str(self.refLVL))\n",
    "        #measurement center frequency\n",
    "        self.SA.write(\"CF \"+str(self.measf)+\"GHZ;\")\n",
    "        \n",
    "        #set up generator\n",
    "        self.GEN.write(\"POW:AMPL \"+str(self.GENpower)+\";\")\n",
    "        self.GEN.write(\"FREQ:CW \"+str(self.GENf)+\"E+9;\") \n",
    "        self.GEN.write(\"OUTP 1;\")\n",
    "        \n",
    "        #set up VNA\n",
    "        VNA.write(\"SOUR1:POW \"+str(self.VNApower))\n",
    "        VNA.write(\"SENS1:FREQ \"+str(self.VNAf)+\"e9\")\n",
    "\n",
    "        #measure a sweep\n",
    "        #instruments.SA.write(\"VAVG 10;\")\n",
    "        #meastime=float(instruments.SA.query(\"ST?\"))+2\n",
    "        self.SA.write(\"TS;\")\n",
    "        #aquire a trace\n",
    "        self.SA.write(\"TRA?;\")\n",
    "        \n",
    "        data=SA.read_raw()\n",
    "        dataint= np.array(struct.unpack('>' + 'H'*601, data))\n",
    "        \n",
    "        #specify vertical scale\n",
    "        RL=float(SA.query(\"RL?\"))\n",
    "        dBperDev=float(SA.query(\"LG?\"))\n",
    "        \n",
    "        #rescale binary data\n",
    "        datadB=RL+dBperDev*(dataint/60.0-10)\n",
    "        \n",
    "        #find span \n",
    "        FA=float(SA.query('FA?'))\n",
    "        FB=float(SA.query('FB?'))\n",
    "        \n",
    "        frequency=np.linspace(FA,FB,601)\n",
    "        if self.savetxt:\n",
    "            f=open(self.savepath+self.txtfname,'w')\n",
    "            for i in range(len(datadB)):\n",
    "                f.write(str(frequency[i])+\" \"+str(dataint[i])+\" \"+str(datadB[i])+\"\\n\")\n",
    "            f.close()\n",
    "        \n",
    "        if self.saveplot:\n",
    "            pl.clf()\n",
    "            pl.plot(frequency-self.measf*1e9,datadB)\n",
    "            pl.xlim([FA-self.measf*1e9,FB-self.measf*1e9])\n",
    "            pl.ylim([RL-120,RL])\n",
    "            pl.ylabel(\"power (dBm)\")\n",
    "            pl.xlabel(\"Frequency offset (Hz)\")\n",
    "            pl.savefig(self.savepath+self.plotfname)\n",
    "    \n",
    "        self.peakval=np.max(datadB)  \n",
    "        \n",
    "    def summary(self):\n",
    "        retstr=\"name \"+str(self.name)\n",
    "        retstr+=\",datafile \"+str(self.txtfname)\n",
    "        retstr+=\",plotname \"+ str(self.plotfname)\n",
    "        retstr+=\",VNAFreq \"+  str(self.VNAf)+\" GHz\"\n",
    "        retstr+=\",GENfreq \"+  str(self.GENf)+\" GHz\"\n",
    "        retstr+=\",measfreq \"+str(self.measf)+\" GHz\"\n",
    "        retstr+=\",resBw \"+str(self.resBW)+\" Hz\"\n",
    "        retstr+=\",span \"+str(self.fspan)+\" Hz\"\n",
    "        retstr+=\",gen pow: \"+str(self.GENpower)+\" dBm\" \n",
    "        retstr+=\",VNA pow: \"+str(self.VNApower)+\" dBm\"         \n",
    "        retstr+=\",ref lvl: \"+str(self.refLVL)+\" dBm\" \n",
    "        retstr+=\",peak val: \"+str(self.peakval)+\" dBm\\n\"\n",
    "        return retstr\n",
    "                                                             \n",
    "savepath=\"C:\\\\Users\\\\hightc\\\\Desktop\\\\20160422 tuning 150k\\\\0p3125VG\\\\\"\n",
    "if os.path.isfile(savepath+\"summary.txt\"):\n",
    "    print \"file summary.txt exists.\"\n",
    "    raise SystemExit\n",
    "\n",
    "# set up instruments\n",
    "rm=visa.ResourceManager()\n",
    "SA=rm.open_resource(u'GPIB0::24::INSTR')\n",
    "GEN=rm.open_resource(u'GPIB0::18::INSTR')\n",
    "VNA=rm.open_resource(u'GPIB0::17::INSTR')\n",
    "\n",
    "#clear input and output buffers\n",
    "SA.clear()\n",
    "GEN.clear()\n",
    "VNA.clear()\n",
    "\n",
    "#initialize to preset state\n",
    "SA.write(\"IP\")\n",
    "GEN.write(\"*RST\")\n",
    "VNA.write(\"SYST:PRESET\")\n",
    "                \n",
    "#single sweep mode on spectrum analyzer\n",
    "SA.write(\"SNGLS;\")\n",
    "        \n",
    "#set no timeout\n",
    "SA.timeout=float('+inf')\n",
    "          \n",
    "#set binary trace output\n",
    "SA.write(\"TDF B;\")\n",
    "\n",
    "#set VNA to CW mode\n",
    "VNA.write('SENS1:SWE:TYPE CW')\n",
    "        \n",
    "#set VNA sweep time to 1 hour to make sure source is on during measurement\n",
    "VNA.write(\"SENS1:SWE:TIME 3600\")\n",
    "\n",
    "measurements=[]\n",
    "\n",
    "tuningFreqs=np.linspace(0,13.25,54)[1::]\n",
    "SecondFreqs=np.hstack(np.linspace(0,6.5,27)[1::])\n",
    "\n",
    "sigf=13.25\n",
    "for i,tf in enumerate(tuningFreqs):\n",
    "    if sigf+tf<50:\n",
    "        mdummy=measurement(VNA,SA,GEN)      \n",
    "        mdummy.savepath=savepath\n",
    "        mdummy.refLVL=-50\n",
    "        mdummy.VNApower=-10 #dBm\n",
    "        mdummy.GENpower=8  #dBm\n",
    "        mdummy.saveplot=False\n",
    "        mdummy.name=\"sumone\"+str(i)\n",
    "        mdummy.txtfname=\"sumone\"+str(i)+\".txt\"\n",
    "        mdummy.plotfname=\"sumone\"+str(i)+\".png\"\n",
    "        mdummy.measf=tf+sigf\n",
    "        mdummy.VNAf=tf\n",
    "        mdummy.GENf=sigf\n",
    "        measurements.append(mdummy)\n",
    "\n",
    "for i,tf in enumerate(tuningFreqs):\n",
    "    mdummy=measurement(VNA,SA,GEN)      \n",
    "    mdummy.savepath=savepath\n",
    "    mdummy.refLVL=-50\n",
    "    mdummy.VNApower=-10 #dBm\n",
    "    mdummy.GENpower=8  #dBm\n",
    "    mdummy.saveplot=False\n",
    "    mdummy.name=\"diffone\"+str(i)\n",
    "    mdummy.txtfname=\"diffone\"+str(i)+\".txt\"\n",
    "    mdummy.plotfname=\"diffone\"+str(i)+\".png\"\n",
    "    mdummy.measf=np.abs(tf-sigf)\n",
    "    mdummy.VNAf=tf\n",
    "    mdummy.GENf=sigf\n",
    "    measurements.append(mdummy)\n",
    "\n",
    "for i,tf in enumerate(SecondFreqs):\n",
    "    mdummy=measurement(VNA,SA,GEN)      \n",
    "    mdummy.savepath=savepath\n",
    "    mdummy.refLVL=-80\n",
    "    mdummy.VNApower=-10 #dBm\n",
    "    mdummy.GENpower=8  #dBm\n",
    "    mdummy.saveplot=False\n",
    "    mdummy.name=\"difftwo\"+str(i)\n",
    "    mdummy.txtfname=\"difftwo\"+str(i)+\".txt\"\n",
    "    mdummy.plotfname=\"difftwo\"+str(i)+\".png\"\n",
    "    mdummy.measf=np.abs(-2.0*tf+sigf)\n",
    "    mdummy.VNAf=tf\n",
    "    mdummy.GENf=sigf\n",
    "    measurements.append(mdummy)\n",
    "\n",
    "for i,tf in enumerate(SecondFreqs):\n",
    "    mdummy=measurement(VNA,SA,GEN)      \n",
    "    mdummy.savepath=savepath\n",
    "    mdummy.refLVL=-80\n",
    "    mdummy.VNApower=-10 #dBm\n",
    "    mdummy.GENpower=8  #dBm\n",
    "    mdummy.saveplot=False\n",
    "    mdummy.name=\"sumtwo\"+str(i)\n",
    "    mdummy.txtfname=\"sumtwo\"+str(i)+\".txt\"\n",
    "    mdummy.plotfname=\"sumtwo\"+str(i)+\".png\"\n",
    "    mdummy.measf=np.abs(2.0*tf+sigf)\n",
    "    mdummy.VNAf=tf\n",
    "    mdummy.GENf=sigf\n",
    "    measurements.append(mdummy)    \n",
    "\n",
    "diff1data=[]\n",
    "diff1fdata=[]\n",
    "\n",
    "diff2data=[]\n",
    "diff2fdata=[]\n",
    "\n",
    "sum1data=[]\n",
    "sum1fdata=[]\n",
    "\n",
    "sum2data=[]\n",
    "sum2fdata=[]\n",
    "\n",
    "random.shuffle(measurements)\n",
    "f=open(savepath+\"summary.txt\",'w')\n",
    "for i, meas in enumerate(measurements):\n",
    "    meas.perform()\n",
    "    text=meas.summary()\n",
    "    if \"diffone\" in meas.name:\n",
    "        diff1data.append(meas.peakval)\n",
    "        diff1fdata.append(meas.measf)\n",
    "    \n",
    "    if \"sumone\" in meas.name:\n",
    "        sum1data.append(meas.peakval)\n",
    "        sum1fdata.append(meas.measf)\n",
    "    \n",
    "    if \"difftwo\" in meas.name:\n",
    "        diff2data.append(meas.peakval)\n",
    "        diff2fdata.append(meas.measf)\n",
    "    \n",
    "    if \"sumtwo\" in meas.name:\n",
    "        sum2data.append(meas.peakval)\n",
    "        sum2fdata.append(meas.measf)\n",
    "    \n",
    "    pl.clf()\n",
    "    pl.plot(sum1fdata,sum1data,marker='o',linestyle=\" \",label=\"sum 1\")\n",
    "    pl.plot(diff1fdata,diff1data,marker='o',linestyle=\" \",label=\"diff 1\")\n",
    "    pl.plot(sum2fdata,sum2data,marker='o',linestyle=\" \",label=\"sum 2\")\n",
    "    pl.plot(diff2fdata,diff2data,marker='o',linestyle=\" \",label=\"diff 2\")\n",
    "    \n",
    "    pl.legend()\n",
    "    pl.xlabel(\"Power (dBm)\")\n",
    "    pl.xlabel(\"Frequency (GHz)\")\n",
    "    try:\n",
    "        pl.savefig(savepath+\"peaks.png\")\n",
    "    except IOError as e:\n",
    "        print \"windows is stupid\", e\n",
    "        \n",
    "    print \"measurement \", i, \" of \",len(measurements)\n",
    "    #print text\n",
    "    f.write(text)\n",
    " \n",
    "f.close()\n",
    "SA.close()\n",
    "GEN.close()\n",
    "VNA.close()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " <a href=\"#top\"> <em>back to top</em></a>\n",
    " <hr style=\"border-top: 2px double #8c8b8b;\"/>\n",
    " <h3><a id=\"refactored\">Refactored Module</a> </h3> "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "#-----------------------------------------------------------------------------\n",
    "# Name:        VNAExperiments\n",
    "# Purpose:     To measure frequency swept data using a spectrum analyzer, vector network\n",
    "# analyzer and a signal generator\n",
    "# Author:      Aaron Hagerstrom\n",
    "# Created:     9/13/2016\n",
    "# License:     MIT License* Aaron you should check which license you want\n",
    "#-----------------------------------------------------------------------------\n",
    "\"\"\" VNAExperiments holds classes and functions important for a frequency swept measurement using a \n",
    "Vector Network Analyzer and a Signal Generator\"\"\"\n",
    "#-----------------------------------------------------------------------------\n",
    "# Standard Imports\n",
    "import os,os.path\n",
    "import random\n",
    "import struct\n",
    "#-----------------------------------------------------------------------------\n",
    "# Third Party Imports\n",
    "try:\n",
    "    import pylab as pl\n",
    "except:\n",
    "    print(\"The package pylab failed to load, please resolve the error or put it on the python path (pip install pylab) or\"\n",
    "          \" http://scipy.org/\")\n",
    "try:\n",
    "    import numpy as np\n",
    "except:\n",
    "    print(\"The package numpy failed to load, please resolve the error or put it on the python path (pip install numpy) or\"\n",
    "          \" http://scipy.org/\")\n",
    "try:\n",
    "    import visa\n",
    "except:\n",
    "    print(\"The package vise failed to load, please resolve the error or put it on the python path (pip install pyvisa) or\"\n",
    "          \"https://pyvisa.readthedocs.io/en/stable/getting.html\")\n",
    "#-----------------------------------------------------------------------------\n",
    "# Function Definitions\n",
    "\n",
    "# Create a function to do the organization currently in the script\n",
    "def parse_measurements(measurement_list):\n",
    "    \"\"\"Given a list of measurements\n",
    "    returns a dictionary of the form {measurement.name:{frequency_list:[],peak_value_list:[]}}\"\"\"\n",
    "    output_dictionary={}\n",
    "    for index,measurement in enumerate(measurement_list):\n",
    "        if measurement.name in output_dictionary.keys():\n",
    "            output_dictionary[measurement.name][\"frequency_list\"].append(measurement.frequency)\n",
    "            output_dictionary[measurement.name][\"peak_value_list\"].append(measurement.peak_value)\n",
    "        else:\n",
    "            output_dictionary[measurement.name]={\"frequency_list\":[],\"peak_value_list\":[]}\n",
    "            output_dictionary[measurement.name][\"frequency_list\"].append(measurement.frequency)\n",
    "            output_dictionary[measurement.name][\"peak_value_list\"].append(measurement.peak_value)\n",
    "    return output_dictionary\n",
    "\n",
    "def plot_measurements(parsed_measurement_dictionary,**options):\n",
    "    \"\"\"Plots all measurements that have been parsed using parse_measurements, passing **options to the plot\"\"\"\n",
    "    # magic that will pass plot options in case you want to change them, it takes out the options we want \n",
    "    defaults={\"marker\":'o',\"linestyle\":\" \",\"display_legend\":True,\"save_figure\":True,\"directory\":None}\n",
    "    plot_options={}\n",
    "    for key,value in defaults.iteritems():\n",
    "        if key not in [\"display_legend\",\"save_figure\",\"directory\"]:\n",
    "            plot_options[key]=value\n",
    "    for key,value in options.iteritems():\n",
    "        if key not in [\"display_legend\",\"save_figure\",\"directory\"]:\n",
    "            plot_options[key]\n",
    "    # this will plot everything \n",
    "    pl.clf()\n",
    "    for name,measurement_dictionary in parsed_measurement_dictionary.iteritems():\n",
    "        pl.plot(measurement_dictionary[\"frequency_list\"],measurement_dictionary[\"peak_value_list\"],label=name,**plot_options)\n",
    "    if plot_options[\"display_legend\"]:\n",
    "        pl.legend()\n",
    "    pl.ylabel(\"Power (dBm)\")\n",
    "    pl.xlabel(\"Frequency (GHz)\")\n",
    "    if plot_options[\"directory\"] is None:\n",
    "        plot_options[\"directory\"]=os.getcwd()\n",
    "    try: \n",
    "        if plot_options[\"save_figure\"]:\n",
    "            pl.savefig(os.path.join(plot_options[\"directory\"],\"peaks.png\"))\n",
    "    except IOError as e:\n",
    "        print(\" Windows is stupid, {0} \".format(e))\n",
    "\n",
    "def build_measurement_list(measurement_frequencies,base_name=\"\",**measurement_settings):\n",
    "    \"\"\"Returns a list of measurements given a list of frequencies [[VNA_frequency,signal_generator_frequency,\n",
    "    measurement_frequency] ...], a base name and the other measurement settings\"\"\"\n",
    "    out_list=[]\n",
    "    for index,row in measurement_frequencies:\n",
    "        new_measurement=VNAMeasurementData(**measurement_settings)\n",
    "        new_measurement.VNA_frequency=row[0]\n",
    "        new_measurement.signal_generator_frequency=row[1]  \n",
    "        new_measurement.signal_generator_frequency=row[1]\n",
    "        new_measurement.name=\"sumone\"+str(index)\n",
    "        new_measurement.text_file_name=base_name+str(index)+\".txt\"\n",
    "        new_measurement.plot_file_name=base_name+str(index)+\".png\"\n",
    "        out_list.append(new_measurement)\n",
    "    return out_list\n",
    "def save_summary(measurement_list):\n",
    "    \"\"\"Saves a summary of all the measurements in measurement_list\"\"\"\n",
    "    summary_file=open(measurement_list[0].directory+\"summary.txt\",'w')\n",
    "    for measurement in measurement_list:\n",
    "        text=self.perform_measurement(measurement)\n",
    "        summary_file.write(text)\n",
    "    summary_file.close()\n",
    "#-----------------------------------------------------------------------------\n",
    "# Class Definitions    \n",
    "\n",
    "class VNAMeasurementData():\n",
    "    \"\"\"VNAMeasurementData holds the data for a single measurement \"\"\"\n",
    "    def __init__(self,**measurement_data):\n",
    "        \"\"\"Intializes the VNAMeasurementData class. When created, data can be passed as a dictionary\n",
    "        VNAMeasurementData(**{\"VNA_power\":0})\n",
    "        or as keyword arguments VNAMeasurementData(VNA_power=0)\"\"\"\n",
    "        defaults={\"directory\":None,\"record_name\":\"measurement\",\"text_file_name\":\"out.txt\",\n",
    "                 \"plot_file_name\":\"out.png\",\"VNA_power\":0,\"signal_generator_power\":-10,\"power_units\":\"dBm\",\n",
    "                 \"VNA_frequency\":10,\"signal_generator_frequency\":10,\"measurement_frequency\":10,\"frequency_units\":\"GHz\",\n",
    "                  \"frequency_span_units\":\"Hz\",\"save_plot\":True,\n",
    "                  \"save_text\":True,\"reference_level\":0,\"peak_value\":\"Not Measured\",\"frequency_span\":100}\n",
    "        self.initial_values={}\n",
    "        for key,value in defaults.iteritems():\n",
    "            self.initial_values[key]=value\n",
    "        for key,value in measurement_data.iteritems():\n",
    "            self.initial_values[key]=value\n",
    "        # now make the values in self.intial_values attributes\n",
    "        for key,value in self.initial_values.iteritems():\n",
    "            self.__dict__[key]=value\n",
    "        try:\n",
    "            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)\n",
    "        except:\n",
    "            print(\"could not calculate resonant bandwidth from frequency span\")\n",
    "            \n",
    "    # in general methods and functions should have verbs as names so instead of summary we write get_summary\n",
    "    def get_summary(self,summary_elements=None):\n",
    "        \"\"\"Returns a text summary of the measurement data, if you need them in a different order pass a list of \n",
    "        attribute names [\"name\",\"text_file_name\",...]\"\"\"\n",
    "        if summary_elements==None:\n",
    "            summary_order=[\"name\",\"test_file_name\",\"plot_file_name\",\"VNA_frequency\",\n",
    "                           \"signal_generator_frequency\",\"measurement_frequency\",\"resonator_bandwidth\",\n",
    "                          \"frequency_span\",\"signal_generator_power\",\"VNA_power\",\"reference_level\",\"peak_value\"]\n",
    "        output_string=\"\"\n",
    "        for index,element in enumerate(summary_elements):\n",
    "            if index == 0:\n",
    "                output_string+=\"{1} : {0}\".format(element,self.__dict__[element])\n",
    "            else:\n",
    "                output_string+=\",{1} : {0}\".format(element,self.__dict__[element])\n",
    "        return output_string\n",
    "    def __str__(self):\n",
    "        \"\"\"Controls the behavior when str(VNAMeasurmentData) or print is called\"\"\"\n",
    "        return self.get_summary()\n",
    "    def reset_measurement_data(self):\n",
    "        \"\"\"Restores to the initialized value\"\"\"\n",
    "        for key,value in self.initial_values.iteritems():\n",
    "            self.__dict__[key]=value\n",
    "        try:\n",
    "            self.resonant_bandwidth=np.ceil(self.frequency_span/600.0)\n",
    "        except:\n",
    "            print(\"could not calculate resonant bandwidth from frequency span\")\n",
    "        \n",
    "class FakeInstrument():\n",
    "    \"\"\"FakeInstrument is a class for testing purposes\"\"\"\n",
    "    def __init__(self,instrument_address):\n",
    "        self.instrument_address=instrument_address\n",
    "        pass\n",
    "    def write(self,command):\n",
    "        \"\"\"Prints a write statement\"\"\"\n",
    "        print(\"Writing the command {0} to {1}\".format(command,self.instrument))\n",
    "    def read(self):\n",
    "        return 0\n",
    "    def query(self,command):\n",
    "        \"\"\"Prints a write statement and returns a fake value\"\"\"\n",
    "        print(\"Writing the command {0} to {1}\".format(command,self.instrument))\n",
    "        return 0\n",
    "    def close(self):\n",
    "        print(\"Writing the command {0} to {1}\".format(\"close()\",self.instrument))\n",
    "    def clear(self):\n",
    "        print(\"The instrument {0} was cleared\".format(self.instrument))\n",
    "        \n",
    "class VNAFrequencySweepExperiment():\n",
    "    \"\"\"VNAFrequencySweepExperiment controls the VNA, signal generator and spectrum analyzer\"\"\"\n",
    "    def __init__(self,**set_up):\n",
    "        \"\"\"Intializes the VNAFrequencySweepExperimentClass, if the gpib address of the instruments are not the default\n",
    "        pass them as VNA_address=u'GPIB0::24::INSTR',spectrum_analyzer_address=u'GPIB0::18::INSTR',\n",
    "        signal_generator_address=u'GPIB0::17::INSTR' or \n",
    "        **{'VNA_address':u'GPIB0::24::INSTR','spectrum_analyzer_address':u'GPIB0::18::INSTR',\n",
    "        'signal_generator_address':u'GPIB0::17::INSTR'} \"\"\"\n",
    "        defaults={'VNA_address':VNA_DEFAULT_ADDRESS,'spectrum_analyzer_address':SPECTRUM_ANALYZER_DEFAULT_ADDRESS,\n",
    "        'signal_generator_address':SIGNAL_GENERATOR_DEFAULT_ADDRESS}\n",
    "        self.instrument_addresses={}\n",
    "        for key,value in defaults.iteritems():\n",
    "            self.instrument_addresses[key]=value\n",
    "        for key,value in setup.iteritems():\n",
    "            self.instrument_addresses[key]=value\n",
    "        self.measurement_list=[]\n",
    "        try:\n",
    "            self.resource_manager=visa.ResourceManager()       \n",
    "            self.open_instruments()\n",
    "        except:\n",
    "            print(\"Could not open the resources, entering testing mode all data is fake\")\n",
    "            self.VNA=self.resource_manager.open_resource(self_instrument_addresses[\"VNA_Address\"])\n",
    "            self.signal_generator=FakeInstrument(self_instrument_addresses[\"signal_generator_address\"])\n",
    "            self.spectrum_analyzer=FakeInstrument(self_instrument_addresses[\"spectrum_analyzer_address\"])\n",
    "            \n",
    "        \n",
    "    def open_instruments(self):\n",
    "        \"\"\"Opens the instruments and creates the attributes VNA, signal_generator and spectrum_analyzer\"\"\"\n",
    "        self.VNA=self.resource_manager.open_resource(self_instrument_addresses[\"VNA_Address\"])\n",
    "        self.signal_generator=self.resource_manager.open_resource(self_instrument_addresses[\"signal_generator_address\"])\n",
    "        self.spectrum_analyzer=self.resource_manager.open_resource(self_instrument_addresses[\"spectrum_analyzer_address\"])\n",
    "        \n",
    "        \n",
    "    def intialize_frequency_sweep(self):\n",
    "        #clear input and output buffers\n",
    "        self.spectrum_analyzer.clear()\n",
    "        self.signal_generator.clear()\n",
    "        self.VNA.clear()\n",
    "        #initialize to preset state\n",
    "        self.spectrum_analyzer.write(\"IP\")\n",
    "        self.signal_generator.write(\"*RST\")\n",
    "        self.VNA.write(\"SYST:PRESET\")\n",
    "        #single sweep mode on spectrum analyzer\n",
    "        self.spectrum_analyzer.write(\"SNGLS;\")\n",
    "        #set no timeout\n",
    "        self.spectrum_analyzer.timeout=float('+inf')\n",
    "        #set binary trace output\n",
    "        self.spectrum_analyzer.write(\"TDF B;\")\n",
    "        #set VNA to CW mode\n",
    "        self.VNA.write('SENS1:SWE:TYPE CW')\n",
    "        #set VNA sweep time to 1 hour to make sure source is on during measurement\n",
    "        self.VNA.write(\"SENS1:SWE:TIME 3600\")\n",
    "        \n",
    "    def perform_measurement(self,measurement):\n",
    "        \"\"\"Performs a single measurement\"\"\"\n",
    "        #sets source to power dBm and frequency cw in GHz.\n",
    "        #saves a textfile with txtfname\n",
    "        #if enabled,saves a plot with plot_file_name\n",
    "        #returns peak val\n",
    "        #set span\n",
    "        self.spectrum_analyzer.write(\"SP \"+str(measurement.frequency_span)+\"HZ;\")\n",
    "        #set resolution bandwidth\n",
    "        self.spectrum_analyzer.write(\"RB \"+str(measurement.resonant_bandwidth))\n",
    "        #set reference level\n",
    "        self.spectrum_analyzer.write(\"RL \"+str(measurement.reference_level))\n",
    "        #measurement center frequency\n",
    "        self.spectrum_analyzer.write(\"CF \"+str(measurement.measurement_frequency)+\"GHZ;\")\n",
    "        #set up generator\n",
    "        self.signal_generator.write(\"POW:AMPL \"+str(measurement.signal_generator_power)+\";\")\n",
    "        self.signal_generator.write(\"FREQ:CW \"+str(measurement.signal_generator_frequency)+\"E+9;\") \n",
    "        self.signal_generator.write(\"OUTP 1;\")\n",
    "        #set up VNA\n",
    "        self.VNA.write(\"SOUR1:POW \"+str(measurement.VNA_power))\n",
    "        self.VNA.write(\"SENS1:FREQ \"+str(measurement.VNA_frequency)+\"e9\")\n",
    "        #measure a sweep\n",
    "        #instruments.SA.write(\"VAVG 10;\")\n",
    "        #meastime=float(instruments.SA.query(\"ST?\"))+2\n",
    "        self.spectrum_analyzer.write(\"TS;\")\n",
    "        #aquire a trace\n",
    "        self.spectrum_analyzer.write(\"TRA?;\")\n",
    "        data=self.spectrum_analyzer.read_raw()\n",
    "        dataint= np.array(struct.unpack('>' + 'H'*601, data))\n",
    "        #specify vertical scale\n",
    "        RL=float(self.spectrum_analyzer.query(\"RL?\"))\n",
    "        dBperDev=float(self.spectrum_analyzer.query(\"LG?\"))\n",
    "        #rescale binary data\n",
    "        datadB=RL+dBperDev*(dataint/60.0-10)\n",
    "        #find span \n",
    "        FA=float(self.spectrum_analyzer.query('FA?'))\n",
    "        FB=float(self.spectrum_analyzer.query('FB?'))\n",
    "        frequency=np.linspace(FA,FB,601)\n",
    "        if measurement.save_text:\n",
    "            f=open(measurement.directory+measurement.text_file_name,'w')\n",
    "            for i in range(len(datadB)):\n",
    "                f.write(str(frequency[i])+\" \"+str(dataint[i])+\" \"+str(datadB[i])+\"\\n\")\n",
    "            f.close()\n",
    "        if measurement.save_plot:\n",
    "            pl.clf()\n",
    "            pl.plot(frequency-measurement.measurement_frequency*1e9,datadB)\n",
    "            pl.xlim([FA-measurement.measurement_frequency*1e9,FB-measurement.measurement_frequency*1e9])\n",
    "            pl.ylim([RL-120,RL])\n",
    "            pl.ylabel(\"power (dBm)\")\n",
    "            pl.xlabel(\"Frequency offset (Hz)\")\n",
    "            pl.savefig(measurement.directory+measurement.plot_file_name)\n",
    "        measurement.peak_value=np.max(datadB)          \n",
    "        #return measurement\n",
    "                                    \n",
    "    def shuffle_measurement_list(self):\n",
    "        \"\"\"Randomly shuffles current measurement list\"\"\"\n",
    "        self.measurement_list=random.shuffle(self.measurement_list)\n",
    "\n",
    "    def perform_all_measurements(self,save_all_text=True,plot_all=True):\n",
    "        \"\"\"Performs all measurements in measurement list\"\"\"\n",
    "        for measurement in self.measurement_list:\n",
    "            self.perform_measurement(measurement)\n",
    "            \n",
    "    def close(self):\n",
    "        \"\"\"Close all\"\"\"\n",
    "        self.VNA.close()\n",
    "        self.signal_generator.close()\n",
    "        self.spectrum_analyzer.close()\n",
    "        \n",
    "    def save_summary(self):\n",
    "        \"\"\"Saves a summary of all the measurements in measurement_list\"\"\"\n",
    "        save_summary(self.measurement_list)\n",
    " \n",
    "#-----------------------------------------------------------------------------\n",
    "# Script Definitions    \n",
    "\n",
    "\n",
    "\n",
    "#-----------------------------------------------------------------------------\n",
    "# Module Runner   \n",
    "\n",
    " if __name__ == '__main__':\n",
    "    measure_two_frequency_script()\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
