circuitpython-ulab/docs/ulab-vectorise.ipynb
2020-10-25 22:43:16 +01:00

776 lines
25 KiB
Text

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-01T09:27:13.438054Z",
"start_time": "2020-05-01T09:27:13.191491Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Notebook magic"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-08-03T18:32:45.342280Z",
"start_time": "2020-08-03T18:32:45.338442Z"
}
},
"outputs": [],
"source": [
"from IPython.core.magic import Magics, magics_class, line_cell_magic\n",
"from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n",
"from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n",
"import subprocess\n",
"import os"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-23T20:31:25.296014Z",
"start_time": "2020-07-23T20:31:25.265937Z"
}
},
"outputs": [],
"source": [
"@magics_class\n",
"class PyboardMagic(Magics):\n",
" @cell_magic\n",
" @magic_arguments()\n",
" @argument('-skip')\n",
" @argument('-unix')\n",
" @argument('-pyboard')\n",
" @argument('-file')\n",
" @argument('-data')\n",
" @argument('-time')\n",
" @argument('-memory')\n",
" def micropython(self, line='', cell=None):\n",
" args = parse_argstring(self.micropython, line)\n",
" if args.skip: # doesn't care about the cell's content\n",
" print('skipped execution')\n",
" return None # do not parse the rest\n",
" if args.unix: # tests the code on the unix port. Note that this works on unix only\n",
" with open('/dev/shm/micropython.py', 'w') as fout:\n",
" fout.write(cell)\n",
" proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n",
" stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
" print(proc.stdout.read().decode(\"utf-8\"))\n",
" print(proc.stderr.read().decode(\"utf-8\"))\n",
" return None\n",
" if args.file: # can be used to copy the cell content onto the pyboard's flash\n",
" spaces = \" \"\n",
" try:\n",
" with open(args.file, 'w') as fout:\n",
" fout.write(cell.replace('\\t', spaces))\n",
" printf('written cell to {}'.format(args.file))\n",
" except:\n",
" print('Failed to write to disc!')\n",
" return None # do not parse the rest\n",
" if args.data: # can be used to load data from the pyboard directly into kernel space\n",
" message = pyb.exec(cell)\n",
" if len(message) == 0:\n",
" print('pyboard >>>')\n",
" else:\n",
" print(message.decode('utf-8'))\n",
" # register new variable in user namespace\n",
" self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n",
" \n",
" if args.time: # measures the time of executions\n",
" pyb.exec('import utime')\n",
" message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n",
" \"\\nprint('execution time: {:d} us'.format(delta))\")\n",
" print(message.decode('utf-8'))\n",
" \n",
" if args.memory: # prints out memory information \n",
" message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n",
" print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n",
" message = pyb.exec(cell)\n",
" print(\">>> \", message.decode('utf-8'))\n",
" message = pyb.exec('print(mem_info())')\n",
" print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n",
"\n",
" if args.pyboard:\n",
" message = pyb.exec(cell)\n",
" print(message.decode('utf-8'))\n",
"\n",
"ip = get_ipython()\n",
"ip.register_magics(PyboardMagic)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## pyboard"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:35:35.126401Z",
"start_time": "2020-05-07T07:35:35.105824Z"
}
},
"outputs": [],
"source": [
"import pyboard\n",
"pyb = pyboard.Pyboard('/dev/ttyACM0')\n",
"pyb.enter_raw_repl()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-19T19:11:18.145548Z",
"start_time": "2020-05-19T19:11:18.137468Z"
}
},
"outputs": [],
"source": [
"pyb.exit_raw_repl()\n",
"pyb.close()"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:35:38.725924Z",
"start_time": "2020-05-07T07:35:38.645488Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import utime\n",
"import ulab as np\n",
"\n",
"def timeit(n=1000):\n",
" def wrapper(f, *args, **kwargs):\n",
" func_name = str(f).split(' ')[1]\n",
" def new_func(*args, **kwargs):\n",
" run_times = np.zeros(n, dtype=np.uint16)\n",
" for i in range(n):\n",
" t = utime.ticks_us()\n",
" result = f(*args, **kwargs)\n",
" run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n",
" print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n",
" print('\\tbest: %d us'%np.min(run_times))\n",
" print('\\tworst: %d us'%np.max(run_times))\n",
" print('\\taverage: %d us'%np.mean(run_times))\n",
" print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n",
" return result\n",
" return new_func\n",
" return wrapper\n",
"\n",
"def timeit(f, *args, **kwargs):\n",
" func_name = str(f).split(' ')[1]\n",
" def new_func(*args, **kwargs):\n",
" t = utime.ticks_us()\n",
" result = f(*args, **kwargs)\n",
" print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n",
" return result\n",
" return new_func"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"__END_OF_DEFS__"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Universal functions\n",
"\n",
"Standard mathematical functions are defined in the `vector` sub-module, and can be calculated on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). The functions execute faster with `ndarray` arguments than with iterables, because the values of the input vector can be extracted faster. \n",
"\n",
"At present, the following functions are supported:\n",
"\n",
"`acos`, `acosh`, `arctan2`, `around`, `asin`, `asinh`, `atan`, `arctan2`, `atanh`, `ceil`, `cos`, `degrees`, `erf`, `erfc`, `exp`, `expm1`, `floor`, `tgamma`, `lgamma`, `log`, `log10`, `log2`, `radians`, `sin`, `sinh`, `sqrt`, `tan`, `tanh`.\n",
"\n",
"These functions are applied element-wise to the arguments, thus, e.g., the exponential of a matrix cannot be calculated in this way. The functions can be invoked by importing the `vector` sub-module first. "
]
},
{
"cell_type": "code",
"execution_count": 525,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-20T06:50:55.762508Z",
"start_time": "2019-10-20T06:50:55.655715Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\t range(0, 9)\n",
"exp(a):\t array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float)\n",
"\n",
"b:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n",
"exp(b):\t array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float)\n",
"\n",
"c:\t array([[1.0, 2.0, 3.0],\n",
"\t [4.0, 5.0, 6.0],\n",
"\t [7.0, 8.0, 9.0]], dtype=float)\n",
"exp(c):\t array([[2.718282, 7.389056, 20.08554],\n",
"\t [54.59816, 148.4132, 403.4288],\n",
"\t [1096.633, 2980.958, 8103.084]], dtype=float)\n",
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"a = range(9)\n",
"b = np.array(a)\n",
"\n",
"# works with ranges, lists, tuples etc.\n",
"print('a:\\t', a)\n",
"print('exp(a):\\t', vector.exp(a))\n",
"\n",
"# with 1D arrays\n",
"print('\\nb:\\t', b)\n",
"print('exp(b):\\t', vector.exp(b))\n",
"\n",
"# as well as with matrices\n",
"c = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n",
"print('\\nc:\\t', c)\n",
"print('exp(c):\\t', vector.exp(c))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Computation expenses\n",
"\n",
"The overhead for calculating with micropython iterables is quite significant: for the 1000 samples below, the difference is more than 800 microseconds, because internally the function has to create the `ndarray` for the output, has to fetch the iterable's items of unknown type, and then convert them to floats. All these steps are skipped for `ndarray`s, because these pieces of information are already known. \n",
"\n",
"Doing the same with `list` comprehension requires 30 times more time than with the `ndarray`, which would become even more, if we converted the resulting list to an `ndarray`. "
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:35:45.696282Z",
"start_time": "2020-05-07T07:35:45.629909Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"iterating over ndarray in ulab\r\n",
"execution time: 441 us\r\n",
"\r\n",
"iterating over list in ulab\r\n",
"execution time: 1266 us\r\n",
"\r\n",
"iterating over list in python\r\n",
"execution time: 11379 us\r\n",
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"import math\n",
"\n",
"a = [0]*1000\n",
"b = np.array(a)\n",
"\n",
"@timeit\n",
"def timed_vector(iterable):\n",
" return vector.exp(iterable)\n",
"\n",
"@timeit\n",
"def timed_list(iterable):\n",
" return [math.exp(i) for i in iterable]\n",
"\n",
"print('iterating over ndarray in ulab')\n",
"timed_vector(b)\n",
"\n",
"print('\\niterating over list in ulab')\n",
"timed_vector(a)\n",
"\n",
"print('\\niterating over list in python')\n",
"timed_list(a)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Vectorising generic python functions\n",
"\n",
"`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html\n",
"\n",
"The examples above use factory functions. In fact, they are nothing but the vectorised versions of the standard mathematical functions. User-defined `python` functions can also be vectorised by help of `vectorize`. This function takes a positional argument, namely, the `python` function that you want to vectorise, and a non-mandatory keyword argument, `otypes`, which determines the `dtype` of the output array. The `otypes` must be `None` (default), or any of the `dtypes` defined in `ulab`. With `None`, the output is automatically turned into a float array. \n",
"\n",
"The return value of `vectorize` is a `micropython` object that can be called as a standard function, but which now accepts either a scalar, an `ndarray`, or a generic `micropython` iterable as its sole argument. Note that the function that is to be vectorised must have a single argument."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-06T22:13:35.735953Z",
"start_time": "2020-05-06T22:13:35.720709Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"f on a scalar: array([1936.0], dtype=float)\n",
"f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float)\n",
"f on a list: array([4.0, 9.0, 16.0], dtype=float)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"def f(x):\n",
" return x*x\n",
"\n",
"vf = vector.vectorize(f)\n",
"\n",
"# calling with a scalar\n",
"print('{:20}'.format('f on a scalar: '), vf(44.0))\n",
"\n",
"# calling with an ndarray\n",
"a = np.array([1, 2, 3, 4])\n",
"print('{:20}'.format('f on an ndarray: '), vf(a))\n",
"\n",
"# calling with a list\n",
"print('{:20}'.format('f on a list: '), vf([2, 3, 4]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As mentioned, the `dtype` of the resulting `ndarray` can be specified via the `otypes` keyword. The value is bound to the function object that `vectorize` returns, therefore, if the same function is to be vectorised with different output types, then for each type a new function object must be created."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-06T22:17:56.649769Z",
"start_time": "2020-05-06T22:17:56.639524Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"output is uint8: array([1, 4, 9, 16], dtype=uint8)\n",
"output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"l = [1, 2, 3, 4]\n",
"def f(x):\n",
" return x*x\n",
"\n",
"vf1 = vector.vectorize(f, otypes=np.uint8)\n",
"vf2 = vector.vectorize(f, otypes=np.float)\n",
"\n",
"print('{:20}'.format('output is uint8: '), vf1(l))\n",
"print('{:20}'.format('output is float: '), vf2(l))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `otypes` keyword argument cannot be used for type coercion: if the function evaluates to a float, but `otypes` would dictate an integer type, an exception will be raised:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-06T22:21:43.616220Z",
"start_time": "2020-05-06T22:21:43.601280Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"integer list: array([1, 4, 9, 16], dtype=uint8)\n",
"\n",
"Traceback (most recent call last):\n",
" File \"/dev/shm/micropython.py\", line 14, in <module>\n",
"TypeError: can't convert float to int\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"int_list = [1, 2, 3, 4]\n",
"float_list = [1.0, 2.0, 3.0, 4.0]\n",
"def f(x):\n",
" return x*x\n",
"\n",
"vf = vector.vectorize(f, otypes=np.uint8)\n",
"\n",
"print('{:20}'.format('integer list: '), vf(int_list))\n",
"# this will raise a TypeError exception\n",
"print(vf(float_list))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Benchmarks\n",
"\n",
"It should be pointed out that the `vectorize` function produces the pseudo-vectorised version of the `python` function that is fed into it, i.e., on the C level, the same `python` function is called, with the all-encompassing `mp_obj_t` type arguments, and all that happens is that the `for` loop in `[f(i) for i in iterable]` runs purely in C. Since type checking and type conversion in `f()` is expensive, the speed-up is not so spectacular as when iterating over an `ndarray` with a factory function: a gain of approximately 30% can be expected, when a native `python` type (e.g., `list`) is returned by the function, and this becomes around 50% (a factor of 2), if conversion to an `ndarray` is also counted.\n",
"\n",
"The following code snippet calculates the square of a 1000 numbers with the vectorised function (which returns an `ndarray`), with `list` comprehension, and with `list` comprehension followed by conversion to an `ndarray`. For comparison, the execution time is measured also for the case, when the square is calculated entirely in `ulab`."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"ExecuteTime": {
"end_time": "2020-05-07T07:32:20.048553Z",
"start_time": "2020-05-07T07:32:19.951851Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"vectorised function\r\n",
"execution time: 7237 us\r\n",
"\r\n",
"list comprehension\r\n",
"execution time: 10248 us\r\n",
"\r\n",
"list comprehension + ndarray conversion\r\n",
"execution time: 12562 us\r\n",
"\r\n",
"squaring an ndarray entirely in ulab\r\n",
"execution time: 560 us\r\n",
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"def f(x):\n",
" return x*x\n",
"\n",
"vf = vector.vectorize(f)\n",
"\n",
"@timeit\n",
"def timed_vectorised_square(iterable):\n",
" return vf(iterable)\n",
"\n",
"@timeit\n",
"def timed_python_square(iterable):\n",
" return [f(i) for i in iterable]\n",
"\n",
"@timeit\n",
"def timed_ndarray_square(iterable):\n",
" return np.array([f(i) for i in iterable])\n",
"\n",
"@timeit\n",
"def timed_ulab_square(ndarray):\n",
" return ndarray**2\n",
"\n",
"print('vectorised function')\n",
"squares = timed_vectorised_square(range(1000))\n",
"\n",
"print('\\nlist comprehension')\n",
"squares = timed_python_square(range(1000))\n",
"\n",
"print('\\nlist comprehension + ndarray conversion')\n",
"squares = timed_ndarray_square(range(1000))\n",
"\n",
"print('\\nsquaring an ndarray entirely in ulab')\n",
"a = np.array(range(1000))\n",
"squares = timed_ulab_square(a)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"From the comparisons above, it is obvious that `python` functions should only be vectorised, when the same effect cannot be gotten in `ulab` only. However, although the time savings are not significant, there is still a good reason for caring about vectorised functions. Namely, user-defined `python` functions become universal, i.e., they can accept generic iterables as well as `ndarray`s as their arguments. A vectorised function is still a one-liner, resulting in transparent and elegant code.\n",
"\n",
"A final comment on this subject: the `f(x)` that we defined is a *generic* `python` function. This means that it is not required that it just crunches some numbers. It has to return a number object, but it can still access the hardware in the meantime. So, e.g., \n",
"\n",
"```python\n",
"\n",
"led = pyb.LED(2)\n",
"\n",
"def f(x):\n",
" if x < 100:\n",
" led.toggle()\n",
" return x*x\n",
"```\n",
"\n",
"is perfectly valid code."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## around\n",
"\n",
"`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html\n",
"\n",
"`numpy`'s `around` function can also be found in the `vector` sub-module. The function implements the `decimals` keyword argument with default value `0`. The first argument must be an `ndarray`. If this is not the case, the function raises a `TypeError` exception. Note that `numpy` accepts general iterables. The `out` keyword argument known from `numpy` is not accepted. The function always returns an ndarray of type `mp_float_t`."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"ExecuteTime": {
"end_time": "2020-03-12T16:24:36.435638Z",
"start_time": "2020-03-12T16:24:36.424468Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float)\n",
"\n",
"decimals = 0\t array([1.0, 2.0, 33.0, 444.0], dtype=float)\n",
"\n",
"decimals = 1\t array([1.0, 2.2, 33.3, 444.4], dtype=float)\n",
"\n",
"decimals = -1\t array([0.0, 0.0, 30.0, 440.0], dtype=float)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"a = np.array([1, 2.2, 33.33, 444.444])\n",
"print('a:\\t\\t', a)\n",
"print('\\ndecimals = 0\\t', vector.around(a, decimals=0))\n",
"print('\\ndecimals = 1\\t', vector.around(a, decimals=1))\n",
"print('\\ndecimals = -1\\t', vector.around(a, decimals=-1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## arctan2\n",
"\n",
"`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html\n",
"\n",
"The two-argument inverse tangent function is also part of the `vector` sub-module. The function implements broadcasting as discussed in the section on `ndarray`s. Scalars (`micropython` integers or floats) are also allowed."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"ExecuteTime": {
"end_time": "2020-03-16T18:34:22.795475Z",
"start_time": "2020-03-16T18:34:22.781447Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float)\n",
"\n",
"arctan2(a, 1.0)\t array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float)\n",
"\n",
"arctan2(1.0, a)\t array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float)\n",
"\n",
"arctan2(a, a): \t array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"from ulab import vector\n",
"\n",
"a = np.array([1, 2.2, 33.33, 444.444])\n",
"print('a:\\t\\t', a)\n",
"print('\\narctan2(a, 1.0)\\t', vector.arctan2(a, 1.0))\n",
"print('\\narctan2(1.0, a)\\t', vector.arctan2(1.0, a))\n",
"print('\\narctan2(a, a): \\t', vector.arctan2(a, a))"
]
}
],
"metadata": {
"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.7.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "382.797px"
},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}