{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# External stabilization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<H2>Current shot </H2>\n",
    "<center>\n",
    "<iframe src='icon.html' width=1300 height=550 ></iframe>\n",
    "<center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<H2>Scan </H2>\n",
    "<center>\n",
    "<iframe src='scan.html' width=1300 height=550 ></iframe>\n",
    "<center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import requests\n",
    "from scipy import integrate, signal, interpolate\n",
    "import pandas as pd\n",
    "import holoviews as hv\n",
    "hv.extension('bokeh')\n",
    "import hvplot.pandas\n",
    "import os, glob\n",
    "ds = np.DataSource()\n",
    "\n",
    "def remove_old(name):\n",
    "    if os.path.exists(name):\n",
    "        os.remove(name)\n",
    "        \n",
    "names = ['analysis.html', 'scan.html', 'icon.html', 'icon-fig.png','icon-fig_Radial.png','icon-fig_Vertical.png','Camera_vs_Mirnov_Radial.png','Camera_vs_Mirnov__Vertical.png','InnerQuadr.csv']\n",
    "for name in names:\n",
    "    remove_old(name)\n",
    "    \n",
    "if os.path.exists('Results/'):\n",
    "    file=glob.glob('Results/*')\n",
    "    for f in file:\n",
    "        os.remove(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "shot_no = 46318\n",
    "def open_remote(shot_no, identifier, url_template):\n",
    "    return ds.open(url_template.format(shot_no=shot_no, identifier=identifier))\n",
    "\n",
    "def read_signal(shot_no, identifier, url, data_type='csv'): \n",
    "    file = open_remote(shot_no, identifier, url)\n",
    "    if data_type == 'lvm':\n",
    "        channels = channels = ['Time', 'mc1','mc5','mc9','mc13','Saddle','InnerQuadr', 'IexRad','IexVert','9', '10', '11', '12']\n",
    "        return pd.read_table(file, sep = '\\t', names=channels, index_col = 'Time')\n",
    "    else:\n",
    "        return pd.read_csv(file, names=['Time', identifier],\n",
    "                     index_col = 'Time', squeeze=True)\n",
    "def read_tab(shot_no,url):\n",
    "    tab=ds.open(url)\n",
    "    df=pd.read_csv(tab)\n",
    "    end=df['Time'].iat[-1]\n",
    "    start=df['Time'].iat[0]\n",
    "    df=df.set_index('Time')\n",
    "    return df, start, end\n",
    "    \n",
    "def remove_offset(data, window):\n",
    "    data-=data.loc[:window].mean()\n",
    "    return data\n",
    "\n",
    "def smooth(data,win=11): #41\n",
    "    smooth_data = signal.savgol_filter(data, win, 3)\n",
    "    return smooth_data\n",
    "\n",
    "url = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/LimiterMirnovCoils/Default/plasma_position.csv'\n",
    "kI=1/0.05 #Rogowski coil (Flux loop) constant\n",
    "\n",
    "\n",
    "try:\n",
    "    df,start,end=read_tab(shot_no,url)\n",
    "    mirnov=True\n",
    "except OSError:\n",
    "    mirnov=False\n",
    "    \n",
    "try:\n",
    "    \n",
    "    #!!! Prohozene I s U\n",
    "    url_I_vert  = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Infrastructure/PositionStabilization/Default/DAS_raw_data_dir_vertical/U%5evertical_fg.csv'\n",
    "    url_U_vert  = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Infrastructure/PositionStabilization/Default/DAS_raw_data_dir_vertical/U%5evertical_currclamp.csv'\n",
    "    \n",
    "    url_I_rad  = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Infrastructure/PositionStabilization/Default/DAS_raw_data_dir_radial/U%5eradial_fg.csv'\n",
    "    url_U_rad  = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Infrastructure/PositionStabilization/Default/DAS_raw_data_dir_radial/U%5eradial_currclamp.csv'\n",
    "    \n",
    "    U_exStabVert = read_signal(shot_no, 'U_fg_Vert', url_U_vert)\n",
    "    \n",
    "    url_dt=requests.get(f'http://golem.fjfi.cvut.cz/shotdir/{shot_no}/Devices/Oscilloscopes/RigolMSO5204-e/HorizontalStabilization/ScopeSetup/XINC')\n",
    "\n",
    "    dt=float(url_dt.text)*1e5\n",
    "    print('dt:',dt)\n",
    "    t_shift=-1\n",
    "    t_osc = pd.Series(np.linspace(0,dt*10, len(U_exStabVert))).rename('Time')+t_shift\n",
    "    \n",
    "    U_exStabVert = pd.Series(U_exStabVert.index[:], index = t_osc)\n",
    "    RogCoilVert = read_signal(shot_no, 'I_fg_Vert', url_I_vert)\n",
    "    I_exStabVert = pd.Series(smooth(RogCoilVert.index[:]*kI), index = t_osc) #the data was multiplied by the consntant [V->A]\n",
    "#     I_exStabVert = pd.Series(smooth(RogCoilVert.index[:]), index = t_osc) #the data is already recalculated\n",
    "         \n",
    "    U_exStabRad = read_signal(shot_no, 'U_fg_Rad', url_U_rad)\n",
    "    \n",
    "    U_exStabRad = pd.Series(U_exStabRad.index[:], index = t_osc)\n",
    "    RogCoilRad = read_signal(shot_no, 'I_fg_Rad', url_I_rad)\n",
    "    I_exStabRad = pd.Series(smooth(RogCoilRad.index[:]*kI), index = t_osc) #the data was multiplied by the consntant [V->A]\n",
    "#     I_exStabRad = pd.Series(smooth(RogCoilRad.index[:]), index = t_osc) #the data is already recalculated\n",
    "        \n",
    "    exStab=True\n",
    "    \n",
    "except OSError:\n",
    "    \n",
    "    exStab=False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Stabilization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if exStab:\n",
    "    names=['$I_{vert}$','$I_{rad}$','$U_{vert}$','$U_{rad}$','Vertical', 'Radial']\n",
    "    Stab=pd.DataFrame({'$I_{vert}$': I_exStabVert, '$I_{rad}$': I_exStabRad, \n",
    "                       '$U_{vert}$': U_exStabVert, '$U_{rad}$': U_exStabRad})\n",
    "    maxI=0\n",
    "    for stab in range(2):\n",
    "        I=names[stab]\n",
    "        U=names[stab+2]\n",
    "        \n",
    "        fig, ax = plt.subplots(dpi=100)\n",
    "        ax = Stab[I].plot(grid=True,label = I, c = 'r') #ocilloscope data\n",
    "        ax2 = ax.twinx()\n",
    "        ax2 = Stab[U].plot(label = '$U_{fg}$',c='tab:blue')\n",
    "        ax.set(ylim=(-max(abs(Stab[I]))-0.5, max(abs(Stab[I]))+0.5), ylabel='I [A]',\n",
    "               title=f'Shot No.{shot_no}, {names[stab+4]} Stabilization' )\n",
    "        ax2.set(ylim=(-max(abs(Stab[U]))-0.5, max(abs(Stab[U]))+0.5), ylabel='U [V]')\n",
    "        ax.legend(loc='upper left');ax2.legend(loc='upper right')\n",
    "        \n",
    "        if mirnov==True:\n",
    "            plt.savefig('icon-fig_'+names[stab+4]+'.png')\n",
    "        else:\n",
    "            plt.savefig('icon-fig.png')\n",
    "            \n",
    "        maxI_stab=max(abs(Stab[I]))\n",
    "        if maxI<maxI_stab:\n",
    "            maxI=maxI_stab\n",
    "        print('Maximum current:', round(maxI_stab,2), 'A')\n",
    "\n",
    "        with open('Results/MaxI_'+names[stab+4], 'w') as f:\n",
    "            f.write(str(round(maxI_stab,2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if exStab:\n",
    "    df_processed = pd.concat([Stab], axis='columns')\n",
    "    df_processed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "if exStab:\n",
    "    data_URL = f\"http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/LimiterMirnovCoils/Default/DAS_raw_data_dir/NIdata_6133.lvm\"\n",
    "    dataNI = read_signal(shot_no, ' ', data_URL, 'lvm').replace([np.inf, -np.inf, np.nan], value = 0)\n",
    "    dataNI.index*=1e3\n",
    "    Ivert=dataNI['IexVert']*kI\n",
    "    Irad=dataNI['IexRad']*kI\n",
    "    InnerQuadr=dataNI['InnerQuadr']\n",
    "    \n",
    "    InnerQuadr.to_csv('Results/InnerQuadr.csv')\n",
    "    df_processed = pd.concat([Ivert, Irad],axis='columns')\n",
    "    if mirnov:\n",
    "        df_processed = pd.concat([df, Ivert, Irad],axis='columns')\n",
    "    names=['IexVert','IexRad']\n",
    "df_processed.to_csv('Results/ExStab.csv')    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plasma position - Mirnov coils "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Position from Mirnov coils:', mirnov)\n",
    "if mirnov:\n",
    "    hline = hv.HLine(0)\n",
    "    hline.opts(color='k', line_dash='dashed', alpha = 0.4, line_width=1.0)\n",
    "    I_v=names[0]; I_r=names[1]\n",
    "    if exStab: \n",
    "        ax_param=dict(ylim=(-85, 85),xlim=(start, end),height=250,width=600, grid = True)\n",
    "        axIvert_param=dict(title = '',ylabel='I_exStab [A]', xlabel='Time [ms]', yaxis='left', height=250, width=600, \n",
    "             color='b', ylim=(-maxI-1.5, maxI+1.5),grid=True)\n",
    "\n",
    "        axIrad_param=dict(title = '',ylabel='I_exStab [A]', xlabel='Time [ms]', yaxis='left', height=250, width=600, \n",
    "             color='r', ylim=(-maxI-1.5,maxI+1.5),grid=True)\n",
    "\n",
    "        plot = df_processed['r'].hvplot(title = '', ylabel='r [mm]/ I[A]', xlabel='', **ax_param) *\\\n",
    "                    df_processed[I_v].hvplot(**axIvert_param) *\\\n",
    "                        df_processed[I_r].hvplot(**axIrad_param)+\\\n",
    "        df_processed['z'].hvplot(title = '',ylabel='z [mm] / I[A]',  xlabel='Time [ms]', **ax_param) *\\\n",
    "            df_processed[I_v].hvplot(**axIvert_param) *\\\n",
    "                df_processed[I_r].hvplot(**axIrad_param)+\\\n",
    "        df_processed['a'].hvplot(title = '',ylabel='a[mm]/ I[A]', xlabel='Time [ms]', **ax_param) *\\\n",
    "            df_processed[I_v].hvplot(**axIvert_param) *\\\n",
    "                    df_processed[I_r].hvplot(**axIrad_param)\n",
    "        plot=plot*hline\n",
    "        plot.cols(2)\n",
    "        \n",
    "    else:\n",
    "        layout = hv.Layout([df_processed[v].hvplot.line(\n",
    "            xlabel='', ylabel=l,ylim=(-85,85), xlim=(start,end),legend=False, title='', grid=True, group_label=v)\n",
    "                            for (v, l) in [('r', ' r [mm]'), ('z', 'z [mm]'), ('a', 'a [mm]')] ])*hline\n",
    "        plot=layout.cols(1).opts(hv.opts.Curve(width=600, height=200),  hv.opts.Curve('a', xlabel='time [ms]'))\n",
    "\n",
    "    hvplot.save(plot, 'icon.html')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### New iconfig"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if mirnov:\n",
    "    fig, axs = plt.subplots(3, 1, sharex=True, dpi=200)\n",
    "    df_processed['r'].plot(grid=True, ax=axs[0])\n",
    "    df_processed['z'].plot(grid=True, ax=axs[1])\n",
    "    df_processed['a'].plot(grid=True, ax=axs[2])\n",
    "    for i in range(3):\n",
    "        ax4=axs[i].twinx()\n",
    "        ax4=df_processed[names[0]].plot(grid=True, c='b',alpha=0.3,label=names[0])\n",
    "        ax4=df_processed[names[1]].plot(grid=True, c='r',alpha=0.3,label=names[1])\n",
    "        plt.legend()\n",
    "        ax4.set(xlim=(start,end), ylim=(-2*maxI,maxI*2),xlabel= 'Time [ms]', ylabel = 'I$_{exStab}$ [A]')\n",
    "        \n",
    "    axs[2].set(ylim=(-85,85), xlim=(start,end), xlabel= 'Time [ms]', ylabel = '$a$ [mm]')\n",
    "    axs[1].set(ylim=(-85,85), xlim=(start,end), xlabel= 'Time [ms]', ylabel = '$\\Delta$z [mm]')\n",
    "    axs[0].set(ylim=(-85,85), xlim=(start,end), xlabel= 'Time [ms]', ylabel = '$\\Delta$r [mm]', \n",
    "               title = 'Horizontal, vertical plasma position and radius #{}'.format(shot_no))\n",
    "    plt.savefig('icon-fig')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Scan over the session"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    ScanDef_url = requests.get(\"http://golem.fjfi.cvut.cz/shots/%i/Production/Parameters/ScanDefinition\" % shot_no)\n",
    "    ScanDef = ScanDef_url.text\n",
    "    ScanDef=ScanDef.split(\" \")\n",
    "    for i in range(len(ScanDef)):\n",
    "        ScanDef[i]=int(ScanDef[i])\n",
    "    Scan=True\n",
    "except ValueError:\n",
    "    Scan=False\n",
    "    ScanDef=[]\n",
    "    print('Scan not defined')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#TODO - osa x, zredukovat pocet smycek\n",
    "if Scan and mirnov:\n",
    "    for shot in ScanDef:\n",
    "        url=f'http://golem.fjfi.cvut.cz/shots/{shot}/Diagnostics/LimiterMirnovCoils/plasma_position.csv'\n",
    "        df2,start,end=read_tab(shot_no, url)\n",
    "        param = ['r','z', 'a']\n",
    "        for j in param:\n",
    "            df_processed = df_processed.join(df2[j],rsuffix=f'_{shot}')\n",
    "            \n",
    "        url=f'http://golem.fjfi.cvut.cz/shots/{shot}/Operation/Discharge/Position_Stabilization/Results/ExStab.csv'\n",
    "        tab=ds.open(url)\n",
    "        Stab2=pd.read_csv(tab).set_index('Time')  \n",
    "        \n",
    "        for name in range(2):\n",
    "            df_processed = df_processed.join(Stab2[names[name]], rsuffix=f'_{shot}')\n",
    "     \n",
    "    ax_param=dict(xlim=(start,end),ylim=(-85, 85),height=250,width=600, grid = True)\n",
    "    axStab=dict(xlim=(start,end),ylim=(-110,110),height=250,width=600, grid = True)\n",
    "    hline = hv.HLine(0)\n",
    "    hline.opts(color='k', line_dash='dashed', alpha = 0.4, line_width=1.0)\n",
    "    \n",
    "    display(df_processed)\n",
    "    plotScan = df_processed['r'].hvplot(title = '', ylabel='r [mm]', xlabel='', **ax_param) +\\\n",
    "                df_processed['z'].hvplot(title = '',ylabel='z [mm]',  xlabel='', **ax_param) +\\\n",
    "                df_processed['a'].hvplot(title = '',ylabel='a[mm]', xlabel='Time [ms]', **ax_param) +\\\n",
    "                df_processed[names[1]].hvplot(title = '',ylabel='I_rad[A]', xlabel='Time [ms]',**axStab)+\\\n",
    "                df_processed[names[0]].hvplot(title = '',ylabel='I_vert[A]', xlabel='Time [ms]',**axStab) \n",
    "                \n",
    "    for shot in ScanDef:\n",
    "        plotScan=plotScan[0]*df_processed['r_%i'%shot].hvplot(title = '', ylabel='r [mm]', xlabel='', **ax_param)+\\\n",
    "            plotScan[1]*df_processed['z_%i'%shot].hvplot(title = '', ylabel='z [mm]', xlabel='', **ax_param)+\\\n",
    "            plotScan[2]*df_processed['a_%i'%shot].hvplot(title = '', ylabel='a [mm]', xlabel='Time', **ax_param)+\\\n",
    "            plotScan[3]*df_processed[names[1]+'_%i'%shot].hvplot(title = '',ylabel='I_rad[A]', xlabel='Time [ms]',**axStab) +\\\n",
    "            plotScan[4]*df_processed[names[0]+'_%i'%shot].hvplot(title = '',ylabel='I_vert[A]', xlabel='Time [ms]',**axStab)\n",
    "                    \n",
    "        plotScan=plotScan*hline\n",
    "        plotScan.cols(3)\n",
    "        hvplot.save(plotScan, 'scan.html')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def scan():\n",
    "    tdur=[]; StabVertParam=[]; StabRadParam=[]; MaxIvertStab=[]; MaxIradStab=[]\n",
    "    ScanDef.append(shot_no)\n",
    "    for shot in ScanDef:\n",
    "        url_tdur=requests.get('http://golem.fjfi.cvut.cz/shots/%i/Diagnostics/BasicDiagnostics/Results/t_plasma_duration'%shot)\n",
    "        tdur.append(float(url_tdur.text))\n",
    "\n",
    "        url_StabVert=requests.get('http://golem.fjfi.cvut.cz/shots/%i/Operation/Discharge/PositionStabilization/Parameters/vertical_waveform'%shot)\n",
    "        StabVertParam.append(url_StabVert.text) \n",
    "        url_StabRad=requests.get('http://golem.fjfi.cvut.cz/shots/%i/Operation/Discharge/PositionStabilization/Parameters/radial_waveform'%shot)\n",
    "        StabRadParam.append(url_StabRad.text) \n",
    "        \n",
    "        url_maxIvert=requests.get('http://golem.fjfi.cvut.cz/shots/%i/Operation/Discharge/PositionStabilization/Results/MaxI_Vertical'%shot)\n",
    "        MaxIvertStab.append(url_maxIvert.text)\n",
    "        url_maxIrad=requests.get('http://golem.fjfi.cvut.cz/shots/%i/Operation/Discharge/PositionStabilization/Results/MaxI_Radial'%shot)\n",
    "        MaxIradStab.append(url_maxIrad.text)\n",
    "    data={'Plasma duration': tdur, 'Vertical Stabilization parameters': StabVertParam, \n",
    "            'Radial Stabilization parameters': StabRadParam, 'Max current in VertStab': MaxIvertStab,\n",
    "              'Max current in RadStab': MaxIradStab, 'Shot_no': ScanDef}\n",
    "    df_results=pd.DataFrame(data)\n",
    "    df_results=df_results.set_index('Shot_no')\n",
    "    df_results.to_csv('Results/scanResults.csv')\n",
    "    return df_results\n",
    "\n",
    "if mirnov and Scan:\n",
    "    display(scan())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cameras vs Mirnov Coils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def comparison_image(shot_no, Position):\n",
    "    \n",
    "    url=f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/LimiterMirnovCoils/plasma_position.csv'\n",
    "    tab=ds.open(url)\n",
    "    df=pd.read_csv(tab)\n",
    "    end=df['Time'].iat[-1]\n",
    "    start=df['Time'].iat[0]\n",
    "    df=df.set_index('Time')\n",
    "    \n",
    "    url_VertCam = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/FastCameras/Camera_Vertical/CameraVerticalPosition'\n",
    "    url_RadCam = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/FastCameras/Camera_Radial/CameraRadialPosition'\n",
    "    \n",
    "    if Position=='Radial':\n",
    "        symb='r'\n",
    "        camera_position=read_signal(shot_no, symb, url_RadCam, data_type='csv')\n",
    "    if Position=='Vertical': \n",
    "        symb='z'\n",
    "        camera_position=read_signal(shot_no, symb, url_VertCam, data_type='csv')\n",
    "\n",
    "    fig = plt.figure(figsize=(12,4),dpi=70)\n",
    "    \n",
    "    FONT = 'DejaVu Sans'\n",
    "\n",
    "    ax = df[symb].plot(label='Mirnov coils')\n",
    "    ax = camera_position.plot(label = 'Fast Camera')\n",
    "\n",
    "    ax.set_ylabel('$\\Delta$'+symb+' [mm]',fontname = FONT, fontsize = 12)\n",
    "    ax.set_xlabel('Time [ms]',fontname = FONT, fontsize = 12)\n",
    "    ax.set_ylim(-85,85) \n",
    "\n",
    "    loclegend='best'\n",
    "    leg = plt.legend(loc = loclegend, shadow = True, fancybox=False) #with marker\n",
    "\n",
    "    leg.get_frame().set_linewidth(1)\n",
    "    leg.get_frame().set_edgecolor('k')\n",
    "    for text in leg.get_texts():\n",
    "          plt.setp(text, fontname=FONT, fontsize = 12)\n",
    "    for line, text in zip(leg.get_lines(), leg.get_texts()):\n",
    "          text.set_color(line.get_color())\n",
    "\n",
    "    ax.grid(which = 'major', c = 'gray', linewidth = 0.5, linestyle = 'solid') \n",
    "    ax.grid(which = 'minor', c = 'gray', linewidth = 0.3, linestyle = 'dashed') \n",
    "\n",
    "    ax.axhline(y=0, color='k', ls='--', lw=1, alpha=0.4)\n",
    "\n",
    "    fig.savefig(f'Camera_vs_Mirnov_{Position}')\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for position in ['Radial','Vertical']:\n",
    "    try:\n",
    "        comparison_image(shot_no, position)\n",
    "    except:\n",
    "        print(f'{position} camera data is missing')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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": 4
}
