|  | #!/usr/bin/env python3 | 
|  | """ | 
|  | cace_makeplot.py | 
|  | Plot routines for CACE using matplotlib | 
|  | """ | 
|  |  | 
|  | import re | 
|  | import os | 
|  | import matplotlib | 
|  | from matplotlib.figure import Figure | 
|  |  | 
|  | # Warning: PIL Tk required, may not be in default install of python3. | 
|  | # For Fedora, for example, need "yum install python-pillow-tk" | 
|  |  | 
|  | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg | 
|  | from matplotlib.backends.backend_agg import FigureCanvasAgg | 
|  |  | 
|  | def twos_comp(val, bits): | 
|  | """compute the 2's compliment of int value val""" | 
|  | if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 | 
|  | val = val - (1 << bits)        # compute negative value | 
|  | return val                         # return positive value as is | 
|  |  | 
|  | def makeplot(plotrec, results, variables, parent = None): | 
|  | """ | 
|  | Given a plot record from a spec sheet and a full set of results, generate | 
|  | a plot.  The name of the plot file and the vectors to plot, labels, legends, | 
|  | and so forth are all contained in the 'plotrec' dictionary. | 
|  | """ | 
|  |  | 
|  | binrex = re.compile(r'([0-9]*)\'([bodh])', re.IGNORECASE) | 
|  | # Organize data into plot lines according to formatting | 
|  |  | 
|  | if 'type' in plotrec: | 
|  | plottype = plotrec['type'] | 
|  | else: | 
|  | plottype = 'xyplot' | 
|  |  | 
|  | # Find index of X data in results | 
|  | if plottype == 'histogram': | 
|  | xname = 'RESULT' | 
|  | else: | 
|  | xname = plotrec['xaxis'] | 
|  | rlen = len(results[0]) | 
|  | try: | 
|  | xidx = next(r for r in range(rlen) if results[0][r] == xname) | 
|  | except StopIteration: | 
|  | return None | 
|  |  | 
|  | # Find unique values of each variable (except results, traces, and iterations) | 
|  | steps = [[0]] | 
|  | traces = [0] | 
|  | bmatch = binrex.match(results[1][0]) | 
|  | if bmatch: | 
|  | digits = bmatch.group(1) | 
|  | if digits == '': | 
|  | digits = len(results[2][0]) | 
|  | else: | 
|  | digits = int(digits) | 
|  | cbase = bmatch.group(2) | 
|  | if cbase == 'b': | 
|  | base = 2 | 
|  | elif cbase == 'o': | 
|  | base = 8 | 
|  | elif cbase == 'd': | 
|  | base = 10 | 
|  | else: | 
|  | base = 16 | 
|  | binconv = [[base, digits]] | 
|  | else: | 
|  | binconv = [[]] | 
|  |  | 
|  | for i in range(1, rlen): | 
|  | lsteps = [] | 
|  |  | 
|  | # results labeled 'ITERATIONS', 'RESULT', 'TRACE', or 'TIME' are treated as plot vectors | 
|  | isvector = False | 
|  | if results[0][i] == 'ITERATIONS': | 
|  | isvector = True | 
|  | elif results[0][i] == 'RESULT': | 
|  | isvector = True | 
|  | elif results[0][i] == 'TIME': | 
|  | isvector = True | 
|  | elif results[0][i].split(':')[0] == 'TRACE': | 
|  | isvector = True | 
|  |  | 
|  | # results whose labels are in the 'variables' list are treated as plot vectors | 
|  | if isvector == False: | 
|  | if variables: | 
|  | try: | 
|  | varrec = next(item for item in variables if item['condition'] == results[0][i]) | 
|  | except StopIteration: | 
|  | pass | 
|  | else: | 
|  | isvector = True | 
|  |  | 
|  | # those results that are not traces are stepped conditions (unless they are constant) | 
|  | if isvector == False: | 
|  | try: | 
|  | for item in list(a[i] for a in results[2:]): | 
|  | if item not in lsteps: | 
|  | lsteps.append(item) | 
|  | except IndexError: | 
|  | # Diagnostic | 
|  | print("Error: Failed to find " + str(i) + " items in result set") | 
|  | print("Results set has " + len(results[0]) + " entries") | 
|  | print(str(results[0])) | 
|  | for x in range(2, len(results)): | 
|  | if len(results[x]) <= i: | 
|  | print("Failed at entry " + str(x)) | 
|  | print(str(results[x])) | 
|  | break | 
|  |  | 
|  | # 'ITERATIONS' and 'TIME' are the x-axis variable, so don't add them to traces | 
|  | # (but maybe just check that xaxis name is not made into a trace?) | 
|  | elif results[0][i] != 'ITERATIONS' and results[0][i] != 'TIME': | 
|  | traces.append(i) | 
|  | steps.append(lsteps) | 
|  |  | 
|  | # Mark which items need converting from digital.  Format is verilog-like.  Use | 
|  | # a format width that is larger than the actual number of digits to force | 
|  | # unsigned conversion. | 
|  | bmatch = binrex.match(results[1][i]) | 
|  | if bmatch: | 
|  | digits = bmatch.group(1) | 
|  | if digits == '': | 
|  | digits = len(results[2][i]) | 
|  | else: | 
|  | digits = int(digits) | 
|  | cbase = bmatch.group(2) | 
|  | if cbase == 'b': | 
|  | base = 2 | 
|  | elif cbase == 'o': | 
|  | base = 8 | 
|  | elif cbase == 'd': | 
|  | base = 10 | 
|  | else: | 
|  | base = 16 | 
|  | binconv.append([base, digits]) | 
|  | else: | 
|  | binconv.append([]) | 
|  |  | 
|  | # Support older method of declaring a digital vector | 
|  | if xname.split(':')[0] == 'DIGITAL': | 
|  | binconv[xidx] = [2, len(results[2][0])] | 
|  |  | 
|  | # Which stepped variables (ignoring X axis variable) have more than one value? | 
|  | watchsteps = list(i for i in range(1, rlen) if len(steps[i]) > 1 and i != xidx) | 
|  |  | 
|  | # Diagnostic | 
|  | # print("Stepped conditions are: ") | 
|  | # for j in watchsteps: | 
|  | #      print(results[0][j] + '  (' + str(len(steps[j])) + ' steps)') | 
|  |  | 
|  | # Collect results.  Make a separate record for each unique set of stepped conditions | 
|  | # encountered.  Record has (X, Y) vector and a list of conditions. | 
|  | pdata = {} | 
|  | for item in results[2:]: | 
|  | if xname.split(':')[0] == 'DIGITAL' or binconv[xidx] != []: | 
|  | base = binconv[xidx][0] | 
|  | digits = binconv[xidx][1] | 
|  | # Recast binary strings as integers | 
|  | # Watch for strings that have been cast to floats (need to find the source of this) | 
|  | if '.' in item[xidx]: | 
|  | item[xidx] = item[xidx].split('.')[0] | 
|  | a = int(item[xidx], base) | 
|  | b = twos_comp(a, digits) | 
|  | xvalue = b | 
|  | else: | 
|  | xvalue = item[xidx] | 
|  |  | 
|  | slist = [] | 
|  | for j in watchsteps: | 
|  | slist.append(item[j]) | 
|  | istr = ','.join(slist) | 
|  | if istr not in pdata: | 
|  | stextlist = [] | 
|  | for j in watchsteps: | 
|  | if results[1][j] == '': | 
|  | stextlist.append(results[0][j] + '=' + item[j]) | 
|  | else: | 
|  | stextlist.append(results[0][j] + '=' + item[j] + ' ' + results[1][j]) | 
|  | pdict = {} | 
|  | pdata[istr] = pdict | 
|  | pdict['xdata'] = [] | 
|  | if stextlist: | 
|  | tracelegnd = False | 
|  | else: | 
|  | tracelegnd = True | 
|  |  | 
|  | for i in traces: | 
|  | aname = 'ydata' + str(i) | 
|  | pdict[aname] = [] | 
|  | alabel = 'ylabel' + str(i) | 
|  | tracename = results[0][i] | 
|  | if ':' in tracename: | 
|  | tracename = tracename.split(':')[1] | 
|  |  | 
|  | if results[1][i] != '' and not binrex.match(results[1][i]): | 
|  | tracename += ' (' + results[1][i] + ')' | 
|  |  | 
|  | pdict[alabel] = tracename | 
|  |  | 
|  | pdict['sdata'] = ' '.join(stextlist) | 
|  | else: | 
|  | pdict = pdata[istr] | 
|  | pdict['xdata'].append(xvalue) | 
|  |  | 
|  | for i in traces: | 
|  | # For each trace, convert the value from digital to integer if needed | 
|  | if binconv[i] != []: | 
|  | base = binconv[i][0] | 
|  | digits = binconv[i][1] | 
|  | a = int(item[i], base) | 
|  | b = twos_comp(a, digits) | 
|  | yvalue = b | 
|  | else: | 
|  | yvalue = item[i] | 
|  |  | 
|  | aname = 'ydata' + str(i) | 
|  | pdict[aname].append(yvalue) | 
|  |  | 
|  | fig = Figure() | 
|  | if parent == None: | 
|  | canvas = FigureCanvasAgg(fig) | 
|  | else: | 
|  | canvas = FigureCanvasTkAgg(fig, parent) | 
|  |  | 
|  | # With no parent, just make one plot and put the legend off to the side.  The | 
|  | # 'extra artists' capability of print_figure will take care of the bounding box. | 
|  | # For display, prepare two subplots so that the legend takes up the space of the | 
|  | # second one. | 
|  | if parent == None: | 
|  | ax = fig.add_subplot(111) | 
|  | else: | 
|  | ax = fig.add_subplot(121) | 
|  |  | 
|  | # fig.hold(True) | 
|  | for record in pdata: | 
|  | pdict = pdata[record] | 
|  |  | 
|  | # Check if xdata is numeric | 
|  | try: | 
|  | test = float(pdict['xdata'][0]) | 
|  | except ValueError: | 
|  | numeric = False | 
|  | xdata = [i for i in range(len(pdict['xdata']))] | 
|  | else: | 
|  | numeric = True | 
|  | xdata = list(map(float,pdict['xdata'])) | 
|  |  | 
|  | if plottype == 'histogram': | 
|  | ax.hist(xdata, histtype='barstacked', label=pdict['sdata'], stacked=True) | 
|  | else: | 
|  | for i in traces: | 
|  | aname = 'ydata' + str(i) | 
|  | alabl = 'ylabel' + str(i) | 
|  | ax.plot(xdata, pdict[aname], label=pdict[alabl] + ' ' + pdict['sdata']) | 
|  | # Diagnostic | 
|  | # print("Y values for " + aname + ": " + str(pdict[aname])) | 
|  |  | 
|  | if not numeric: | 
|  | ax.set_xticks(xdata) | 
|  | ax.set_xticklabels(pdict['xdata']) | 
|  |  | 
|  | if 'xlabel' in plotrec: | 
|  | if results[1][xidx] == '' or binrex.match(results[1][xidx]): | 
|  | ax.set_xlabel(plotrec['xlabel']) | 
|  | else: | 
|  | ax.set_xlabel(plotrec['xlabel'] + ' (' + results[1][xidx] + ')') | 
|  | else: | 
|  | # Automatically generate X axis label if not given alternate text | 
|  | xtext = results[0][xidx] | 
|  | if results[1][xidx] != '': | 
|  | xtext += ' (' + results[1][xidx] + ')' | 
|  | ax.set_xlabel(xtext) | 
|  |  | 
|  | if 'ylabel' in plotrec: | 
|  | if results[1][0] == '' or binrex.match(results[1][0]): | 
|  | ax.set_ylabel(plotrec['ylabel']) | 
|  | else: | 
|  | ax.set_ylabel(plotrec['ylabel'] + ' (' + results[1][0] + ')') | 
|  | else: | 
|  | # Automatically generate Y axis label if not given alternate text | 
|  | ytext = results[0][0] | 
|  | if results[1][0] != '' or binrex.match(results[1][0]): | 
|  | ytext += ' (' + results[1][0] + ')' | 
|  | ax.set_ylabel(ytext) | 
|  |  | 
|  | ax.grid(True) | 
|  | if watchsteps or tracelegnd: | 
|  | legnd = ax.legend(loc = 2, bbox_to_anchor = (1.05, 1), borderaxespad=0.) | 
|  | else: | 
|  | legnd = None | 
|  |  | 
|  | if legnd: | 
|  | legnd.set_draggable(True) | 
|  |  | 
|  | if parent == None: | 
|  | if not os.path.exists('ngspice/simulation_files'): | 
|  | os.makedirs('ngspice/simulation_files') | 
|  |  | 
|  | filename = 'ngspice/simulation_files/' + plotrec['filename'] | 
|  | # NOTE: print_figure only makes use of bbox_extra_artists if | 
|  | # bbox_inches is set to 'tight'.  This forces a two-pass method | 
|  | # that calculates the real maximum bounds of the figure.  Otherwise | 
|  | # the legend gets clipped. | 
|  | if legnd: | 
|  | canvas.print_figure(filename, bbox_inches = 'tight', | 
|  | bbox_extra_artists = [legnd]) | 
|  | else: | 
|  | canvas.print_figure(filename, bbox_inches = 'tight') | 
|  |  | 
|  | return canvas |