%%html
<style>
.CodeMirror {
width: 100vw;
}
S
.container {
width: 99% !important;
}
.rendered_html {
font-size:0.8em;
}
.rendered_html table, .rendered_html th, .rendered_html tr, .rendered_html td {
font-size: 100%;
}
</style>
%%capture
%run "NAND programming language.ipynb"
# from IPython.display import clear_output
# clear_output()
from IPython.core.display import HTML
HTML('Laptops etc last 5 rows<br><iframe src="http://free.timeanddate.com/countdown/i5vf6j5p/n43/cf11/cm0/cu4/ct1/cs1/ca0/co0/cr0/ss0/cac09f/cpc09f/pct/tcfff/fs100/szw576/szh243/iso2018-09-13T10:30:00" allowTransparency="true" frameborder="0" width="177" height="35"></iframe>')
# Python format strings
x= 3
print( f" x = {x} , x^2 = {x*x} " )
Define a language NAND' which is NAND + extra commands. Define notion of "computing a function" in NAND'
Example: NAND' = NAND + function definitions.
Theorem: For every NAND' program $P'$, there exists a NAND program $P$ s.t. $P$ computes same function as $P'$.
Theorem: For every NAND' program $P'$ with $n$ inputs, there exists a NAND program $P$ s.t. for every $x\in \{0,1\}^n$, $P(x)=P'(x)$.
Proof:
Show a transformation of $P'$ into $P$.
Prove that transformation works.
mystery = IF
print(nandcode(mystery))
What function does mystery
compute? pollev.com/cs121
a. $MAJ:\{0,1\}^3 \rightarrow \{0,1\}$
b. $MAJ:\{0,1\}^5 \rightarrow \{0,1\}$
c. $f(a,b,c) = a\cdot b + (1-a)\cdot c$
d. $g(a,b,c) = a + b + c \mod 2$
%%html
<iframe src="https://e.ggtimer.com/120?alert=false" frameborder="0" allowfullscreen width=" 80%" height="100" style="box-shadow: 0 0 16px gray; margin: 10px;" name="test" id="test"></iframe>
for a in [0,1]:
for b in [0,1]:
for c in [0,1]: print(f"{a} {b} {c} | {mystery(a,b,c)}")
EXERCISE: Use IF
and other sugar to write NAND program to compute
x | f(x) |
---|---|
000 | 1 |
001 | 1 |
010 | 0 |
011 | 0 |
100 | 1 |
101 | 0 |
110 | 0 |
111 | 1 |
%%html
<iframe src="https://e.ggtimer.com/120?alert=false" frameborder="0" allowfullscreen width=" 80%" height="100" style="box-shadow: 0 0 16px gray; margin: 10px;" name="test" id="test"></iframe>
def f(a,b,c):
return IF(a,
IF(b,
IF(c,1,0),
IF(c,0,1)),
IF(b,
IF(c,0,0),
IF(c,1,1))
)
for a in [0,1]:
for b in [0,1]:
for c in [0,1]: print(f"{a} {b} {c} | {f(a,b,c)}")
def f(a,b,c):
temp = NAND(a,a)
one = NAND(a,temp)
zero = NAND(one,one)
return IF(a,
IF(b,
IF(c,one,zero),
IF(c,zero,one)),
IF(b,
IF(c,zero,zero),
IF(c,one,one))
)
for a in [0,1]:
for b in [0,1]:
for c in [0,1]: print(f"{a} {b} {c} | {f(a,b,c)}")
0 0 0 | 1 0 0 1 | 1 0 1 0 | 0 0 1 1 | 0 1 0 0 | 1 1 0 1 | 0 1 1 0 | 0 1 1 1 | 1
nandcircuit(f)
code= nandcode(f)
print(code)
Temp[0] = NAND(X[0],X[0]) Temp[1] = NAND(X[0],Temp[0]) Temp[2] = NAND(Temp[1],Temp[1]) Temp[3] = NAND(X[2],X[2]) Temp[4] = NAND(Temp[2],Temp[3]) Temp[5] = NAND(Temp[1],X[2]) Temp[6] = NAND(Temp[4],Temp[5]) Temp[7] = NAND(X[2],X[2]) Temp[8] = NAND(Temp[1],Temp[7]) Temp[9] = NAND(Temp[2],X[2]) Temp[10] = NAND(Temp[8],Temp[9]) Temp[11] = NAND(X[1],X[1]) Temp[12] = NAND(Temp[10],Temp[11]) Temp[13] = NAND(Temp[6],X[1]) Temp[14] = NAND(Temp[12],Temp[13]) Temp[15] = NAND(X[2],X[2]) Temp[16] = NAND(Temp[2],Temp[15]) Temp[17] = NAND(Temp[2],X[2]) Temp[18] = NAND(Temp[16],Temp[17]) Temp[19] = NAND(X[2],X[2]) Temp[20] = NAND(Temp[1],Temp[19]) Temp[21] = NAND(Temp[1],X[2]) Temp[22] = NAND(Temp[20],Temp[21]) Temp[23] = NAND(X[1],X[1]) Temp[24] = NAND(Temp[22],Temp[23]) Temp[25] = NAND(Temp[18],X[1]) Temp[26] = NAND(Temp[24],Temp[25]) Temp[27] = NAND(X[0],X[0]) Temp[28] = NAND(Temp[26],Temp[27]) Temp[29] = NAND(Temp[14],X[0]) Y[0] = NAND(Temp[28],Temp[29])
code.count('\n')
def EVAL(prog,n,m,x):
"""Evaluate NAND program prog with n inputs and m outputs on input x."""
vartable = {} # dictionary for variables
for i in range(n): vartable[f'X[{i}]']=x[i] # assign x[i] to variable "X[i]"
for line in prog.split('\n'): # split code into lines
if not(line): continue # ignore empty lines
a = line.find('=')
b = line.find('(')
c=line.find(',')
d= line.find(')')
foo = line[:a].strip()
bar = line[b+1:c].strip()
blah = line[c+1:d].strip()
vartable[foo] = NAND(vartable[bar],vartable[blah])
return [vartable[f'Y[{j}]'] for j in range(m)]
# Explain this code to each other in groups of 2-4
%%html
<iframe src="https://e.ggtimer.com/240?alert=false" frameborder="0" allowfullscreen width=" 80%" height="100" style="box-shadow: 0 0 16px gray; margin: 10px;" name="test" id="test"></iframe>
for a in [0,1]:
for b in [0,1]:
for c in [0,1]: print(f"{a} {b} {c} | {EVAL(code,3,1,[a,b,c])}")
0 0 0 | [1] 0 0 1 | [1] 0 1 0 | [0] 0 1 1 | [0] 1 0 0 | [1] 1 0 1 | [0] 1 1 0 | [0] 1 1 1 | [1]
Theorem: For every $f:\{0,1\}^n \rightarrow \{0,1\}$, there is a NAND program of $O(2^n)$ lines that computes $f$.
def XORn(X):
r = X[0]
for i in range(1,len(X)):
r = XOR(r,X[i])
return r
nandcircuit(restrict(XORn,3))
That is, $\{ f | f:\{0,1\}^n \rightarrow \{0,1\} \} \subseteq SIZE(c\cdot 2^n)$ (powerpoint)
Theorem: For every $f:\{0,1\}^n \rightarrow \{0,1\}$, there is a NAND program of $O(2^n)$ lines that computes $f$.
Proof: Procedure that takes table of $2^n$ values $f(0^n)$,$f(0^{n-1}1)$,$f(0^{n-2}10)$,$\ldots$,$f(1^n)$ and converts it to NAND program that computes $f$.
NAND program for $LOOKUP_n:\{0,1\}^{2^n+n} \rightarrow \{0,1\}$ where $LOOKUP(T,i)=T_i$ for $i\in [2^n]$
"Hardwire" the outputs of $f$ inside the table $T$ to obtain a program with $n$ inputs and one output.
def LOOKUP(T,I): # T length 2^n, I length n, return T[I]
l = len(I)
if l==1: return IF(I[0],T[1],T[0])
return IF(I[l-1], # look at most significant bit of I
LOOKUP(T[2**(l-1):],I[:-1]), # recurse on second half of T
LOOKUP(T[:2**(l-1)],I[:-1])) # recurse on first half of T
LOOKUP([0,0,1,1,0,1,1,0],[1,1,1])
0
def LOOKUPCODE(n): # return NAND code of LOOKUP - can ignore
def f(X):
return LOOKUP( X[:2**n] , X[2**n:2**n+n])
return nandcode(f,numargs=2**n+n)
import itertools
def int2list(i, n = 0):
"""Utility function: transform number to list of 0/1 values in binary rep, LSB first"""
L = [int(c) for c in bin(i)[2:]][::-1]
return L + [0]*max(0,n-len(L))
int2list(13,6)
def NANDPROG(f,n):
# takes f:{0,1}^n --> {0,1}, returns NAND code to compute f
lookupcode = LOOKUPCODE(n) # NAND code for lookup function on 2^n + n inputs
for i in range(2**n):
x = int2list(i,n) # binary representation of i, padded to length n
lookupcode = lookupcode.replace(f'X[{i}]',('one' if f(*x) else 'zero'))
for i in range(2**n,2**n+n):
lookupcode = lookupcode.replace(f'X[{i}]',f'X[{i-2**n}]')
preamble = '''temp = NAND(X[0],X[0])
one = NAND(temp,X[0])
zero = NAND(one,one)
'''
return preamble+lookupcode
# Explain to code to one another in groups of 2-4
%%html
<iframe src="https://e.ggtimer.com/300?alert=false" frameborder="0" allowfullscreen width=" 80%" height="100" style="box-shadow: 0 0 16px gray; margin: 10px;" name="test" id="test"></iframe>
def f(a,b,c):
if a+b+c > 1: return 1
return 0
code = NANDPROG(f,3)
print(code)
temp = NAND(X[0],X[0]) one = NAND(temp,X[0]) zero = NAND(one,one) Temp[0] = NAND(X[0],X[0]) Temp[1] = NAND(one,Temp[0]) Temp[2] = NAND(one,X[0]) Temp[3] = NAND(Temp[1],Temp[2]) Temp[4] = NAND(X[0],X[0]) Temp[5] = NAND(zero,Temp[4]) Temp[6] = NAND(one,X[0]) Temp[7] = NAND(Temp[5],Temp[6]) Temp[8] = NAND(X[1],X[1]) Temp[9] = NAND(Temp[7],Temp[8]) Temp[10] = NAND(Temp[3],X[1]) Temp[11] = NAND(Temp[9],Temp[10]) Temp[12] = NAND(X[0],X[0]) Temp[13] = NAND(zero,Temp[12]) Temp[14] = NAND(one,X[0]) Temp[15] = NAND(Temp[13],Temp[14]) Temp[16] = NAND(X[0],X[0]) Temp[17] = NAND(zero,Temp[16]) Temp[18] = NAND(zero,X[0]) Temp[19] = NAND(Temp[17],Temp[18]) Temp[20] = NAND(X[1],X[1]) Temp[21] = NAND(Temp[19],Temp[20]) Temp[22] = NAND(Temp[15],X[1]) Temp[23] = NAND(Temp[21],Temp[22]) Temp[24] = NAND(X[2],X[2]) Temp[25] = NAND(Temp[23],Temp[24]) Temp[26] = NAND(Temp[11],X[2]) Y[0] = NAND(Temp[25],Temp[26])
for a in [0,1]:
for b in [0,1]:
for c in [0,1]: print(f"{a} {b} {c} | {EVAL(code,3,1,[a,b,c])}")
0 0 0 | [0] 0 0 1 | [0] 0 1 0 | [0] 0 1 1 | [1] 1 0 0 | [0] 1 0 1 | [1] 1 1 0 | [1] 1 1 1 | [1]
def DIVTHREE(L): return 0 if sum(L) % 3 else 1
def DIVTHREE5(a,b,c,d,e): return DIVTHREE( [a,b,c,d,e] )
code = NANDPROG(DIVTHREE5,5)
EVAL(code,5,1,[1,0,0,1,1])
[1]
def code(n): return NANDPROG(restrict(DIVTHREE,n),n)
[(n,code(n).count('\n')) for n in range(2,10)]
[(2, 15), (3, 31), (4, 63), (5, 127), (6, 255), (7, 511), (8, 1023), (9, 2047)]
[code(n).count('\n')/(2**n) for n in range(2,10)]
[3.75, 3.875, 3.9375, 3.96875, 3.984375, 3.9921875, 3.99609375, 3.998046875]
Theorem: $DIVTHREE_n \in SIZE(O(n))$.
That is, exists $c$ such that $DIVTHREE_n \in SIZE(c\cdot n)$ for every sufficiently large $n$.
%%html
<iframe src="https://e.ggtimer.com/420?alert=false" frameborder="0" allowfullscreen width=" 80%" height="100" style="box-shadow: 0 0 16px gray; margin: 10px;" name="test" id="test"></iframe>
Theorem: $DIVTHREE_n \in SIZE(O(n))$. i.e., exists $c$ such that $DIVTHREE_n \in SIZE(c\cdot n)$ for every sufficiently large $n$.
Proof outline:
i) Represent $[3]=\{0,1,2\}$ as $\{0,1\}^2$.
ii) We'll compute $f:[3]^n \rightarrow [3]$ where $f(a_0,\ldots,a_{n-1}) = \sum_{i=0}^{n-1} a_i \mod 3$. enough!
To compute $f(a_0,\ldots,a_{n-1})$:
i) Set $s=0$
ii) For $i=0,\ldots,n-1$: set $s = g(s,a_i)$ where $g:[3]^2 \rightarrow [3]$ is $g(a,b)= a+b \mod 3$.
Cost: $n \times cost(g) = O(n)$ using main theorem!
To compute $f:\{0,1\}^n \rightarrow \{0,1\}^2$: define
$g_0:\{0,1\}^n \rightarrow \{0,1\}$ as $f_0(x) = f(x)_0$
$g_1:\{0,1\}^n \rightarrow \{0,1\}$ as $f_1(x) = f(x)_1$.
Using $P_0$ to compute $g_0$ and $P_1$ to compute $g_1$ can obtain $P$ to compute $f$.
Can compute every function $f:\{0,1\}^n \rightarrow \{0,1\}^m$ using $O(m \cdot 2^n)$ (in fact $O(m\cdot 2^n / n)$) lines.
Sometimes can do much much much better
Think of $f:\{0,1\}^n \rightarrow \{0,1\}$ as $f:\{0,1\}^{n-k} \times \{0,1\}^k \rightarrow \{0,1\}$.
For every $x\in \{0,1\}^{n-k}$, define $f_x:\{0,1\}^k \rightarrow \{0,1\}$ as the function $f_x(y)=f(x,y)$. Can be thought of as a string of length $2^k$.
So we can compute the map $x \mapsto f_x$ using $O(2^k \cdot 2^{n-k})$ lines.
Afte we have $f_x$, we can compute $y \mapsto f_x(y)=f(x,y)$ with a lookup of $O(2^k)$ lines.
If we set $k = \log(n-2\log n)$ we get that $2^k \cdot 2^{n-k} = O(2^n/n)$.
Think of program $P$ as a string - give it to another program $Q$ as input