{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp utils.common_utils"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Common utils\n",
    "> A collection of utilities often used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import sys\n",
    "import os\n",
    "import ssl\n",
    "import os.path as osp\n",
    "from six.moves import urllib\n",
    "import errno\n",
    "import tarfile\n",
    "import zipfile\n",
    "import bz2\n",
    "import gzip\n",
    "import pandas as pd\n",
    "from tabulate import tabulate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#exporti\n",
    "def makedirs(path):\n",
    "    try:\n",
    "        os.makedirs(osp.expanduser(osp.normpath(path)))\n",
    "    except OSError as e:\n",
    "        if e.errno != errno.EEXIST and osp.isdir(path):\n",
    "            raise e"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def wget_download(url, savepath):\n",
    "    import wget\n",
    "    wget.download(url, str(savepath))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def download_url(url: str, folder: str, log: bool = True):\n",
    "    r\"\"\"Downloads the content of an URL to a specific folder.\n",
    "    Args:\n",
    "        url (string): The url.\n",
    "        folder (string): The folder.\n",
    "        log (bool, optional): If :obj:`False`, will not print anything to the\n",
    "            console. (default: :obj:`True`)\n",
    "    \"\"\"\n",
    "\n",
    "    filename = url.rpartition('/')[2]\n",
    "    filename = filename if filename[0] == '?' else filename.split('?')[0]\n",
    "    path = osp.join(folder, filename)\n",
    "\n",
    "    if osp.exists(path):  # pragma: no cover\n",
    "        if log:\n",
    "            print(f'Using existing file {filename}', file=sys.stderr)\n",
    "        return path\n",
    "\n",
    "    if log:\n",
    "        print(f'Downloading {url}', file=sys.stderr)\n",
    "\n",
    "    makedirs(folder)\n",
    "\n",
    "    context = ssl._create_unverified_context()\n",
    "    data = urllib.request.urlopen(url, context=context)\n",
    "\n",
    "    with open(path, 'wb') as f:\n",
    "        f.write(data.read())\n",
    "\n",
    "    return path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading https://files.grouplens.org/datasets/movielens/ml-1m.zip\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'./data/bronze/ml-1m.zip'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "download_url('https://files.grouplens.org/datasets/movielens/ml-1m.zip',\n",
    "             './data/bronze')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "./data\n",
      "└── bronze\n",
      "    └── ml-1m.zip\n",
      "\n",
      "1 directory, 1 file\n"
     ]
    }
   ],
   "source": [
    "!tree ./data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[01;34m./data\u001b[00m\n",
      "├── [ 24M]  \u001b[01;34mbronze\u001b[00m\n",
      "│   └── [ 24M]  \u001b[01;34mml-1m\u001b[00m\n",
      "│       ├── [167K]  movies.dat\n",
      "│       ├── [ 23M]  ratings.dat\n",
      "│       ├── [5.4K]  README\n",
      "│       └── [131K]  users.dat\n",
      "└── [3.0M]  \u001b[01;34msilver\u001b[00m\n",
      "    └── [3.0M]  \u001b[01;34mml-1m_min_rating0-min_uc5-min_sc5-splitleave_one_out\u001b[00m\n",
      "        └── [3.0M]  dataset.pkl\n",
      "\n",
      "  27M used in 4 directories, 5 files\n"
     ]
    }
   ],
   "source": [
    "!tree --du -h -C ./data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Extract"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#exporti\n",
    "def maybe_log(path, log=True):\n",
    "    if log:\n",
    "        print(f'Extracting {path}', file=sys.stderr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def extract_tar(path: str, folder: str, mode: str = 'r:gz', log: bool = True):\n",
    "    r\"\"\"Extracts a tar archive to a specific folder.\n",
    "    Args:\n",
    "        path (string): The path to the tar archive.\n",
    "        folder (string): The folder.\n",
    "        mode (string, optional): The compression mode. (default: :obj:`\"r:gz\"`)\n",
    "        log (bool, optional): If :obj:`False`, will not print anything to the\n",
    "            console. (default: :obj:`True`)\n",
    "    \"\"\"\n",
    "    maybe_log(path, log)\n",
    "    with tarfile.open(path, mode) as f:\n",
    "        f.extractall(folder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def extract_zip(path: str, folder: str, log: bool = True):\n",
    "    r\"\"\"Extracts a zip archive to a specific folder.\n",
    "    Args:\n",
    "        path (string): The path to the tar archive.\n",
    "        folder (string): The folder.\n",
    "        log (bool, optional): If :obj:`False`, will not print anything to the\n",
    "            console. (default: :obj:`True`)\n",
    "    \"\"\"\n",
    "    maybe_log(path, log)\n",
    "    with zipfile.ZipFile(path, 'r') as f:\n",
    "        f.extractall(folder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def extract_bz2(path: str, folder: str, log: bool = True):\n",
    "    r\"\"\"Extracts a bz2 archive to a specific folder.\n",
    "    Args:\n",
    "        path (string): The path to the tar archive.\n",
    "        folder (string): The folder.\n",
    "        log (bool, optional): If :obj:`False`, will not print anything to the\n",
    "            console. (default: :obj:`True`)\n",
    "    \"\"\"\n",
    "    maybe_log(path, log)\n",
    "    path = osp.abspath(path)\n",
    "    with bz2.open(path, 'r') as r:\n",
    "        with open(osp.join(folder, '.'.join(path.split('.')[:-1])), 'wb') as w:\n",
    "            w.write(r.read())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def extract_gz(path: str, folder: str, log: bool = True):\n",
    "    r\"\"\"Extracts a gz archive to a specific folder.\n",
    "    Args:\n",
    "        path (string): The path to the tar archive.\n",
    "        folder (string): The folder.\n",
    "        log (bool, optional): If :obj:`False`, will not print anything to the\n",
    "            console. (default: :obj:`True`)\n",
    "    \"\"\"\n",
    "    maybe_log(path, log)\n",
    "    path = osp.abspath(path)\n",
    "    with gzip.open(path, 'r') as r:\n",
    "        with open(osp.join(folder, '.'.join(path.split('.')[:-1])), 'wb') as w:\n",
    "            w.write(r.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tabulate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def print_result_as_table(results, tag=None):\n",
    "    \"\"\"Print results as a table.\"\"\"\n",
    "    eval_infos = set()\n",
    "    for result in results:\n",
    "        eval_infos.update(result.keys())\n",
    "    eval_infos = list(eval_infos)\n",
    "    print(\"-\" * 80)\n",
    "    if tag is not None:\n",
    "        print(tag)\n",
    "    for result in results:\n",
    "        for eval_info in eval_infos:\n",
    "            if eval_info not in result:\n",
    "                result[eval_info] = \"--\"\n",
    "    df = pd.DataFrame(results)\n",
    "    df = df.set_index(\"model\")\n",
    "    df = df.T\n",
    "    print(tabulate(df, headers=df.columns, tablefmt=\"psql\"))\n",
    "    print(\"-\" * 80)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "+------+------+-------+\n",
      "|      | MF   |   NCF |\n",
      "|------+------+-------|\n",
      "| MRR  | 0.35 |  0.42 |\n",
      "| nDCG | --   |  0.25 |\n",
      "+------+------+-------+\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "results = [{'model':'MF', 'MRR':.35},\n",
    "           {'model':'NCF', 'MRR':.42, 'nDCG':.25}]\n",
    "\n",
    "print_result_as_table(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Listing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def list_files(startpath):\n",
    "    \"\"\"\n",
    "    Util function to print the nested structure of a directory\n",
    "    \"\"\"\n",
    "    for root, dirs, files in os.walk(startpath):\n",
    "        level = root.replace(startpath, \"\").count(os.sep)\n",
    "        indent = \" \" * 4 * (level)\n",
    "        print(\"{}{}/\".format(indent, os.path.basename(root)))\n",
    "        subindent = \" \" * 4 * (level + 1)\n",
    "        for f in files:\n",
    "            print(\"{}{}\".format(subindent, f))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sample_data/\n",
      "    README.md\n",
      "    anscombe.json\n",
      "    california_housing_train.csv\n",
      "    california_housing_test.csv\n",
      "    mnist_test.csv\n",
      "    mnist_train_small.csv\n"
     ]
    }
   ],
   "source": [
    "list_files('./sample_data')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deterministic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import sys\n",
    "import os\n",
    "import numpy as np\n",
    "import torch\n",
    "import random"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def seed_everything(seed=40):\n",
    "    \"\"\"sets the random seed to establish deterministic behaviors\n",
    "\n",
    "    Args:\n",
    "        seed (int): the random seed integer\n",
    "    \"\"\"\n",
    "    random.seed(seed)\n",
    "    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n",
    "    np.random.seed(seed)\n",
    "    torch.manual_seed(seed)\n",
    "    torch.cuda.manual_seed(seed)\n",
    "    torch.backends.cudnn.deterministic = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mapping, Masking, and Padding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import random\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import torch\n",
    "from torch.nn import functional as F"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "def map_column(df: pd.DataFrame, col_name: str):\n",
    "    \"\"\"Maps column values to integers.\n",
    "    \"\"\"\n",
    "    values = sorted(list(df[col_name].unique()))\n",
    "    mapping = {k: i + 2 for i, k in enumerate(values)}\n",
    "    inverse_mapping = {v: k for k, v in mapping.items()}\n",
    "    df[col_name + \"_mapped\"] = df[col_name].map(mapping)\n",
    "    return df, mapping, inverse_mapping\n",
    "\n",
    "def get_context(df: pd.DataFrame, split: str, context_size: int = 120, val_context_size: int = 5, seed: int = 42):\n",
    "    \"\"\"Create a training / validation samples.\n",
    "    \"\"\"\n",
    "    random.seed(seed)\n",
    "    if split == \"train\":\n",
    "        end_index = random.randint(10, df.shape[0] - val_context_size)\n",
    "    elif split in [\"val\", \"test\"]:\n",
    "        end_index = df.shape[0]\n",
    "    else:\n",
    "        raise ValueError\n",
    "    start_index = max(0, end_index - context_size)\n",
    "    context = df[start_index:end_index]\n",
    "    return context\n",
    "\n",
    "## Padding\n",
    "\n",
    "def pad_arr(arr: np.ndarray, expected_size: int = 30):\n",
    "    \"\"\"Pad top of array when there is not enough history.\n",
    "    \"\"\"\n",
    "    arr = np.pad(arr, [(expected_size - arr.shape[0], 0), (0, 0)], mode=\"edge\")\n",
    "    return arr\n",
    "\n",
    "def pad_list(list_integers, history_size: int, pad_val: int = 0, mode=\"left\"):\n",
    "    \"\"\"Pad list from left or right\n",
    "    \"\"\"\n",
    "    if len(list_integers) < history_size:\n",
    "        if mode == \"left\":\n",
    "            list_integers = [pad_val] * (history_size - len(list_integers)) + list_integers\n",
    "        else:\n",
    "            list_integers = list_integers + [pad_val] * (history_size - len(list_integers))\n",
    "    return list_integers\n",
    "\n",
    "# Masking\n",
    "\n",
    "def mask_list(l1, p=0.8, mask=1, seed=42):\n",
    "    random.seed(seed)\n",
    "    l1 = [a if random.random() < p else mask for a in l1]\n",
    "    return l1\n",
    "\n",
    "def mask_last_elements_list(l1, val_context_size: int = 5, seed=42):\n",
    "    l1 = l1[:-val_context_size] + mask_list(l1[-val_context_size:], p=0.5, seed=seed)\n",
    "    return l1\n",
    "\n",
    "def masked_accuracy(y_pred: torch.Tensor, y_true: torch.Tensor, mask: torch.Tensor):\n",
    "    _, predicted = torch.max(y_pred, 1)\n",
    "    y_true = torch.masked_select(y_true, mask)\n",
    "    predicted = torch.masked_select(predicted, mask)\n",
    "    acc = (y_true == predicted).double().mean()\n",
    "    return acc\n",
    "\n",
    "def masked_ce(y_pred, y_true, mask):\n",
    "    loss = F.cross_entropy(y_pred, y_true, reduction=\"none\")\n",
    "    loss = loss * mask\n",
    "    return loss.sum() / (mask.sum() + 1e-8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import unittest\n",
    "from numpy.testing import assert_array_equal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestUtils(unittest.TestCase):\n",
    "    def testColMapping(self):\n",
    "        \"test the column mapping function\"\n",
    "        df = pd.DataFrame(\n",
    "            {'uid': [1,2,3,4],\n",
    "             'sid': [1,3,5,7]}\n",
    "        )\n",
    "        df, _, _ = map_column(df, col_name='sid')\n",
    "        assert_array_equal(df.sid_mapped.values,\n",
    "                           [2, 3, 4, 5])\n",
    "        \n",
    "    def testSplit(self):\n",
    "        \"test the train/test/val split\"\n",
    "        SEED = 42\n",
    "        df = pd.DataFrame(\n",
    "            {'uid': list(np.arange(50)),\n",
    "                'sid': list(np.arange(50))}\n",
    "        )\n",
    "        context = get_context(df, split='train', context_size=5, seed=SEED)\n",
    "        assert_array_equal(context.sid.values,\n",
    "                           [12, 13, 14, 15, 16])\n",
    "        \n",
    "    def testArrayPadding(self):\n",
    "        \"test array padding function\"\n",
    "        pad_output_1 = pad_arr(np.array([[1,2,3],[7,8,9]]), expected_size=5)\n",
    "        pad_output_2 = pad_arr(np.array([[1,2,3]]), expected_size=3)\n",
    "        assert_array_equal(pad_output_1,\n",
    "                           [[1, 2, 3],\n",
    "                            [1, 2, 3],\n",
    "                            [1, 2, 3],\n",
    "                            [1, 2, 3],\n",
    "                            [7, 8, 9]])\n",
    "        assert_array_equal(pad_output_2,\n",
    "                           [[1, 2, 3],\n",
    "                            [1, 2, 3],\n",
    "                            [1, 2, 3]])\n",
    "        \n",
    "    def testListPadding(self):\n",
    "        \"test list padding function\"\n",
    "        pad_output_1 = pad_list([1,2,3], history_size=5, pad_val=0, mode='left')\n",
    "        pad_output_2 = pad_list([1,2,3], history_size=6, pad_val=1, mode='right')\n",
    "        assert_array_equal(pad_output_1,\n",
    "                           [0, 0, 1, 2, 3])\n",
    "        assert_array_equal(pad_output_2,\n",
    "                           [1, 2, 3, 1, 1, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestModelUtils(unittest.TestCase):\n",
    "    def testMaskedAccuracy(self):\n",
    "        \"test the masked accuracy\"\n",
    "        output1 = masked_accuracy(torch.Tensor([[0,1,1,0]]),\n",
    "                                torch.Tensor([[0,1,1,1]]),\n",
    "                                torch.tensor([1,1,1,1], dtype=torch.bool))\n",
    "\n",
    "        output2 = masked_accuracy(torch.Tensor([[0,1,1,0]]),\n",
    "                                torch.Tensor([[0,1,1,1]]),\n",
    "                                torch.tensor([1,0,0,1], dtype=torch.bool))\n",
    "\n",
    "        self.assertEqual(output1, torch.tensor(0.75, dtype=torch.float64))\n",
    "        self.assertEqual(output2, torch.tensor(0.5, dtype=torch.float64))\n",
    "\n",
    "    def testMaskedCrossEntropy(self):\n",
    "        input = [[1.1049, 1.5729, 1.4864],\n",
    "        [-1.8321, -0.3137, -0.3257]]\n",
    "        target = [0,2]\n",
    "\n",
    "        output1 = masked_ce(torch.tensor(input),\n",
    "                            torch.tensor(target),\n",
    "                            torch.tensor([1,0], dtype=torch.bool))\n",
    "\n",
    "        output2 = masked_ce(torch.tensor(input), \n",
    "                            torch.tensor(target),\n",
    "                            torch.tensor([1,1], dtype=torch.bool))\n",
    "        \n",
    "        assert_array_equal(output1.numpy().round(4),\n",
    "                           np.array(1.4015, dtype=np.float32))\n",
    "        assert_array_equal(output2.numpy().round(4),\n",
    "                           np.array(1.1026, dtype=np.float32))\n",
    "        \n",
    "    def testMaskList(self):\n",
    "        seed = 42\n",
    "        assert_array_equal(mask_list([1,2,3,4,5,6,7,8], seed=seed),\n",
    "                           [1,2,3,4,5,6,1,8])\n",
    "        seed = 40\n",
    "        assert_array_equal(mask_list([1,2,3,4,5,6,7,8], seed=seed),\n",
    "                           [1,1,3,4,1,6,7,8])\n",
    "\n",
    "    def testMaskListLastElement(self):\n",
    "        seed = 42\n",
    "        output1 = mask_last_elements_list([1,2,3,4,5,6,7,8], val_context_size=5, seed=seed)\n",
    "        output2 = mask_last_elements_list([1,2,3,4,5,6,7,8], val_context_size=3, seed=seed)\n",
    "        assert_array_equal(output1, [1,2,3,1,5,6,7,1])\n",
    "        assert_array_equal(output2, [1,2,3,4,5,1,7,8])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "testMaskList (__main__.TestModelUtils) ... ok\n",
      "testMaskListLastElement (__main__.TestModelUtils) ... ok\n",
      "testMaskedAccuracy (__main__.TestModelUtils)\n",
      "test the masked accuracy ... ok\n",
      "testMaskedCrossEntropy (__main__.TestModelUtils) ... ok\n",
      "testArrayPadding (__main__.TestUtils)\n",
      "test array padding function ... ok\n",
      "testColMapping (__main__.TestUtils)\n",
      "test the column mapping function ... ok\n",
      "testListPadding (__main__.TestUtils)\n",
      "test list padding function ... ok\n",
      "testSplit (__main__.TestUtils)\n",
      "test the train/test/val split ... ok\n",
      "\n",
      "----------------------------------------------------------------------\n",
      "Ran 8 tests in 0.032s\n",
      "\n",
      "OK\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<unittest.main.TestProgram at 0x7fc8ba85ced0>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-21 12:52:30\n",
      "\n",
      "Compiler    : GCC 7.5.0\n",
      "OS          : Linux\n",
      "Release     : 5.4.104+\n",
      "Machine     : x86_64\n",
      "Processor   : x86_64\n",
      "CPU cores   : 2\n",
      "Architecture: 64bit\n",
      "\n",
      "sys    : 3.7.12 (default, Sep 10 2021, 00:21:48) \n",
      "[GCC 7.5.0]\n",
      "google : 2.0.3\n",
      "numpy  : 1.19.5\n",
      "pandas : 1.1.5\n",
      "tarfile: 0.9.0\n",
      "six    : 1.15.0\n",
      "IPython: 5.5.0\n",
      "\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "%reload_ext watermark\n",
    "%watermark -a \"Sparsh A.\" -m -iv -u -t -d"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
