One of the advantages of Python scripting with the AWR Design Environment (AWRDE) is the flexibility in graphing that is available with Python. Different viewpoints of the same data can be a big benefit in solving engineering problems. Presenting data in highly customizable graphs is a further advantage of extracting data from AWRDE and plotting the results in Python.
Some of the graphing capabilities that Python can offer are:
Another area that may be of interest is to read trace data from AWRDE into Python then perform some post processing of the trace data such as filtering, smoothing, etc. and then export the results back into AWRDE. Whether plotting data in Python or performing some sort of data processing in Python, importing and exporting data between AWRDE and Python is involved. This article explains some of the graphing related techniques using Python with AWRDE including:
All of this information is available in the AWR API Scripting Guide , Working with Graphs section. However as mentioned in AWR Scripting in Python: Using the AWRDE API Scripting Guide the API Scripting guide is written for the SAX Basic implementation of Visual Basic for Applications (VBA). The AWR API Scripting Guide will still be required for a complete list of the command syntax options. It is the intent that the example code in this article aids in interpreting the SAX Basic syntax found in the AWR API Scripting Guide into Python syntax.
This article assumes that Python, a Python IDE and the pyawr module have been installed. See AWR Scripting in Python: Getting Started and Installation for installation instructions. Additionally a Python plotting module will need to be installed. This article uses the popular Matplotlib module. Please refer to https://matplotlib.org/ for instructions on installing Matplotlib as well as detailed tutorials, API syntax, and plotting examples.
At the beginning of the Python script, the appropriate modules must be installed. The following examples use numpy, matplotlib and pyawr:
import numpy as np #Import numpy math module import pyawr.mwoffice as mwo #Import pyawr interface to AWRDE import matplotlib.pyplot as plt #Import pyplot module from Matplotlib
Next a link between Python and AWRDE must be established:
awrde = mwo.CMWOffice() #Establish link to AWRDE project = awrde.Project #Create "project" variable
The project variable is a convenience item. More information on linking Python to AWRDE can be found in AWR Scripting in Python: Using the AWRDE API Scripting Guide.
The coding examples in this article use the AWRDE installed project LPF_Lumped.emp.
Matplotlib is an open-source Python module that is highly configurable and can be used to present data in almost any type of graph imaginable. Higher level plotting functions are available in the submodule pyplot and this is what will be used for the following examples. For even more advanced graphics programming, other submodules are available within the umbrella of the matplotlib module. The intent of this article is to mostly show the AWRDE graphing API commands, not to cover matplotib programming, please refer to the above matplotlib link for matplotlib commands beyond what is introduced here.
Before showing the AWRDE graph API commands, a couple of very basic matplotib plotting examples will be shown. Later on data originating from AWRDE will be used in these types of graphs. For the simplest of graphs, only two matplotlib lines of code are required: one to plot the data and the other to display the graph window. In the following example, x and y vectors are created for a sine wave:
#Create data x_ay = np.linspace(0,100,101) #Create vector from 0 to 100 with 101 points y_ay = np.sin(2*np.pi*x_ay/100) #Sine wave with 1 period over the x_ay range plt.plot(x_ay, y_ay) #pyplot plot function plt.show() #Display plot window
This code produces this graph:
Further embellishments include adding titles, axis labels, grids and setting the axis ranges:
#Create data x_ay = np.linspace(0,100,101) #Create vector from 0 to 100 with 101 points y_ay = np.sin(2*np.pi*x_ay/100) #Sine wave with 1 period over the x_ay range # plt.xlim([0,100]) #x-axis range plt.ylim([-2,2]) #y-axis range plt.title('Sine Wave', fontsize=16, fontweight='bold') #Plot title plt.xlabel('x-axis values', fontsize=14) #x-axis label plt.ylabel('y-axis values', fontsize=14) #y-axis label plt.grid(True, linestyle=':') #Enable grid. Set linestyle to dots # plt.plot(x_ay, y_ay, color='r', linewidth=2) #plot with red trace color and specified width plt.show() #Display plot window
Producing this output:
List All the Graphs in the Project
The collection variable Graphs contains of all the graphs in the project. The following code shows how to loop through each graph in the project and print the name of the graph and its type
for graph in project.Graphs: #Loop through all the Graphs in the project print(graph.Name) #Print graph name print(mwo.mwGraphType(graph.Type)) #Print graph type (Rectangular, Smith, etc.)
From above, the variable project is equal to awrde.Project
This code produces this result from the installed example LPF_Lumped.emp
Edit Graph Properties
Shown here are a few examples of editing graph properties
graph = project.Graphs('Passband and Stopband') #Set the graph variable to a particular graph graph.Title = 's21 and s11' #Change graph title x_axis = graph.Axes #Assign x-axis to variable x_axis.MinimumScale = 200 #Set x-axis minimum to 200 MHz x_axis.MaximumScale = 700 #Set x-axis maximum to 700 MHz x_axis.MajorGridlinesStep = 50 #Set x-axis step size to 50 MHz
Create a New Graph
These commands add a Smith chart to the LPF_Lumped.emp example project
if project.Graphs.Exists('LPF Smith Chart'): #Check to see if the Graph exists project.Graphs.Remove('LPF Smith Chart') #Delete graph if it exists # #Add new graph with graph name: 'LPF Smith Chart' and type Smith Chart graph = project.Graphs.Add('LPF Smith Chart', mwo.mwGraphType.mwGT_SmithChart)
In case the graph already exists, the first two lines delete the graph first.
List of Measurements in a Graph
Measurements collection contains all the measurements within a single graph. This code demonstrate returning the number of measurements in a graph, then for each measurement return: measurement name, source document and enabled state
graph = project.Graphs('Passband and Stopband') NumberOfMeasurements = graph.Measurements.Count #Number of traces on graph for meas in graph.Measurements: #Loop through all the measurements in the graph Name = meas.Name #Name as shown in project tree for the measurement SourceDocument = meas.Source #Schematic where measurement originates (Source Document) IsEnabled = meas.Enabled #Enabled or disabled status
Adding, Deleting and Accessing a Single Measurement
When adding a measurement, the pass parameters are source document (schematic where measurement originates) and the measurement name. Deleting the measurement uses a single pass parameter which is source document and measurement name separated by a colon. Accessing a measurement can use either index or measurement name.
graph = project.Graphs('Passband and Stopband') #Add measurement to graph graph.Measurements.Add('LPF','DB(|S(1,2)|)') #Parameters are (Source Document, Measurement Name) #Remove measurement from graph graph.Measurements.Remove('LPF:DB(|S(1,2)|)') #Access a measurement meas = graph.Measurements #Access by index meas = graph.Measurements('LPF:DB(|S(2,1)|)') #Access by measurement name #Toggle enable/disable meas.Enabled = True meas.Enabled = False
Reading Measurement Scalar Data
Scalar data is single Y data point such as magnitude, phase, real, etc. An example of of multiple Y data points would be complex data (for example from a Smith chart). The first method is to read the entire measurement into an array at once. The meas.TraceValues() method takes a single value of 1 as the pass parameter for data that does not include swept variables. This function returns an array of tuples with each data point an (x ,y) tuple. The numpy function asarray() can convert this array into a standard array
graph = project.Graphs('Passband and Stopband') meas = graph.Measurements('LPF:DB(|S(2,1)|)') #Method 1: Read measurment into an array MeasData_Tuple = meas.TraceValues(1) #Returns set of (x,y) tuples for each data point MeasData_ay = np.asarray(MeasData_Tuple) #numpy function to covert tuple into an array Freq_ay = MeasData_ay[:,0] #Extract x data vector s21_ay = MeasData_ay[:,1] #Extract y data vector plt.plot(Freq_ay, s21_ay) #Matplotlib plot function plt.show() #Display graph window
Resulting Python graph:
The second method is to read individual data points. For instance only select data points may want to be read instead of the entire measurement array.
graph = project.Graphs('Passband and Stopband') meas = graph.Measurements('LPF:DB(|S(2,1)|)') #Method 2: Read individual measuremnt data points Freq_list = list() s21_list = list() NumDataPoints = meas.XPointCount #Number of data points in the measurement for i in range(1, NumDataPoints, 10): #Loop through every 10th data point Freq_list.append(meas.XValue(i)) #Read x-data point s21_list.append(meas.YValue(i,1)) #Read y-data point plt.plot(Freq_list, s21_list, marker='o') plt.show()
The XValue() and YValue() methods are indexed beginning at a value of 1. The resulting graph looks like:
Reading Multi-value Y-axis Data
For y-axis data that may contain two values, for example complex data, the measurement read process is similar to that of scalar data. For the trace array read (Method 1) , the tuple contains three elements: (x, y_real and y_imaginary). The code here adds a Smith Chart to the LPF_Lumped.emp example and reads the complex data measurement:
#Add new Smith Chart graph if project.Graphs.Exists('LPF Smith Chart'): #Check to see if the Graph exists project.Graphs.Remove('LPF Smith Chart') #Delete graph if it exists #end if # #Add new Smith Chart graph graph = project.Graphs.Add('LPF Smith Chart',mwo.mwGraphType.mwGT_SmithChart) meas = graph.Measurements.Add('LPF','S(1,1)') #Add measurement project.Simulator.Analyze() #Simulate # MeasData_Tuple = meas.TraceValues(1) #Returns set of (x, y_real, y_imag) tuples #for each data point MeasData_ay = np.asarray(MeasData_Tuple) #numpy function to covert tuple into an array Freq_ay = MeasData_ay[:,0] #Extract x-axis vector s11_Real_ay = MeasData_ay[:,1] #Extract y-axis real vector s11_Imag_ay = MeasData_ay[:,2] #Extract y-axis imaginary vector Legend_list = list() #Create list for plot legend # plt.plot(Freq_ay, s11_Real_ay, color='b') #Plot real data Legend_list.append('s11 Real') #Update legend plt.plot(Freq_ay, s11_Imag_ay, color='g') #Plot imaginary data Legend_list.append('s11 Imaginary') #Update legend # #Add legend to graph LegendHandle = plt.legend(Legend_list, loc='upper left', ncol=1) plt.setp(LegendHandle.get_texts(), fontsize=10) # plt.show()
Resulting Python graph:
The syntax for reading individual points for the y-axis data would be:
Freq_list.append(meas.XValue(i)) #Read x-data point s11_real_list.append(meas.YValue(i,1)) #Read y-data real point s11_imag_list.append(meas.YValue(i,2)) #Read y-data imaginary point
Reading Swept Variable Data
Swept variable simulation is an example of multiple traces in a single measurement. This code first adds a SWPVAR element to the LPF schematic in the LPF_Lumped.emp example and then reads in all the traces from the swept variable s11 measurement.
#Add SWPVAR to schematic to sweep the inductace value of INDQ.L3 schem = project.Schematics('LPF') #Point schem variable to the LPF schematic schem.Equations.Add('L3 = 33', 1000, -1300) #Add equation at specific x,y location in schematic element = schem.Elements('INDQ.L3') #Point element variable to inductor L3 element.Parameters('L').ValueAsString = 'L3' #Replace L parameter with the eqquation varaible element = schem.Elements.Add('SWPVAR',1000,-2000) #Add SWPVAR element at specific location element.Parameters('Values').ValueAsString = 'swpstp(25,35,5)' #Set sweep parameters element.Parameters('VarName').ValueAsString = '"L3"' #Set sweep variable project.Simulator.Analyze() #Simulate # graph = project.Graphs('Passband and Stopband') meas = graph.Measurements('LPF:DB(|S(1,1)|)') Ydata_list = list() SweepLabel_list = list() Legend_list = list() NumTraces = meas.TraceCount #Number of traces in the measurement for trace_idx in range(1, NumTraces+1): #Loop through traces beginning at index 1 MeasData_Tuple = meas.TraceValues(trace_idx) #Read data is Tuple for each data point MeasData_ay = np.asarray(MeasData_Tuple) #numpy function to covert to array if trace_idx == 1: Freq_ay = MeasData_ay[:,0] #end if Ydata_list.append(MeasData_ay[:,1]) # #Sweep label value SweepLabel_list.append(meas.SweepLabels(trace_idx).Item(1).Value) # #Sweep label name SweepLabel_Name = meas.SweepLabels(1).Item(1).Name # for trace_idx in range(NumTraces): plt.plot(Freq_ay, Ydata_list[trace_idx]) Legend_list.append(SweepLabel_Name+' = '+str(SweepLabel_list[trace_idx])) #Add legend to graph # LegendHandle = plt.legend(Legend_list, loc='upper left', ncol=1) plt.setp(LegendHandle.get_texts(), fontsize=10) # plt.show()
Resulting Python graph:
Markers are not part of the Measurement collection, but rather part of the Graph collect. The following code shows how to add a marker to a particular measurement within the graph, get the marker values, set marker to min and max y-axis positions and finally how to use the search marker method
graph = project.Graphs('Passband and Stopband') # #Add marker on the S(2,1) measurement, trace index 1, x-axis value=300 MHz marker = graph.Markers.Add('LPF:DB(|S(2,1)|)', 1, 300e6) print('Maker Name:',marker.Name) #Marker name (m1, m2, etc.) print('Marker Measurement:',marker.Measurement) #Marker measurement Name print('Maker DataValue(1):',marker.DataValue(1)) #Marker x-axis value print('Maker DataValue(2):',marker.DataValue(2)) #Marker y-axis value # marker.MoveToMinimum() #Move Marker to minimum y-axis point marker.MoveToMaximum() #Move Marker to maximum y-axis point # #Search marker -3 dB relative to max marker position. Search right along x-axis marker.Search(-3, mwo.mwMarkerSearchMode.mwMST_Delta,\ mwo.mwMarkerSearchDirection.mwMSD_SearchRight,\ mwo.mwMarkerSearchVariable.mwMSV_Y)
Exporting data from AWRDE involves writing a text file into the AWRDE project directory. Then plot the data in AWRDE using Data > PlotCol measurement. This code create sine wave x,y points in Python, writes the text file, imports the data in AWRDE and finally creates a graph and plots the imported data:
#Create data in Python x_ay = np.linspace(0,100,101) #Create vector from 0 to 100 with 101 points y_ay = np.sin(2*np.pi*x_ay/100) #Sine wave with 1 period over the x_ay range # #Write data to text file in the AWRDE project directory Path = project.Path #Project directory path fw = open(Path+'SineWave.txt', 'w+') #Low level Python file write for i in range(len(x_ay)): fw.write(str(x_ay[i])+' '+str(y_ay[i])+'\n') #Write space delimited x,y per line fw.close() # #Import data file into AWRDE as text data file type project.DataFiles.Import('SineWave','SineWave.txt',mwo.mwDataFileType.mwDFT_TXT) #Add new rectangular graph graph = project.Graphs.Add('SineWave',mwo.mwGraphType.mwGT_Rectangular) #Add measurement to graph graph.Measurements.Add('SineWave','PlotCol(1,2)') project.Simulator.Analyze() #Simultate
The resulting AWRDE graph: