In [1]:
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
In [2]:
db = sqlite3.connect('results.sqlite3')
In [3]:
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
In [4]:
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=':')
In [14]:
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)
In [6]:
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() ]
In [11]:
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
In [8]:
plot_run(None, *fetch_runs('green1'), figsize=(6, 4), svgfile='/tmp/driver_linearity_raw.svg')
Channel 31 offset:  2.681lsb
In [15]:
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))
In [17]:
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')
In [8]:
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))
In [9]:
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))
In [10]:
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))
In [28]:
plot_run('Linearity test run', *fetch_runs('test102'))
Channel 23 offset:  0.741lsb
In [19]:
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))
In [20]:
frob_export_for_blog(simulate_bitslide(data), svgfile='/tmp/corrected_brightness_sim.svg')