{ "cells": [ { "cell_type": "code", "execution_count": 33, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%html\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "%%capture\n", "%run \"NAND programming language.ipynb\"\n", "# from IPython.display import clear_output\n", "# clear_output()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Lecture 4: Computing Every function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from IPython.core.display import HTML\n", "HTML('Laptops etc last 5 rows
')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# Python format strings\n", "x= 3\n", "print( f\" x = {x} , x^2 = {x*x} \" )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Syntactic Sugar\n", "\n", "Define a language _NAND'_ which is NAND + extra commands. Define notion of \"computing a function\" in NAND'\n", "\n", "__Example:__ NAND' = NAND + function definitions.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__Theorem:__ For every NAND' program $P'$, there exists a NAND program $P$ s.t. $P$ computes same function as $P'$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__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)$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__Proof:__ \n", "\n", "1. Show a _transformation_ of $P'$ into $P$.\n", "\n", "2. Prove that transformation works." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "mystery = IF" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "print(nandcode(mystery))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "What function does `mystery` compute? [pollev.com/cs121](http://pollev.com/cs121) \n", "__a.__ $MAJ:\\{0,1\\}^3 \\rightarrow \\{0,1\\}$ \n", "__b.__ $MAJ:\\{0,1\\}^5 \\rightarrow \\{0,1\\}$ \n", "__c.__ $f(a,b,c) = a\\cdot b + (1-a)\\cdot c$ \n", "__d.__ $g(a,b,c) = a + b + c \\mod 2$" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "%%html\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "for a in [0,1]:\n", " for b in [0,1]:\n", " for c in [0,1]: print(f\"{a} {b} {c} | {mystery(a,b,c)}\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__EXERCISE:__ Use `IF` and other sugar to write NAND program to compute \n", "\n", "\n", " x | f(x)\n", "-----|--------\n", "000 | 1\n", "001 | 1\n", "010 | 0\n", "011 | 0\n", "100 | 1\n", "101 | 0\n", "110 | 0\n", "111 | 1\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "%%html\n", "" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def f(a,b,c):\n", " return IF(a,\n", " IF(b,\n", " IF(c,1,0),\n", " IF(c,0,1)),\n", " IF(b,\n", " IF(c,0,0),\n", " IF(c,1,1))\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "for a in [0,1]:\n", " for b in [0,1]:\n", " for c in [0,1]: print(f\"{a} {b} {c} | {f(a,b,c)}\")" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0 0 | 1\n", "0 0 1 | 1\n", "0 1 0 | 0\n", "0 1 1 | 0\n", "1 0 0 | 1\n", "1 0 1 | 0\n", "1 1 0 | 0\n", "1 1 1 | 1\n" ] } ], "source": [ "def f(a,b,c):\n", " temp = NAND(a,a)\n", " one = NAND(a,temp)\n", " zero = NAND(one,one)\n", " return IF(a,\n", " IF(b,\n", " IF(c,one,zero),\n", " IF(c,zero,one)),\n", " IF(b,\n", " IF(c,zero,zero),\n", " IF(c,one,one))\n", " )\n", "\n", "for a in [0,1]:\n", " for b in [0,1]:\n", " for c in [0,1]: print(f\"{a} {b} {c} | {f(a,b,c)}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "nandcircuit(f)" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Temp[0] = NAND(X[0],X[0])\n", "Temp[1] = NAND(X[0],Temp[0])\n", "Temp[2] = NAND(Temp[1],Temp[1])\n", "Temp[3] = NAND(X[2],X[2])\n", "Temp[4] = NAND(Temp[2],Temp[3])\n", "Temp[5] = NAND(Temp[1],X[2])\n", "Temp[6] = NAND(Temp[4],Temp[5])\n", "Temp[7] = NAND(X[2],X[2])\n", "Temp[8] = NAND(Temp[1],Temp[7])\n", "Temp[9] = NAND(Temp[2],X[2])\n", "Temp[10] = NAND(Temp[8],Temp[9])\n", "Temp[11] = NAND(X[1],X[1])\n", "Temp[12] = NAND(Temp[10],Temp[11])\n", "Temp[13] = NAND(Temp[6],X[1])\n", "Temp[14] = NAND(Temp[12],Temp[13])\n", "Temp[15] = NAND(X[2],X[2])\n", "Temp[16] = NAND(Temp[2],Temp[15])\n", "Temp[17] = NAND(Temp[2],X[2])\n", "Temp[18] = NAND(Temp[16],Temp[17])\n", "Temp[19] = NAND(X[2],X[2])\n", "Temp[20] = NAND(Temp[1],Temp[19])\n", "Temp[21] = NAND(Temp[1],X[2])\n", "Temp[22] = NAND(Temp[20],Temp[21])\n", "Temp[23] = NAND(X[1],X[1])\n", "Temp[24] = NAND(Temp[22],Temp[23])\n", "Temp[25] = NAND(Temp[18],X[1])\n", "Temp[26] = NAND(Temp[24],Temp[25])\n", "Temp[27] = NAND(X[0],X[0])\n", "Temp[28] = NAND(Temp[26],Temp[27])\n", "Temp[29] = NAND(Temp[14],X[0])\n", "Y[0] = NAND(Temp[28],Temp[29])\n", "\n" ] } ], "source": [ "code= nandcode(f)\n", "print(code)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "code.count('\\n')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def EVAL(prog,n,m,x):\n", " \"\"\"Evaluate NAND program prog with n inputs and m outputs on input x.\"\"\"\n", " vartable = {} # dictionary for variables\n", " \n", " for i in range(n): vartable[f'X[{i}]']=x[i] # assign x[i] to variable \"X[i]\"\n", " \n", " for line in prog.split('\\n'): # split code into lines\n", " if not(line): continue # ignore empty lines\n", " a = line.find('=') \n", " b = line.find('(')\n", " c=line.find(',')\n", " d= line.find(')')\n", " \n", " foo = line[:a].strip()\n", " bar = line[b+1:c].strip()\n", " blah = line[c+1:d].strip()\n", "\n", " vartable[foo] = NAND(vartable[bar],vartable[blah])\n", " \n", " return [vartable[f'Y[{j}]'] for j in range(m)]\n", "# Explain this code to each other in groups of 2-4" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "%%html\n", "" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0 0 | [1]\n", "0 0 1 | [1]\n", "0 1 0 | [0]\n", "0 1 1 | [0]\n", "1 0 0 | [1]\n", "1 0 1 | [0]\n", "1 1 0 | [0]\n", "1 1 1 | [1]\n" ] } ], "source": [ "for a in [0,1]:\n", " for b in [0,1]:\n", " for c in [0,1]: print(f\"{a} {b} {c} | {EVAL(code,3,1,[a,b,c])}\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__Theorem:__ For every $f:\\{0,1\\}^n \\rightarrow \\{0,1\\}$, there is a NAND program of $O(2^n)$ lines that computes $f$." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def XORn(X): \n", " r = X[0]\n", " for i in range(1,len(X)):\n", " r = XOR(r,X[i])\n", " return r\n", "\n", "nandcircuit(restrict(XORn,3))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "That is, $\\{ f | f:\\{0,1\\}^n \\rightarrow \\{0,1\\} \\} \\subseteq SIZE(c\\cdot 2^n)$ (powerpoint)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__Theorem:__ For every $f:\\{0,1\\}^n \\rightarrow \\{0,1\\}$, there is a NAND program of $O(2^n)$ lines that computes $f$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__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$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "1. NAND program for $LOOKUP_n:\\{0,1\\}^{2^n+n} \\rightarrow \\{0,1\\}$ where $LOOKUP(T,i)=T_i$ for $i\\in [2^n]$\n", "\n", "2. \"Hardwire\" the outputs of $f$ inside the table $T$ to obtain a program with $n$ inputs and one output." ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def LOOKUP(T,I): # T length 2^n, I length n, return T[I]\n", " l = len(I)\n", " if l==1: return IF(I[0],T[1],T[0])\n", " \n", " return IF(I[l-1], # look at most significant bit of I\n", " LOOKUP(T[2**(l-1):],I[:-1]), # recurse on second half of T\n", " LOOKUP(T[:2**(l-1)],I[:-1])) # recurse on first half of T\n", "\n", "LOOKUP([0,0,1,1,0,1,1,0],[1,1,1])" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def LOOKUPCODE(n): # return NAND code of LOOKUP - can ignore\n", " def f(X):\n", " return LOOKUP( X[:2**n] , X[2**n:2**n+n])\n", " return nandcode(f,numargs=2**n+n)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import itertools\n", "\n", "def int2list(i, n = 0):\n", " \"\"\"Utility function: transform number to list of 0/1 values in binary rep, LSB first\"\"\"\n", " L = [int(c) for c in bin(i)[2:]][::-1]\n", " return L + [0]*max(0,n-len(L))\n", "\n", "int2list(13,6)" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def NANDPROG(f,n):\n", " # takes f:{0,1}^n --> {0,1}, returns NAND code to compute f\n", " \n", " lookupcode = LOOKUPCODE(n) # NAND code for lookup function on 2^n + n inputs\n", "\n", " for i in range(2**n):\n", " x = int2list(i,n) # binary representation of i, padded to length n\n", " lookupcode = lookupcode.replace(f'X[{i}]',('one' if f(*x) else 'zero'))\n", " \n", " for i in range(2**n,2**n+n):\n", " lookupcode = lookupcode.replace(f'X[{i}]',f'X[{i-2**n}]')\n", " \n", " preamble = '''temp = NAND(X[0],X[0])\n", "one = NAND(temp,X[0])\n", "zero = NAND(one,one)\n", "'''\n", " return preamble+lookupcode\n", "\n", "# Explain to code to one another in groups of 2-4" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%html\n", "" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def f(a,b,c): \n", " if a+b+c > 1: return 1\n", " return 0" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "temp = NAND(X[0],X[0])\n", "one = NAND(temp,X[0])\n", "zero = NAND(one,one)\n", "Temp[0] = NAND(X[0],X[0])\n", "Temp[1] = NAND(one,Temp[0])\n", "Temp[2] = NAND(one,X[0])\n", "Temp[3] = NAND(Temp[1],Temp[2])\n", "Temp[4] = NAND(X[0],X[0])\n", "Temp[5] = NAND(zero,Temp[4])\n", "Temp[6] = NAND(one,X[0])\n", "Temp[7] = NAND(Temp[5],Temp[6])\n", "Temp[8] = NAND(X[1],X[1])\n", "Temp[9] = NAND(Temp[7],Temp[8])\n", "Temp[10] = NAND(Temp[3],X[1])\n", "Temp[11] = NAND(Temp[9],Temp[10])\n", "Temp[12] = NAND(X[0],X[0])\n", "Temp[13] = NAND(zero,Temp[12])\n", "Temp[14] = NAND(one,X[0])\n", "Temp[15] = NAND(Temp[13],Temp[14])\n", "Temp[16] = NAND(X[0],X[0])\n", "Temp[17] = NAND(zero,Temp[16])\n", "Temp[18] = NAND(zero,X[0])\n", "Temp[19] = NAND(Temp[17],Temp[18])\n", "Temp[20] = NAND(X[1],X[1])\n", "Temp[21] = NAND(Temp[19],Temp[20])\n", "Temp[22] = NAND(Temp[15],X[1])\n", "Temp[23] = NAND(Temp[21],Temp[22])\n", "Temp[24] = NAND(X[2],X[2])\n", "Temp[25] = NAND(Temp[23],Temp[24])\n", "Temp[26] = NAND(Temp[11],X[2])\n", "Y[0] = NAND(Temp[25],Temp[26])\n", "\n" ] } ], "source": [ "code = NANDPROG(f,3)\n", "print(code)" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0 0 | [0]\n", "0 0 1 | [0]\n", "0 1 0 | [0]\n", "0 1 1 | [1]\n", "1 0 0 | [0]\n", "1 0 1 | [1]\n", "1 1 0 | [1]\n", "1 1 1 | [1]\n" ] } ], "source": [ "for a in [0,1]:\n", " for b in [0,1]:\n", " for c in [0,1]: print(f\"{a} {b} {c} | {EVAL(code,3,1,[a,b,c])}\")" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[1]" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def DIVTHREE(L): return 0 if sum(L) % 3 else 1\n", "def DIVTHREE5(a,b,c,d,e): return DIVTHREE( [a,b,c,d,e] )\n", "\n", "code = NANDPROG(DIVTHREE5,5)\n", "EVAL(code,5,1,[1,0,0,1,1])" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[(2, 15), (3, 31), (4, 63), (5, 127), (6, 255), (7, 511), (8, 1023), (9, 2047)]" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def code(n): return NANDPROG(restrict(DIVTHREE,n),n)\n", "[(n,code(n).count('\\n')) for n in range(2,10)]" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[3.75, 3.875, 3.9375, 3.96875, 3.984375, 3.9921875, 3.99609375, 3.998046875]" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[code(n).count('\\n')/(2**n) for n in range(2,10)]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Is this the best we can do?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__Theorem:__ $DIVTHREE_n \\in SIZE(O(n))$. \n", "That is, exists $c$ such that $DIVTHREE_n \\in SIZE(c\\cdot n)$ for every sufficiently large $n$." ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "scrolled": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%html\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__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$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__Proof outline:__ \n", "i) Represent $[3]=\\{0,1,2\\}$ as $\\{0,1\\}^2$. \n", "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!_" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "To compute $f(a_0,\\ldots,a_{n-1})$: \n", "i) Set $s=0$ \n", "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$.\n", "\n", "__Cost:__ $n \\times cost(g) = O(n)$ using main theorem!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Multi-output functions:\n", "\n", "To compute $f:\\{0,1\\}^n \\rightarrow \\{0,1\\}^2$: define\n", "\n", "* $g_0:\\{0,1\\}^n \\rightarrow \\{0,1\\}$ as $f_0(x) = f(x)_0$\n", "\n", "* $g_1:\\{0,1\\}^n \\rightarrow \\{0,1\\}$ as $f_1(x) = f(x)_1$.\n", "\n", "Using $P_0$ to compute $g_0$ and $P_1$ to compute $g_1$ can obtain $P$ to compute $f$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Bottom line\n", "\n", "* 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.\n", "\n", "* Sometimes can do _much much much_ better" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Shaving off an $n$ factor\n", "\n", "Think of $f:\\{0,1\\}^n \\rightarrow \\{0,1\\}$ as $f:\\{0,1\\}^{n-k} \\times \\{0,1\\}^k \\rightarrow \\{0,1\\}$.\n", "\n", "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$.\n", "\n", "So we can compute the map $x \\mapsto f_x$ using $O(2^k \\cdot 2^{n-k})$ lines.\n", "\n", "Afte we have $f_x$, we can compute $y \\mapsto f_x(y)=f(x,y)$ with a lookup of $O(2^k)$ lines.\n", "\n", "If we set $k = \\log(n-2\\log n)$ we get that $2^k \\cdot 2^{n-k} = O(2^n/n)$.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Next up: \"Code = Data\"\n", "\n", "Think of program $P$ as a _string_ - give it to another program $Q$ as input" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }