{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "30affe0d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import torch\n",
    "# print(torch.__version__)\n",
    "# print(torch.cuda.is_available())\n",
    "# print(torch.cuda.device_count())\n",
    "# # 1.10.0+cu113\n",
    "# # True\n",
    "# # 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "da6a8605",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Populating the interactive namespace from numpy and matplotlib\n"
     ]
    }
   ],
   "source": [
    "%pylab inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b2041515",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['CUDA_VISIBLE_DEVICES'] = '1'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d504830d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2649e36",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "e4dca123",
   "metadata": {},
   "source": [
    "### Do the predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9134dec1",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[NeMo W 2022-03-09 17:04:32 optimizers:55] Apex was not found. Using the lamb or fused_adam optimizer will error out.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[NeMo I 2022-03-09 17:04:33 cloud:56] Found existing object /home/georg/.cache/torch/NeMo/NeMo_1.8.0rc0/stt_en_contextnet_1024/ad5749479c87c9e9c4ab3ebe491ebd99/stt_en_contextnet_1024.nemo.\n",
      "[NeMo I 2022-03-09 17:04:33 cloud:62] Re-using file from: /home/georg/.cache/torch/NeMo/NeMo_1.8.0rc0/stt_en_contextnet_1024/ad5749479c87c9e9c4ab3ebe491ebd99/stt_en_contextnet_1024.nemo\n",
      "[NeMo I 2022-03-09 17:04:33 common:704] Instantiating model from pre-trained checkpoint\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "################################################################################\n",
      "### WARNING, path does not exist: KALDI_ROOT=/mnt/matylda5/iveselyk/Tools/kaldi-trunk\n",
      "###          (please add 'export KALDI_ROOT=<your_path>' in your $HOME/.profile)\n",
      "###          (or run as: KALDI_ROOT=<your_path> python <your_script>.py)\n",
      "################################################################################\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[NeMo I 2022-03-09 17:04:37 mixins:146] Tokenizer SentencePieceTokenizer initialized with 1024 tokens\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[NeMo W 2022-03-09 17:04:37 modelPT:148] If you intend to do training or fine-tuning, please call the ModelPT.setup_training_data() method and provide a valid configuration file to setup the train data loader.\n",
      "    Train config : \n",
      "    manifest_filepath: null\n",
      "    sample_rate: 16000\n",
      "    batch_size: 32\n",
      "    trim_silence: false\n",
      "    max_duration: 20.0\n",
      "    shuffle: true\n",
      "    is_tarred: false\n",
      "    tarred_audio_filepaths: null\n",
      "    tarred_shard_strategy: scatter\n",
      "    use_start_end_token: false\n",
      "    \n",
      "[NeMo W 2022-03-09 17:04:37 modelPT:155] If you intend to do validation, please call the ModelPT.setup_validation_data() or ModelPT.setup_multiple_validation_data() method and provide a valid configuration file to setup the validation data loader(s). \n",
      "    Validation config : \n",
      "    manifest_filepath: null\n",
      "    sample_rate: 16000\n",
      "    batch_size: 32\n",
      "    shuffle: false\n",
      "    use_start_end_token: false\n",
      "    \n",
      "[NeMo W 2022-03-09 17:04:37 modelPT:161] Please call the ModelPT.setup_test_data() or ModelPT.setup_multiple_test_data() method and provide a valid configuration file to setup the test data loader(s).\n",
      "    Test config : \n",
      "    manifest_filepath: null\n",
      "    sample_rate: 16000\n",
      "    batch_size: 32\n",
      "    shuffle: false\n",
      "    use_start_end_token: false\n",
      "    \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[NeMo I 2022-03-09 17:04:37 features:255] PADDING: 16\n",
      "[NeMo I 2022-03-09 17:04:37 features:272] STFT using torch\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[NeMo W 2022-03-09 17:04:39 nemo_logging:349] /home/georg/venvs/ml/lib/python3.8/site-packages/torch/nn/modules/rnn.py:62: UserWarning: dropout option adds dropout after all but last recurrent layer, so non-zero dropout expects num_layers greater than 1, but got dropout=0.1 and num_layers=1\n",
      "      warnings.warn(\"dropout option adds dropout after all but last \"\n",
      "    \n",
      "[NeMo W 2022-03-09 17:04:39 rnnt:740] `experimental_fuse_loss_wer` will be deprecated in NeMo 1.6. Please use `fuse_loss_wer` instead.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[NeMo I 2022-03-09 17:04:39 rnnt_models:207] Using RNNT Loss : warprnnt_numba\n",
      "    Loss warprnnt_numba_kwargs: {'fastemit_lambda': 0.001}\n",
      "[NeMo I 2022-03-09 17:04:39 audio_preprocessing:496] Numba CUDA SpecAugment kernel is being used\n",
      "[NeMo I 2022-03-09 17:04:43 save_restore_connector:157] Model EncDecRNNTBPEModel was successfully restored from /home/georg/.cache/torch/NeMo/NeMo_1.8.0rc0/stt_en_contextnet_1024/ad5749479c87c9e9c4ab3ebe491ebd99/stt_en_contextnet_1024.nemo.\n"
     ]
    }
   ],
   "source": [
    "import nemo.collections.asr as nemo_asr\n",
    "\n",
    "asr_model = nemo_asr.models.EncDecRNNTBPEModel.from_pretrained(model_name=\"stt_en_contextnet_1024\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6d7aa85f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "87282e3fad64467fa96bb7ebe0f1f49c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Transcribing:   0%|          | 0/1 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "'president joe biden backed by the full symbolic power of the western alliance is locked in a showdown with russian president vladimir putin who is using ukraine as a hostage to try to force the u s to renegotiate the settled outcome of the cold war'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "asr_model.transcribe([\"/home/georg/data/sample_audio/russia.wav\"])[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f7fc0b42",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "523d24bf5bd44570a81cf627e19aea1c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Transcribing:   0%|          | 0/1 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "'that state'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "asr_model.transcribe([\"/mnt/data-ssd-1/data/supreme_corpus2/processed_data/slices/12-574/576970_588340.wav\"])[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4060c49f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "708dbf2c5fe34ff4b7a76111473f66da",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Transcribing:   0%|          | 0/1 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "'significant harmful impacts to wenuski residents through potential threats to future funding whereas winuski and state of vermont policy makers and government employees are reliant on accurate population accounts and census data to inform local and state policy'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ps_dir = \"/mnt/data-ssd-1/data/peoplespeech/subset/\"\n",
    "asr_model.transcribe([\n",
    "    ps_dir + \"cctv17vt-4_2_18_Winooski_City_Council/4_2_18_Winooski_City_Council_DOT_mp3_00350.flac\"\n",
    "])[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ea573fdb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "DATA_DIR = \"/mnt/data-ssd-1/data/supreme_corpus2/processed_data/\"\n",
    "with open(DATA_DIR + \"slices_meta.jsonl\") as f:\n",
    "    slice_meta = [json.loads(e) for e in f]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "92094932",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "651371cb4ef749148b5ac3edf441728c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Transcribing:   0%|          | 0/1 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['this court has made it clear that permanent incorrigibility is incapacity for rehabilitation']\n"
     ]
    }
   ],
   "source": [
    "out = asr_model.transcribe([DATA_DIR + slice_meta[24004][\"path\"]], batch_size=32)\n",
    "print(out[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "b5cde134",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "69962e3bb30f41e7a29f55ca0266e368",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Transcribing:   0%|          | 0/2 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "out = asr_model.transcribe([DATA_DIR + e[\"path\"] for e in slice_meta][:64], batch_size=32)\n",
    "preds = out[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "b102a9c8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['we will hear argument this morning in case twenty one one forty seven egbert versus boule miss harris mister chief justice and may it please the court',\n",
       " 'decades of intervening cases',\n",
       " \"reject that premise and remove any doctrinal basis for bivan's extensions second this court has held that any reason to think congress might doubt the efficacy or the necessity of a damages remedy\"]"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "preds[:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fa6b9881",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ccc80fd",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "276d34da",
   "metadata": {},
   "outputs": [],
   "source": [
    "# look for outliers, eg streaks of insertion (esp at boundaries)\n",
    "# find hard examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a03548a",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(DATA_DIR + \"nemo_preds.jsonl\", \"w\") as f:\n",
    "    for e, p in zip(slice_meta, preds):\n",
    "        json.dumps({e[\"uuid\"]: p}) + \"\\n\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06421e26",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c96af1ee",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57a2d81c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f8506c6",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58f11a35",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b00b3837",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d759b782",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "4ad6bd5a",
   "metadata": {},
   "source": [
    "## Playground"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "c4dc22e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import argparse\n",
    "import json\n",
    "import os\n",
    "import tempfile\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import tqdm\n",
    "\n",
    "from suno_utils.datasets.supreme_court.constants import SAMPLE_RATE\n",
    "from suno_utils.datasets.supreme_court.parser import get_alignment_fulltext_plus_meta, parse_pdf\n",
    "from suno_utils.datasets.supreme_court.scraper import get_raw_audio_dir, get_raw_text_dir\n",
    "from suno_utils.datasets.supreme_court.slicer import get_slices\n",
    "from suno_utils.utils.alignment import get_gentle_alignment, process_align_meta\n",
    "from suno_utils.utils.conversion import convert_audio, write_wav\n",
    "\n",
    "\n",
    "def _write_slices(processed_data_dir, uuid, slice_objects):\n",
    "    slices_dir = processed_data_dir + \"slices/\" + uuid + \"/\"\n",
    "    if not os.path.exists(slices_dir):\n",
    "        os.makedirs(slices_dir)\n",
    "    slice_meta = []\n",
    "    for slice_audio_bytes, ((start_s, end_s), turns) in slice_objects:\n",
    "        slice_fn = \"{}_{}.wav\".format(int(start_s * 1_000), int(end_s * 1_000))\n",
    "        write_wav(slices_dir + slice_fn, slice_audio_bytes, sample_rate=SAMPLE_RATE)\n",
    "        slice_meta.append({\n",
    "            \"uuid\": uuid,\n",
    "            \"path\": \"slices/\" + uuid + \"/\" + slice_fn,\n",
    "            \"timestamp\": (start_s, end_s),\n",
    "            \"transcript\": [\n",
    "                {\n",
    "                    \"text\": text,\n",
    "                    \"speaker\": speaker,\n",
    "                }\n",
    "                for speaker, text in turns\n",
    "            ],\n",
    "        })\n",
    "    return slice_meta\n",
    "\n",
    "\n",
    "def _load_jsonl(fp):\n",
    "    data = []\n",
    "    with open(fp) as f:\n",
    "        for line in f:\n",
    "            data.append(json.loads(line))\n",
    "    return data\n",
    "\n",
    "\n",
    "def _write_jsonl(data, fp):\n",
    "    with open(fp, \"w\") as f:\n",
    "        for l in data:\n",
    "            json.dump(l, f)\n",
    "            f.write(\"\\n\")\n",
    "\n",
    "\n",
    "def do_slicing(uuid, raw_data_dir, processed_data_dir, align_dir):\n",
    "    # create a tmp dir for wav\n",
    "    with tempfile.TemporaryDirectory() as temp_dir:\n",
    "        # create wav file of known sample rate\n",
    "        mp3_fp = get_raw_audio_dir(raw_data_dir) + uuid + \".mp3\"\n",
    "        wav_fp = temp_dir + uuid + \".wav\"\n",
    "        convert_audio(mp3_fp, wav_fp, sample_rate=SAMPLE_RATE)\n",
    "        # extract text with metadata from pdf and create txt file for aligner\n",
    "        pdf_fp = get_raw_text_dir(raw_data_dir) + uuid + \".pdf\"\n",
    "        annotated_sections = parse_pdf(uuid, pdf_fp)\n",
    "        speaker_meta, fulltext = get_alignment_fulltext_plus_meta(annotated_sections)\n",
    "        txt_fp = temp_dir + uuid + \".txt\"\n",
    "        with open(txt_fp, \"w\") as f:\n",
    "            f.write(fulltext)\n",
    "        # do alignment\n",
    "        # if alignment was already done then skip ahead\n",
    "        align_fp = align_dir + uuid + \".json\"\n",
    "        if os.path.exists(align_fp):\n",
    "            with open(align_fp) as f:\n",
    "                align_dict = json.load(f)\n",
    "        else:\n",
    "            align_dict = get_gentle_alignment(wav_fp, txt_fp)\n",
    "            # save raw alignment file\n",
    "            with open(align_fp, \"w\") as f:\n",
    "                json.dump(align_dict, f)\n",
    "        align_meta = process_align_meta(align_dict[\"words\"])\n",
    "        # skip making slices with alignment doesn't seem promising\n",
    "        if np.mean([e[\"success\"] for e in align_meta]) < 0.5:\n",
    "            raise ValueError(\"alignment not good enough\")\n",
    "        # make slices\n",
    "        slice_objects = get_slices(\n",
    "            wav_fp, fulltext, align_meta, speaker_meta, annotated_sections\n",
    "        )\n",
    "        slices_meta = _write_slices(processed_data_dir, uuid, slice_objects)\n",
    "    return slices_meta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "15fe7d92",
   "metadata": {},
   "outputs": [],
   "source": [
    "# !rm -rf /mnt/data-ssd-1/data/supreme_corpus2/processed_data/*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "e1828f96",
   "metadata": {},
   "outputs": [],
   "source": [
    "raw_data_dir = \"/mnt/data-ssd-1/data/supreme_corpus2/raw_data/\"\n",
    "processed_data_dir = \"/mnt/data-ssd-1/data/supreme_corpus2/processed_data/\"\n",
    "if not os.path.exists(processed_data_dir):\n",
    "    os.makedirs(processed_data_dir)\n",
    "# get metadata\n",
    "meta_df = pd.read_json(raw_data_dir + \"scrape_meta.jsonl\", lines=True)\n",
    "# get already processed data\n",
    "processed_records_fp = processed_data_dir + \"processed_records.jsonl\"\n",
    "if os.path.exists(processed_records_fp):\n",
    "    processed_records = _load_jsonl(processed_records_fp)\n",
    "    known_uuids = set([e[\"uuid\"] for e in processed_records])\n",
    "else:\n",
    "    processed_records = []\n",
    "    known_uuids = set([])\n",
    "\n",
    "slice_meta_fp = processed_data_dir + \"slices_meta.jsonl\"\n",
    "if os.path.exists(slice_meta_fp):\n",
    "    processed_slices_meta = _load_jsonl(slice_meta_fp)\n",
    "else:\n",
    "    processed_slices_meta = []\n",
    "\n",
    "align_dir = processed_data_dir + \"alignments/\"\n",
    "if not os.path.exists(align_dir):\n",
    "    os.makedirs(align_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "9d51c9f6",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/792 [01:53<?, ?it/s]\n"
     ]
    }
   ],
   "source": [
    "for _, row in tqdm.tqdm(meta_df.iterrows(), total=meta_df.shape[0]):\n",
    "    uuid = row[\"uuid\"]\n",
    "    if uuid in known_uuids:\n",
    "        continue\n",
    "    try:\n",
    "        slices_meta = do_slicing(uuid, raw_data_dir, processed_data_dir, align_dir)\n",
    "    except ValueError:\n",
    "        slices_meta = []\n",
    "    processed_records.append({\"uuid\": uuid})\n",
    "    processed_slices_meta.extend(slices_meta)\n",
    "    break\n",
    "# _write_jsonl(processed_slices_meta, slice_meta_fp)\n",
    "# _write_jsonl(processed_records, processed_records_fp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "428c9a95",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e83e2da",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25e21e6f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce7ca468",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9637fdf",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eeff09b5",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "055e4ca1",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a596a99",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ed3c6d9a",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0093a7b",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "081e9dbc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1fc9245a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
