timfuz: least square tests

Signed-off-by: John McMaster <johndmcmaster@gmail.com>
This commit is contained in:
John McMaster 2018-08-29 13:13:46 -07:00
parent d4d0af9d9f
commit 41cf8469b5
11 changed files with 470 additions and 169 deletions

View File

@ -1,10 +0,0 @@
.NOTPARALLEL:
SUBDIRS := $(patsubst %/,%, $(wildcard */))
.PHONY: $(SUBDIRS)
$(MAKECMDGOALS): $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ -f ../Makefile.database $(MAKECMDGOALS)

View File

@ -1,15 +0,0 @@
.PHONY: all clean
all: tilegrid.json
# Small dance to say that there is a single recipe that is run once to generate
# multiple files.
tileconn.json tilegrid.json: fuzzers
.INTERMEDIATE: fuzzers
fuzzers: SHELL:=/bin/bash
fuzzers: settings.sh
source settings.sh && $(MAKE) -C ../../fuzzers all
clean:
rm -f *.db tileconn.json tilegrid.json *.yaml
rm -rf html

View File

@ -0,0 +1,272 @@
#!/usr/bin/env python3
# https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html
from scipy.optimize import linprog
from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, simplify_rows, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json, A_ub_np2d, print_eqns, print_eqns_np
from timfuz_massage import massage_equations
import numpy as np
import glob
import json
import math
from collections import OrderedDict
from fractions import Fraction
import sys
import datetime
import os
import time
import timfuz_solve
import numpy
import scipy.optimize as optimize
from scipy.optimize import least_squares
def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None):
# Given timing scores for above delays (-ps)
assert type(Anp[0]) is np.ndarray, type(Anp[0])
assert type(b) is np.ndarray, type(b)
#check_feasible(Anp, b)
'''
Be mindful of signs
Have something like
timing1/timing 2 are constants
delay1 + delay2 + delay4 >= timing1
delay2 + delay3 >= timing2
But need it in compliant form:
-delay1 + -delay2 + -delay4 <= -timing1
-delay2 + -delay3 <= -timing2
'''
rows = len(Anp)
cols = len(Anp[0])
print('Unique delay elements: %d' % len(names))
print('Input paths')
print(' # timing scores: %d' % len(b))
print(' Rows: %d' % rows)
'''
You must have at least as many things to optimize as variables
That is, the system must be plausibly constrained for it to attempt a solve
If not, you'll get a message like
TypeError: Improper input: N=3 must not exceed M=2
'''
if rows < cols:
raise Exception("rows must be >= cols")
def func(params, xdata, ydata):
return (ydata - np.dot(xdata, params))
print('')
# Now find smallest values for delay constants
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
print('Running leastsq w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
x0 = np.array([0.0 for _x in range(cols)])
x, cov_x, infodict, mesg, ier = optimize.leastsq(func, x0, args=(Anp, b), full_output=True)
print('x', x)
print('cov_x', cov_x)
print('infodictx', infodict)
print('mesg', mesg)
print('ier', ier)
print(' Solution found: %s' % (ier in (1, 2, 3, 4)))
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Solve timing solution'
)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--massage', action='store_true', help='')
parser.add_argument('--sub-json', help='Group substitutions to make fully ranked')
parser.add_argument('--corner', default="slow_max", help='')
parser.add_argument('--out', default=None, help='output timing delay .json')
parser.add_argument(
'fns_in',
nargs='*',
help='timing3.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
timfuz_solve.run(run_corner=run_corner, sub_json=sub_json,
fns_in=fns_in, corner=args.corner, massage=args.massage, outfn=args.out, verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
# optimize.curve_fit wrapper
def test1():
# Generate artificial data = straight line with a=0 and b=1
# plus some noise.
xdata = np.array([0.0,1.0,2.0,3.0,4.0,5.0])
ydata = np.array([0.1,0.9,2.2,2.8,3.9,5.1])
# Initial guess.
x0 = np.array([0.0, 0.0, 0.0])
sigma = np.array([1.0,1.0,1.0,1.0,1.0,1.0])
def func(x, a, b, c):
return a + b*x + c*x*x
print(optimize.curve_fit(func, xdata, ydata, x0, sigma))
# optimize.leastsq
def test2():
# The function whose square is to be minimised.
# params ... list of parameters tuned to minimise function.
# Further arguments:
# xdata ... design matrix for a linear model.
# ydata ... observed data.
def func(params, xdata, ydata):
return (ydata - np.dot(xdata, params))
x0 = np.array([0.0, 0.0])
'''
a = 10
a + b = 100
'''
xdata = np.array([[1, 0],
[1, 1]])
ydata = np.array([10, 100])
'''
x [ 10. 90.]
cov_x [[ 1. -1.]
[-1. 2.]]
infodictx {'ipvt': array([1, 2], dtype=int32), 'qtf': array([ 1.69649118e-10, 1.38802454e-10]), 'nfev': 7, 'fjac': array([[ 1.41421356, 0.70710678],
[ 0.70710678, 0.70710678]]), 'fvec': array([ 0., 0.])}
mesg The relative error between two consecutive iterates is at most 0.000000
ier 2
Solution found: True
'''
x, cov_x, infodict, mesg, ier = optimize.leastsq(func, x0, args=(xdata, ydata), full_output=True)
print('x', x)
print('cov_x', cov_x)
print('infodictx', infodict)
print('mesg', mesg)
print('ier', ier)
print(' Solution found: %s' % (ier in (1, 2, 3, 4)))
# non-square
def test3():
def func(params, xdata, ydata):
return (ydata - np.dot(xdata, params))
x0 = np.array([0.0, 0.0, 0.0])
'''
a = 10
a + b + c = 100
'''
xdata = np.array([[1, 0, 0],
[1, 1, 1],
[0, 0, 0]])
ydata = np.array([10, 100, 0])
x, cov_x, infodict, mesg, ier = optimize.leastsq(func, x0, args=(xdata, ydata), full_output=True)
print('x', x)
print('cov_x', cov_x)
print('infodictx', infodict)
print('mesg', mesg)
print('ier', ier)
print(' Solution found: %s' % (ier in (1, 2, 3, 4)))
def test4():
def func(params):
print('')
print('iter')
print(params)
print(xdata)
print(ydata)
return (ydata - np.dot(xdata, params))
x0 = np.array([0.0, 0.0, 0.0])
'''
You must have at least as many things to optimize as variables
That is, the system must be plausibly constrained for it to attempt a solve
If not, you'll get a message like
TypeError: Improper input: N=3 must not exceed M=2
'''
xdata = np.array([[1, 0, 0],
[1, 1, 1],
[1, 0, 1],
[0, 1, 1],
])
ydata = np.array([10, 100, 120, 140])
x, cov_x, infodict, mesg, ier = optimize.leastsq(func, x0, full_output=True)
print('x', x)
print('cov_x', cov_x)
print('infodictx', infodict)
print('mesg', mesg)
print('ier', ier)
print(' Solution found: %s' % (ier in (1, 2, 3, 4)))
def test5():
from scipy.optimize import least_squares
def fun_rosenbrock(x):
return np.array([10 * (x[1] - x[0]**2), (1 - x[0])])
x0_rosenbrock = np.array([2, 2])
res = least_squares(fun_rosenbrock, x0_rosenbrock)
'''
active_mask: array([ 0., 0.])
cost: 9.8669242910846867e-30
fun: array([ 4.44089210e-15, 1.11022302e-16])
grad: array([ -8.89288649e-14, 4.44089210e-14])
jac: array([[-20.00000015, 10. ],
[ -1. , 0. ]])
message: '`gtol` termination condition is satisfied.'
nfev: 3
njev: 3
optimality: 8.8928864934219529e-14
status: 1
success: True
x: array([ 1., 1.])
'''
print(res)
def test6():
from scipy.optimize import least_squares
def func(params):
return (ydata - np.dot(xdata, params))
x0 = np.array([0.0, 0.0, 0.0])
'''
a = 10
a + b + c = 100
'''
xdata = np.array([[1, 0, 0],
[1, 1, 1],
[0, 0, 0]])
ydata = np.array([10, 100, 0])
res = least_squares(func, x0)
'''
'''
print(res)
if __name__ == '__main__':
#main()
#test1()
#test2()
#test3()
#test4()
#test5()
test6()

View File

@ -0,0 +1,150 @@
#!/usr/bin/env python3
# https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html
from scipy.optimize import linprog
from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, simplify_rows, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json, A_ub_np2d, print_eqns, print_eqns_np
from timfuz_massage import massage_equations
import numpy as np
import glob
import json
import math
from collections import OrderedDict
from fractions import Fraction
import sys
import datetime
import os
import time
import timfuz_solve
def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None):
# Given timing scores for above delays (-ps)
assert type(Anp[0]) is np.ndarray, type(Anp[0])
assert type(b) is np.ndarray, type(b)
#check_feasible(Anp, b)
'''
Be mindful of signs
Have something like
timing1/timing 2 are constants
delay1 + delay2 + delay4 >= timing1
delay2 + delay3 >= timing2
But need it in compliant form:
-delay1 + -delay2 + -delay4 <= -timing1
-delay2 + -delay3 <= -timing2
'''
rows = len(Anp)
cols = len(Anp[0])
print('Scaling to solution form...')
b_ub = -1.0 * b
#A_ub = -1.0 * Anp
A_ub = [-1.0 * x for x in Anp]
if verbose:
print('')
print('A_ub b_ub')
print_eqns_np(A_ub, b_ub, verbose=verbose)
print('')
print('Creating misc constants...')
# Minimization function scalars
# Treat all logic elements as equally important
c = [1 for _i in range(len(names))]
# Delays cannot be negative
# (this is also the default constraint)
#bounds = [(0, None) for _i in range(len(names))]
# Also you can provide one to apply to all
bounds = (0, None)
# Seems to take about rows + 3 iterations
# Give some margin
#maxiter = int(1.1 * rows + 100)
#maxiter = max(1000, int(1000 * rows + 1000))
# Most of the time I want it to just keep going unless I ^C it
maxiter = 1000000
if verbose >= 2:
print('b_ub', b)
print('Unique delay elements: %d' % len(names))
print(' # delay minimization weights: %d' % len(c))
print(' # delay constraints: %d' % len(bounds))
print('Input paths')
print(' # timing scores: %d' % len(b))
print(' Rows: %d' % rows)
tlast = [time.time()]
iters = [0]
printn = [0]
def callback(xk, **kwargs):
iters[0] = kwargs['nit']
if time.time() - tlast[0] > 1.0:
sys.stdout.write('I:%d ' % kwargs['nit'])
tlast[0] = time.time()
printn[0] += 1
if printn[0] % 10 == 0:
sys.stdout.write('\n')
sys.stdout.flush()
print('')
# Now find smallest values for delay constants
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
print('Running linprog w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, callback=callback,
options={"disp": True, 'maxiter': maxiter, 'bland': True, 'tol': 1e-6,})
nonzeros = 0
print('Ran %d iters' % iters[0])
if res.success:
print('Result sample (%d elements)' % (len(res.x)))
plim = 3
for xi, (name, x) in enumerate(zip(names, res.x)):
nonzero = x >= 0.001
if nonzero:
nonzeros += 1
#if nonzero and (verbose >= 1 or xi > 30):
if nonzero and (verbose or ((nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
print(' % 4u % -80s % 10.1f' % (xi, name, x))
print('Delay on %d / %d' % (nonzeros, len(res.x)))
if not os.path.exists('res'):
os.mkdir('res')
fn_out = 'res/%s' % datetime.datetime.utcnow().isoformat().split('.')[0]
print('Writing %s' % fn_out)
np.save(fn_out, (3, c, A_ub, b_ub, bounds, names, res, meta))
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Solve timing solution'
)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--massage', action='store_true', help='')
parser.add_argument('--sub-json', help='Group substitutions to make fully ranked')
parser.add_argument('--corner', default="slow_max", help='')
parser.add_argument('--out', default=None, help='output timing delay .json')
parser.add_argument(
'fns_in',
nargs='*',
help='timing3.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
timfuz_solve.run(run_corner=run_corner, sub_json=sub_json,
fns_in=fns_in, corner=args.corner, massage=args.massage, outfn=args.out, verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -718,6 +718,9 @@ def load_sub(fn):
return j
def row_sub_syms(row, sub_json, verbose=False):
zero = Fraction(0)
zero = 0
if 0 and verbose:
print("")
print(row.items())
@ -751,8 +754,9 @@ def row_sub_syms(row, sub_json, verbose=False):
if verbose:
print('pivot %i %s' % (n, pivot))
for subk, subv in sorted(sub_json['subs'][group].items()):
oldn = row.get(subk, Fraction(0))
rown = oldn - n * subv
oldn = row.get(subk, zero)
rown = -n * subv
rown += type(rown)(oldn)
if verbose:
print(" %s: %d => %d" % (subk, oldn, rown))
if rown == 0:
@ -768,8 +772,9 @@ def row_sub_syms(row, sub_json, verbose=False):
# after all constants are applied, the row should end up positive?
# numeric precision issues previously limited this
# Ex: AssertionError: ('PIP_BSW_2ELSING0', -2.220446049250313e-16)
for k, v in sorted(row.items()):
assert v > 0, (k, v)
if 0:
for k, v in sorted(row.items()):
assert v > 0, (k, v)
def run_sub_json(Ads, sub_json, verbose=False):
nrows = 0
@ -805,7 +810,7 @@ def run_sub_json(Ads, sub_json, verbose=False):
print('')
print("Sub: %u / %u rows changed" % (nsubs, nrows))
print("Sub: %u => %u cols" % (ncols_old, ncols_new))
print("Sub: %u => %u non-zero row cols" % (ncols_old, ncols_new))
def print_eqns(Ads, b, verbose=0, lim=3, label=''):
rows = len(b)

View File

@ -322,6 +322,7 @@ def derive_eq_by_col(Ads, b_ub, verbose=0):
print('Derive col: %d => %d rows' % (len(b_ub), len(b_ret)))
return Ads_ret, b_ret
# keep derriving until solution is (probably) stable
def massage_equations_old(Ads, b, verbose=False, derive_lim=3):
'''
Equation pipeline
@ -392,7 +393,8 @@ def massage_equations_old(Ads, b, verbose=False, derive_lim=3):
debug("final (sorted)")
return Ads, b
def massage_equations_old2(Ads, b, verbose=False):
# iteratively increasing column limit until all columns are added
def massage_equations_inc_col_lim(Ads, b, verbose=False):
'''
Equation pipeline
Some operations may generate new equations
@ -463,7 +465,9 @@ def massage_equations_old2(Ads, b, verbose=False):
print('Massage final: %d => %d rows' % (dstart, dend))
return Ads, b
def massage_equations(Ads, b, verbose=False):
# only derive based on nearby equations
# theory is they will be the best to diff
def massage_equations_near(Ads, b, verbose=False):
'''
Equation pipeline
Some operations may generate new equations
@ -525,3 +529,5 @@ def massage_equations(Ads, b, verbose=False):
print('')
print('Massage final: %d => %d rows' % (dstart, dend))
return Ads, b
massage_equations = massage_equations_inc_col_lim

View File

@ -32,9 +32,12 @@ def check_feasible(A_ub, b_ub):
progress = max(1, rows / 100)
# Chose a high arbitrary value for x
# Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
xs = [10e3 for _i in range(cols)]
'''
Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
Series can have several hundred delay elements
Max delay in ballpark
'''
xs = [1e9 for _i in range(cols)]
# FIXME: use the correct np function to do this for me
# Verify bounds
@ -65,102 +68,14 @@ def check_feasible(A_ub, b_ub):
raise Exception("Bad ")
print(' done')
def run_corner(Anp, b, names, verbose=False, opts={}, meta={}):
# Given timing scores for above delays (-ps)
assert type(Anp[0]) is np.ndarray, type(Anp[0])
assert type(b) is np.ndarray, type(b)
def instances(Ads):
ret = 0
for row_ds in Ads:
ret += sum(row_ds.values())
return ret
#check_feasible(Anp, b)
'''
Be mindful of signs
Have something like
timing1/timing 2 are constants
delay1 + delay2 + delay4 >= timing1
delay2 + delay3 >= timing2
But need it in compliant form:
-delay1 + -delay2 + -delay4 <= -timing1
-delay2 + -delay3 <= -timing2
'''
rows = len(Anp)
cols = len(Anp[0])
print('Scaling to solution form...')
b_ub = -1.0 * b
#A_ub = -1.0 * Anp
A_ub = [-1.0 * x for x in Anp]
if verbose:
print('')
print('A_ub b_ub')
print_eqns_np(A_ub, b_ub, verbose=verbose)
print('')
print('Creating misc constants...')
# Minimization function scalars
# Treat all logic elements as equally important
c = [1 for _i in range(len(names))]
# Delays cannot be negative
# (this is also the default constraint)
#bounds = [(0, None) for _i in range(len(names))]
# Also you can provide one to apply to all
bounds = (0, None)
# Seems to take about rows + 3 iterations
# Give some margin
#maxiter = int(1.1 * rows + 100)
#maxiter = max(1000, int(1000 * rows + 1000))
# Most of the time I want it to just keep going unless I ^C it
maxiter = 1000000
if verbose >= 2:
print('b_ub', b)
print('Unique delay elements: %d' % len(names))
print(' # delay minimization weights: %d' % len(c))
print(' # delay constraints: %d' % len(bounds))
print('Input paths')
print(' # timing scores: %d' % len(b))
print(' Rows: %d' % rows)
tlast = [time.time()]
iters = [0]
printn = [0]
def callback(xk, **kwargs):
iters[0] = kwargs['nit']
if time.time() - tlast[0] > 1.0:
sys.stdout.write('I:%d ' % kwargs['nit'])
tlast[0] = time.time()
printn[0] += 1
if printn[0] % 10 == 0:
sys.stdout.write('\n')
sys.stdout.flush()
print('')
# Now find smallest values for delay constants
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
print('Running linprog w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, callback=callback,
options={"disp": True, 'maxiter': maxiter, 'bland': True, 'tol': 1e-6,})
nonzeros = 0
print('Ran %d iters' % iters[0])
if res.success:
print('Result sample (%d elements)' % (len(res.x)))
plim = 3
for xi, (name, x) in enumerate(zip(names, res.x)):
nonzero = x >= 0.001
if nonzero:
nonzeros += 1
#if nonzero and (verbose >= 1 or xi > 30):
if nonzero and (verbose or ((nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
print(' % 4u % -80s % 10.1f' % (xi, name, x))
print('Delay on %d / %d' % (nonzeros, len(res.x)))
if not os.path.exists('res'):
os.mkdir('res')
fn_out = 'res/%s' % datetime.datetime.utcnow().isoformat().split('.')[0]
print('Writing %s' % fn_out)
np.save(fn_out, (3, c, A_ub, b_ub, bounds, names, res, meta))
def run(fns_in, corner, sub_json=None, dedup=True, massage=False, verbose=False):
def run(fns_in, corner, run_corner, sub_json=None, dedup=True, massage=False, outfn=None, verbose=False, **kwargs):
print('Loading data')
Ads, b = loadc_Ads_b(fns_in, corner, ico=True)
# Remove duplicate rows
@ -168,15 +83,19 @@ def run(fns_in, corner, sub_json=None, dedup=True, massage=False, verbose=False)
# maybe better to just add them into the matrix directly
if dedup:
oldn = len(Ads)
iold = instances(Ads)
Ads, b = simplify_rows(Ads, b)
print('Simplify %u => %u rows' % (oldn, len(Ads)))
print('Simplify %u => %u instances' % (iold, instances(Ads)))
if sub_json:
print('Sub: %u rows' % len(Ads))
iold = instances(Ads)
names_old = index_names(Ads)
run_sub_json(Ads, sub_json, verbose=verbose)
names = index_names(Ads)
print("Sub: %u => %u names" % (len(names_old), len(names)))
print('Sub: %u => %u instances' % (iold, instances(Ads)))
else:
names = index_names(Ads)
@ -187,46 +106,20 @@ def run(fns_in, corner, sub_json=None, dedup=True, massage=False, verbose=False)
#print
#col_dist(A_ubd, 'final', names)
'''
Given:
a >= 10
a + b >= 100
A valid solution is:
a = 100
However, a better solution is something like
a = 10
b = 90
This creates derived constraints to provide more realistic results
'''
if massage:
Ads, b = massage_equations(Ads, b)
print('Converting to numpy...')
names, Anp = A_ds2np(Ads)
run_corner(Anp, np.asarray(b), names, verbose=verbose)
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Solve timing solution'
)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--massage', action='store_true', help='')
parser.add_argument('--sub-json', help='Group substitutions to make fully ranked')
parser.add_argument('--corner', default="slow_max", help='')
parser.add_argument(
'fns_in',
nargs='*',
help='timing3.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
run(sub_json=sub_json,
fns_in=fns_in, verbose=args.verbose, corner=args.corner, massage=args.massage)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()
run_corner(Anp, np.asarray(b), names, outfn=outfn, verbose=verbose, **kwargs)