Plotting ngspice results with Python
Written by Lucas on
My favourite schematic capture program on Linux is Gschem, part of the gEDA project. The gEDA project contains a lot of open source tools which aid in electronic design automation. Think of programs to create schematics and PCB boards, to view Gerber files and more.
They're not as "user friendly" as OrCad or Multisim, but they work fine. For example, unlike most commercial schematic capture programs, most symbols in gEDA don't include a SPICE model by default, for most symbols you have to explicitly define which SPICE model to use when you want to start simulating. When you know what to do it's not that hard, and a project like spicelib makes it even easier.
gEDA also doesn't integrate a circuit simulator into its user interface. We do actually have some nice simulators on Linux, which are both command line based. Examples are ngspice and gnucap. Both work great, but they aren't tightly integrated as most commercial software. This can be a bit overwhelming for users who are just starting with gEDA.
Luckily, there's a nice tutorial on the gEDA wiki on how to simulate a circuit created with gEDA, and I'm not going to repeat that in this post. The point is, I don't like the builtin plot viewer of ngspice. When viewing a graph, you can't zoom, you can't move the viewport, it's just really basic. So, I've written a Python script which uses the matplotlib package to plot the results of ngspice, which looks a lot better than the builtin viewer. Details after the break!
Preparing your data
The Python script below reads a file with all calculated points of the plot. Of course, this file has to be generated by ngspice first, and that's done with 'wrdata' command, when you're in the ngspice interpreter. Usage:
wrdata file netname1 [netname2 ...]
Ngspice automatically adds the ".data" extension to the given filename. An example of a generated file:
1.000000e+00 1.000000e+00 1.000000e+00 9.999747e-01 1.047129e+00 1.000000e+00 1.047129e+00 9.999723e-01 1.096478e+00 1.000000e+00 1.096478e+00 9.999696e-01 1.148154e+00 1.000000e+00 1.148154e+00 9.999667e-01 1.202264e+00 1.000000e+00 1.202264e+00 9.999635e-01 1.258925e+00 1.000000e+00 1.258925e+00 9.999600e-01 1.318257e+00 1.000000e+00 1.318257e+00 9.999561e-01 1.380384e+00 1.000000e+00 1.380384e+00 9.999519e-01 1.445440e+00 1.000000e+00 1.445440e+00 9.999472e-01 1.513561e+00 1.000000e+00 1.513561e+00 9.999421e-01 1.584893e+00 1.000000e+00 1.584893e+00 9.999365e-01 1.659587e+00 1.000000e+00 1.659587e+00 9.999304e-01 ... ...
The first column are the used points on the x-axis for the first netname, the second the corresponding y-values, the third column contains the points on the x-axis for the second netname, and the fourth the y-values, and so forth (if you're plotting more netnames).
Script usage
usage: ngspice.py [-h] [--plotfunc PLOT_FUNC] file [legend [legend ...]]
Plots ngspice results
positional arguments:
file The file with the data generated by the ngspice wrdata
command
legend Optional legend names for the plotted lines
optional arguments:
-h, --help show this help message and exit
--plotfunc PLOT_FUNC, -p PLOT_FUNC
The matplotlib (pylab) plot function to use, default
pylab.plot
As you can see, you can enter labels for the lines which will be displayed in the legend, and you can give an other plot function (which must be in the pylab module). For example, if you want to want a logarithmic scale on the x-axis you can use the following command:
python ngspice.py -p semilogx simulation.data Vin Vout
Matplotlib is a HUGE library, and there are probably tons of more options for plotting, but for now this suits my needs. :)
Example
The builtin ngspice viewer:

A plot using matplotlib:

The code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | from numpy import array, zeros from pylab import plot, figure, grid, show, legend import argparse import sys class NGSpiceData: """ Class which represents ngspice simulation data """ def __init__(self, filename=""): """ Constructor, intializes member variables :Args: * filename (str): If filename is given it immediately loads the data from it """ self.data = None if filename: self.load_data(filename) def load_data(self, filename): """ Reads ngspice data file, generated by the ngspice 'wrdata' command, and puts it in a numpy array. :Args: * filename (str): The file to read :Returns: NumPy array containing the data """ with open(filename) as file: lines = file.readlines() for i, line in enumerate(lines): parts = [float(part) for part in line.split()] if self.data == None: self.data = zeros((len(lines), len(parts))) self.data[i] = array(parts) self.data = self.data.transpose() def plot(self, plot_function=plot, *legends, **kwargs): """ Plots the results in a single figure :Args: * plot_function (func): The Matplotlib plot function to use * legends (list): Other positional arguments given to the function will be used as legends * Keyword arguments are forwarded to the plot function """ if not 'figure' in kwargs: kwargs['figure'] = figure() i = 0 artists = [] while i <= len(self.data) / 2: artists.append(plot_function(self.data[i], self.data[i+1], **kwargs)) i += 2 grid(which='both') if legends: legend(artists, legends) class PyLabAction(argparse.Action): """ Retrieves a pylab function based on the argument """ def __call__(self, parser, namespace, values, option_string): import pylab func = getattr(pylab, values) if hasattr(pylab, values) else pylab.plot setattr(namespace, self.dest, func) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Plots ngspice results") parser.add_argument('file', type=str, nargs=1, action='store', help="The file with the data generated by the ngspice wrdata command") parser.add_argument('legend', type=str, nargs='*', default=[], help="Optional legend names for the plotted lines") parser.add_argument('--plotfunc', '-p', type=str, dest="plot_func", default=plot, action=PyLabAction, help="The matplotlib (pylab) plot function to use, default pylab.plot") arguments = parser.parse_args(sys.argv[1:]) results = NGSpiceData(arguments.file[0]) results.plot(arguments.plot_func, *arguments.legend) show() |
Filled under: electronics geda python
