from matplotlib import pyplot as plt
%matplotlib notebook
import numpy as np
import numpy.polynomial.polynomial as poly
import statistics
import warnings
import itertools
import sqlite3
db = sqlite3.connect('results.sqlite3')
def fetch_run(names_or_ids):
run_info, grouped, cals = [], {}, {}
for name_or_id in names_or_ids:
if type(name_or_id) is str:
runs = db.execute('SELECT run_id FROM runs WHERE name LIKE ?', (name_or_id,)).fetchall()
if len(runs) > 1:
raise ValueError('Ambiguous run name {} matches run ids {}'.format(run_name, runs))
((run_id,),), run_name = runs, name_or_id
else:
run_id, (run_name,) = name_or_id, db.execute('SELECT name FROM runs WHERE run_id == ?', (name_or_id,)).fetchone()
run_info.append((run_id, run_name))
data = db.execute('''
SELECT channel, duty_cycle, voltage, voltage_stdev FROM measurements
WHERE run_id == ?
ORDER BY channel ASC, duty_cycle ASC;
''', (run_id,)).fetchall()
_ch, cal_duty, *cal = data[0]
assert cal_duty == 0
cals[run_id] = cal
for ch, data in itertools.groupby(data, lambda elem: elem[0]):
if ch == -1: # skip cal data
continue
if ch in grouped:
warnings.warn('Duplicate data: Channel {} found in more than one run!'.format(ch))
grouped[ch] = [(duty, volt, stdev) for _ch, duty, volt, stdev in data]
return run_info, grouped, next(iter(cals.values())) # for now just use some random cal value
def apply_style(ax):
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_color('#08bdf9')
ax.spines['left'].set_color('#08bdf9')
ax.tick_params(axis='x', colors='#01769D', which='both')
ax.tick_params(axis='y', colors='#01769D', which='both')
ax.xaxis.label.set_color('#01769D')
ax.yaxis.label.set_color('#01769D')
ax.grid(color='#08bdf9', linestyle=':')
color_bright, color_dark = '#ffd2e9', '#fe3ea0'
def plot_run(figtitle, *names_or_ids, figsize=None, combine_plots=False, svgfile=None):
run_info, data, cal = fetch_run(names_or_ids)
if combine_plots:
rows, cols = 1, 1
else:
rows = (len(data)+1)//2
cols = 2 if len(data) > 1 else 1
fig, axs = plt.subplots(rows, cols, figsize=figsize or (8,3*max(2, rows)), squeeze=False)
if figtitle:
fig.suptitle(figtitle)
if combine_plots:
axs = np.array([axs[0,0]] * len(names_or_ids))
cal_volt, cal_stdev = cal
offsets = []
for ch, ax in zip(data, axs.flat):
ch_data = data[ch]
duty, volt, stdev = zip(*ch_data)
duty = np.array(duty) / duty[0]
volt = np.array(volt) - cal_volt
vref = volt[0]
stdev = np.array(stdev)
max_y = max(volt)/vref
min_x, max_x = min(duty), max(duty)
offx, slope = fit_coefs = poly.polyfit(duty, volt, 1)
fit_func = poly.polyval(duty, fit_coefs)
ax.errorbar(duty, volt/vref, yerr=stdev/vref, color=color_bright, zorder=1)
ax.plot(duty, fit_func/vref, color=color_dark, zorder=2)
apply_style(ax)
ax.set_xscale('log')
ax.set_yscale('log')
bit_offx = offx/slope
offsets.append(bit_offx)
print('Channel {} offset: {:6.3f}lsb'.format(ch, bit_offx))
if figtitle:
ax.set_title('Channel {}, offset={:.3f}lsb'.format(ch, bit_offx))
# reuse latest duty cycles here
ax.set_xticks(duty)
ax.set_xticklabels([str(i) for i in range(len(duty))])
ax.set_xlabel('bit index')
ax.set_yticks([2**i for i in range(len(duty))])
ax.set_yticklabels([str(2**i) for i in range(len(duty))])
ax.set_xlim([min_x*0.9, max_x*1.1])
ax.set_ylim([0, max_y*1.1])
if len(names_or_ids) > 1:
print('Offset statistics: mean={:.4f}lsb, stdev={:.4f}lsb'.format(
statistics.mean(offsets), statistics.stdev(offsets)))
if svgfile:
fig.savefig(svgfile)
def fetch_runs(*names):
return [run_id for name in names for run_id, in db.execute('''
SELECT DISTINCT runs.run_id
FROM runs JOIN measurements USING (run_id)
WHERE name LIKE ? AND channel != -1
''', (name,)).fetchall() ]
plot_run('All channels, blue runs', *fetch_runs('green1', 'green2', 'green3', 'green4'))
Channel 31 offset: 2.681lsb Channel 30 offset: 2.633lsb Channel 29 offset: 2.616lsb Channel 28 offset: 2.643lsb Offset statistics: mean=2.6432lsb, stdev=0.0276lsb
plot_run(None, *fetch_runs('green1'), figsize=(6, 4), svgfile='/tmp/driver_linearity_raw.svg')
Channel 31 offset: 2.681lsb
def bitslide(nbits, offx_lsb):
return [ sum((2**n + offx_lsb) if i&(2**n) else 0 for n in range(nbits)) for i in range(2**nbits) ]
def plot_bitslide(data):
fig, (axl, axr) = plt.subplots(1, 2, figsize=(8, 3))
apply_style(axl)
apply_style(axr)
axl.plot(data, color=color_dark)
axr.plot(data, color=color_dark)
axr.set_yscale('log')
axr.set_xscale('log')
axl.set_xlim((0, len(data)))
axr.set_xlim((0, len(data)))
plot_bitslide(bitslide(8, 2.5))
def frob_export_for_blog(data, svgfile=None):
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
apply_style(ax)
ax.plot(data, color=color_dark)
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlim((0, len(data)))
if svgfile:
fig.savefig(svgfile)
frob_export_for_blog(bitslide(8, 2.5), svgfile='/tmp/uncorrected_brightness_sim.svg')
def cutoff_reference(nbits, offx_lsb, cutoff):
return [ sum((2**n + offx_lsb) if i&(2**n) else 0 for n in range(cutoff, nbits))
for i in range(2**nbits) ]
plot_bitslide(cutoff_reference(8, 2.5, 3))
def improved_bitslide1(nbits, offx_lsb, cutoff):
bs = sorted(bitslide(cutoff, offx_lsb))
return [ sum((2**n + offx_lsb) if i&(2**n) else 0 for n in range(cutoff, nbits))
+ bs[i%(2**cutoff)] for i in range(2**nbits) ]
plot_bitslide(improved_bitslide1(8, 2.5, 5))
def improved_bitslide2(nbits, offx_lsb, cutoff):
bs = lambda x: min(bitslide(cutoff, offx_lsb), key=lambda y: abs(x-y))
return [ sum((2**n + offx_lsb) if i&(2**n) else 0 for n in range(cutoff, nbits))
+ bs(i%(2**cutoff)) for i in range(2**nbits) ]
plot_bitslide(improved_bitslide2(8, 2.5, 5))
plot_run('Linearity test run', *fetch_runs('test102'))
Channel 23 offset: 0.741lsb
def simulate_bitslide(data):
nbits = len(data)
return [ sum(data[n] if i&(2**n) else 0 for n in range(nbits)) for i in range(2**nbits) ]
info, data, (zero_cal, _stdev) = fetch_run(['test103'])
data = np.array(next(iter(data.values())))[:,1] - zero_cal
plot_bitslide(simulate_bitslide(data))
frob_export_for_blog(simulate_bitslide(data), svgfile='/tmp/corrected_brightness_sim.svg')