blob: 76eb649785b278afbc5240199977c81102c0acf6 [file] [log] [blame]
emayecs5656b2b2021-08-04 12:44:13 -04001#!/usr/bin/env python3
emayecs5966a532021-07-29 10:07:02 -04002"""
3cace_makeplot.py
4Plot routines for CACE using matplotlib
5"""
6
7import re
8import os
9import matplotlib
10from matplotlib.figure import Figure
11from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
12from matplotlib.backends.backend_agg import FigureCanvasAgg
13
14def twos_comp(val, bits):
15 """compute the 2's compliment of int value val"""
16 if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
17 val = val - (1 << bits) # compute negative value
18 return val # return positive value as is
19
20def makeplot(plotrec, results, variables, parent = None):
21 """
22 Given a plot record from a spec sheet and a full set of results, generate
23 a plot. The name of the plot file and the vectors to plot, labels, legends,
24 and so forth are all contained in the 'plotrec' dictionary.
25 """
26
27 binrex = re.compile(r'([0-9]*)\'([bodh])', re.IGNORECASE)
28 # Organize data into plot lines according to formatting
29
30 if 'type' in plotrec:
31 plottype = plotrec['type']
32 else:
33 plottype = 'xyplot'
34
35 # Find index of X data in results
36 if plottype == 'histogram':
37 xname = 'RESULT'
38 else:
39 xname = plotrec['xaxis']
40 rlen = len(results[0])
41 try:
42 xidx = next(r for r in range(rlen) if results[0][r] == xname)
43 except StopIteration:
44 return None
45
46 # Find unique values of each variable (except results, traces, and iterations)
47 steps = [[0]]
48 traces = [0]
49 bmatch = binrex.match(results[1][0])
50 if bmatch:
51 digits = bmatch.group(1)
52 if digits == '':
53 digits = len(results[2][0])
54 else:
55 digits = int(digits)
56 cbase = bmatch.group(2)
57 if cbase == 'b':
58 base = 2
59 elif cbase == 'o':
60 base = 8
61 elif cbase == 'd':
62 base = 10
63 else:
64 base = 16
65 binconv = [[base, digits]]
66 else:
67 binconv = [[]]
68
69 for i in range(1, rlen):
70 lsteps = []
71
72 # results labeled 'ITERATIONS', 'RESULT', 'TRACE', or 'TIME' are treated as plot vectors
73 isvector = False
74 if results[0][i] == 'ITERATIONS':
75 isvector = True
76 elif results[0][i] == 'RESULT':
77 isvector = True
78 elif results[0][i] == 'TIME':
79 isvector = True
80 elif results[0][i].split(':')[0] == 'TRACE':
81 isvector = True
82
83 # results whose labels are in the 'variables' list are treated as plot vectors
84 if isvector == False:
85 if variables:
86 try:
87 varrec = next(item for item in variables if item['condition'] == results[0][i])
88 except StopIteration:
89 pass
90 else:
91 isvector = True
92
93 # those results that are not traces are stepped conditions (unless they are constant)
94 if isvector == False:
95 try:
96 for item in list(a[i] for a in results[2:]):
97 if item not in lsteps:
98 lsteps.append(item)
99 except IndexError:
100 # Diagnostic
101 print("Error: Failed to find " + str(i) + " items in result set")
102 print("Results set has " + len(results[0]) + " entries")
103 print(str(results[0]))
104 for x in range(2, len(results)):
105 if len(results[x]) <= i:
106 print("Failed at entry " + str(x))
107 print(str(results[x]))
108 break
109
110 # 'ITERATIONS' and 'TIME' are the x-axis variable, so don't add them to traces
111 # (but maybe just check that xaxis name is not made into a trace?)
112 elif results[0][i] != 'ITERATIONS' and results[0][i] != 'TIME':
113 traces.append(i)
114 steps.append(lsteps)
115
116 # Mark which items need converting from digital. Format is verilog-like. Use
117 # a format width that is larger than the actual number of digits to force
118 # unsigned conversion.
119 bmatch = binrex.match(results[1][i])
120 if bmatch:
121 digits = bmatch.group(1)
122 if digits == '':
123 digits = len(results[2][i])
124 else:
125 digits = int(digits)
126 cbase = bmatch.group(2)
127 if cbase == 'b':
128 base = 2
129 elif cbase == 'o':
130 base = 8
131 elif cbase == 'd':
132 base = 10
133 else:
134 base = 16
135 binconv.append([base, digits])
136 else:
137 binconv.append([])
138
139 # Support older method of declaring a digital vector
140 if xname.split(':')[0] == 'DIGITAL':
141 binconv[xidx] = [2, len(results[2][0])]
142
143 # Which stepped variables (ignoring X axis variable) have more than one value?
144 watchsteps = list(i for i in range(1, rlen) if len(steps[i]) > 1 and i != xidx)
145
146 # Diagnostic
147 # print("Stepped conditions are: ")
148 # for j in watchsteps:
149 # print(results[0][j] + ' (' + str(len(steps[j])) + ' steps)')
150
151 # Collect results. Make a separate record for each unique set of stepped conditions
152 # encountered. Record has (X, Y) vector and a list of conditions.
153 pdata = {}
154 for item in results[2:]:
155 if xname.split(':')[0] == 'DIGITAL' or binconv[xidx] != []:
156 base = binconv[xidx][0]
157 digits = binconv[xidx][1]
158 # Recast binary strings as integers
159 # Watch for strings that have been cast to floats (need to find the source of this)
160 if '.' in item[xidx]:
161 item[xidx] = item[xidx].split('.')[0]
162 a = int(item[xidx], base)
163 b = twos_comp(a, digits)
164 xvalue = b
165 else:
166 xvalue = item[xidx]
167
168 slist = []
169 for j in watchsteps:
170 slist.append(item[j])
171 istr = ','.join(slist)
172 if istr not in pdata:
173 stextlist = []
174 for j in watchsteps:
175 if results[1][j] == '':
176 stextlist.append(results[0][j] + '=' + item[j])
177 else:
178 stextlist.append(results[0][j] + '=' + item[j] + ' ' + results[1][j])
179 pdict = {}
180 pdata[istr] = pdict
181 pdict['xdata'] = []
182 if stextlist:
183 tracelegnd = False
184 else:
185 tracelegnd = True
186
187 for i in traces:
188 aname = 'ydata' + str(i)
189 pdict[aname] = []
190 alabel = 'ylabel' + str(i)
191 tracename = results[0][i]
192 if ':' in tracename:
193 tracename = tracename.split(':')[1]
194
195 if results[1][i] != '' and not binrex.match(results[1][i]):
196 tracename += ' (' + results[1][i] + ')'
197
198 pdict[alabel] = tracename
199
200 pdict['sdata'] = ' '.join(stextlist)
201 else:
202 pdict = pdata[istr]
203 pdict['xdata'].append(xvalue)
204
205 for i in traces:
206 # For each trace, convert the value from digital to integer if needed
207 if binconv[i] != []:
208 base = binconv[i][0]
209 digits = binconv[i][1]
210 a = int(item[i], base)
211 b = twos_comp(a, digits)
212 yvalue = b
213 else:
214 yvalue = item[i]
215
216 aname = 'ydata' + str(i)
217 pdict[aname].append(yvalue)
218
219 fig = Figure()
220 if parent == None:
221 canvas = FigureCanvasAgg(fig)
222 else:
223 canvas = FigureCanvasTkAgg(fig, parent)
224
225 # With no parent, just make one plot and put the legend off to the side. The
226 # 'extra artists' capability of print_figure will take care of the bounding box.
227 # For display, prepare two subplots so that the legend takes up the space of the
228 # second one.
229 if parent == None:
230 ax = fig.add_subplot(111)
231 else:
232 ax = fig.add_subplot(121)
233
234 fig.hold(True)
235 for record in pdata:
236 pdict = pdata[record]
237
238 # Check if xdata is numeric
239 try:
240 test = float(pdict['xdata'][0])
241 except ValueError:
242 numeric = False
243 xdata = [i for i in range(len(pdict['xdata']))]
244 else:
245 numeric = True
246 xdata = list(map(float,pdict['xdata']))
247
248 if plottype == 'histogram':
249 ax.hist(xdata, histtype='barstacked', label=pdict['sdata'], stacked=True)
250 else:
251 for i in traces:
252 aname = 'ydata' + str(i)
253 alabl = 'ylabel' + str(i)
254 ax.plot(xdata, pdict[aname], label=pdict[alabl] + ' ' + pdict['sdata'])
255 # Diagnostic
256 # print("Y values for " + aname + ": " + str(pdict[aname]))
257
258 if not numeric:
259 ax.set_xticks(xdata)
260 ax.set_xticklabels(pdict['xdata'])
261
262 if 'xlabel' in plotrec:
263 if results[1][xidx] == '' or binrex.match(results[1][xidx]):
264 ax.set_xlabel(plotrec['xlabel'])
265 else:
266 ax.set_xlabel(plotrec['xlabel'] + ' (' + results[1][xidx] + ')')
267 else:
268 # Automatically generate X axis label if not given alternate text
269 xtext = results[0][xidx]
270 if results[1][xidx] != '':
271 xtext += ' (' + results[1][xidx] + ')'
272 ax.set_xlabel(xtext)
273
274 if 'ylabel' in plotrec:
275 if results[1][0] == '' or binrex.match(results[1][0]):
276 ax.set_ylabel(plotrec['ylabel'])
277 else:
278 ax.set_ylabel(plotrec['ylabel'] + ' (' + results[1][0] + ')')
279 else:
280 # Automatically generate Y axis label if not given alternate text
281 ytext = results[0][0]
282 if results[1][0] != '' or binrex.match(results[1][0]):
283 ytext += ' (' + results[1][0] + ')'
284 ax.set_ylabel(ytext)
285
286 ax.grid(True)
287 if watchsteps or tracelegnd:
288 legnd = ax.legend(loc = 2, bbox_to_anchor = (1.05, 1), borderaxespad=0.)
289 else:
290 legnd = None
291
292 if legnd:
293 legnd.draggable()
294
295 if parent == None:
296 if not os.path.exists('simulation_files'):
297 os.makedirs('simulation_files')
298
299 filename = 'simulation_files/' + plotrec['filename']
300 # NOTE: print_figure only makes use of bbox_extra_artists if
301 # bbox_inches is set to 'tight'. This forces a two-pass method
302 # that calculates the real maximum bounds of the figure. Otherwise
303 # the legend gets clipped.
304 if legnd:
305 canvas.print_figure(filename, bbox_inches = 'tight',
306 bbox_extra_artists = [legnd])
307 else:
308 canvas.print_figure(filename, bbox_inches = 'tight')
309
310 return canvas