{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%matplotlib notebook\n",
    "%matplotlib inline\n",
    "from ipywidgets import interact, IntSlider"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# set up environment\n",
    "dim = 2\n",
    "grid_size = {'x': 20, 'y': 20} # to start out\n",
    "domain_size = {'x': 20, 'y': 20} # to start out\n",
    "\n",
    "# boundary_condition\n",
    "# constant boundary condition\n",
    "bc_left = 0\n",
    "bc_right = 0\n",
    "bc_top = 0\n",
    "bc_bottom = 0\n",
    "\n",
    "delta_x = domain_size['x'] / grid_size['x']\n",
    "delta_y = domain_size['y'] / grid_size['y'] \n",
    "\n",
    "alpha_x = np.ones((grid_size['x'],grid_size['y']))\n",
    "alpha_y = np.ones((grid_size['x'],grid_size['y']))\n",
    "\n",
    "# time dimension\n",
    "max_stable_time_step_x = delta_x**2 / (2 * np.max(alpha_x))\n",
    "max_stable_time_step_y = delta_y**2 / (2 * np.max(alpha_y))\n",
    "max_stable_time_step = max_stable_time_step_x if max_stable_time_step_x <= max_stable_time_step_y else max_stable_time_step_y\n",
    "\n",
    "time_step = 0.1\n",
    "if time_step > max_stable_time_step:\n",
    "    print('Warning: Time step more than maximum stable time step!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 12488 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# initialize environment with start values\n",
    "C_t = np.zeros((grid_size['x'],grid_size['y']))\n",
    "C_t[0,0] = 2000\n",
    "# C_t[19,19] = 2000\n",
    "\n",
    "\n",
    "plt.imshow(C_t, vmin=0, vmax=2000)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# calculate mean between two cells\n",
    "def alpha_interblock(alpha1, alpha2, harmonic=True):\n",
    "    if not harmonic:\n",
    "        return 0.5 * (alpha1 + alpha2)\n",
    "    else:\n",
    "        return 2 / ((1/alpha1) + (1/alpha2))\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementation for constant boundary conditions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# simulate one time step\n",
    "def simulate_constant_boundary(C_t): \n",
    "        C_t1 = np.zeros((grid_size['x'],grid_size['y']))\n",
    "\n",
    "        # inner cells\n",
    "        for i in range(1, grid_size['y']-1): #rows\n",
    "                for j in range(1, grid_size['x']-1): #columns\n",
    "                        C_t1[i,j] = C_t[i,j] \\\n",
    "                                + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j]) \\\n",
    "                                + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "                        \n",
    "        # boundary conditions\n",
    "        # left without corners / looping over rows\n",
    "        for i in range(1, grid_size['y']-1):\n",
    "               j = 0\n",
    "               C_t1[i,0] = C_t[i,0] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1] \n",
    "                                - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + 2 * alpha_x[i,j]) * C_t[i,j]\n",
    "                                + 2 * alpha_x[i,j] * bc_left) \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j])        \n",
    "\n",
    "        # right without corners / looping over rows\n",
    "        n = grid_size['x']-1 # maximum index in x-direction (columns)\n",
    "        for i in range(1, grid_size['y']-1):\n",
    "               j = n\n",
    "               C_t1[i,n] = C_t[i,n] \\\n",
    "                        + time_step/delta_x**2 * (2 * alpha_x[i,n] * bc_right \n",
    "                                - (alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) + 2 * alpha_x[i,n]) * C_t[i,n]\n",
    "                                + alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * C_t[i,n-1]) \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j])\n",
    "\n",
    "        # top without corners / looping over columns\n",
    "        for j in range(1, grid_size['x']-1):\n",
    "               i = 0\n",
    "               C_t1[0,j] = C_t[0,j] \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * C_t[1,j] \n",
    "                                - (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) + 2 * alpha_y[0,j]) * C_t[0,j]\n",
    "                                + 2 * alpha_y[0,j] * bc_top) \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "\n",
    "        # bottom without corners / looping over columns\n",
    "        m = grid_size['y']-1 # maximum index in y-direction (rows)\n",
    "        for j in range(1, grid_size['x']-1):\n",
    "               i = m\n",
    "               C_t1[m,j] = C_t[m,j] \\\n",
    "                        + time_step/delta_y**2 * (2 * alpha_y[m,j] * bc_bottom \n",
    "                                - (alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) + 2 * alpha_y[m,j]) * C_t[m,j]\n",
    "                                + alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) * C_t[m-1,j]) \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "\n",
    "        # corner top left\n",
    "        i = 0\n",
    "        j = i\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1] \n",
    "                                - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + 2 * alpha_x[i,j]) * C_t[i,j]\n",
    "                                + 2 * alpha_x[i,j] * bc_left) \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * C_t[1,j] \n",
    "                                - (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) + 2 * alpha_y[0,j]) * C_t[0,j]\n",
    "                                + 2 * alpha_y[0,j] * bc_top)\n",
    "                        \n",
    "\n",
    "        # corner top right\n",
    "        i = 0\n",
    "        j = grid_size['x']-1\n",
    "        n = j\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (2 * alpha_x[i,n] * bc_right \n",
    "                                - (alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) + 2 * alpha_x[i,n]) * C_t[i,n]\n",
    "                                + alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * C_t[i,n-1]) \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * C_t[1,j] \n",
    "                                - (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) + 2 * alpha_y[0,j]) * C_t[0,j]\n",
    "                                + 2 * alpha_y[0,j] * bc_top)\n",
    "\n",
    "        # corner bottom left\n",
    "        i = grid_size['y']-1\n",
    "        m = i\n",
    "        j = 0\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1] \n",
    "                                - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + 2 * alpha_x[i,j]) * C_t[i,j]\n",
    "                                + 2 * alpha_x[i,j] * bc_left) \\\n",
    "                        + time_step/delta_y**2 * (2 * alpha_y[m,j] * bc_bottom \n",
    "                                - (alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) + 2 * alpha_y[m,j]) * C_t[m,j]\n",
    "                                + alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) * C_t[m-1,j])\n",
    "\n",
    "        # corner bottom right\n",
    "        i = grid_size['y']-1\n",
    "        j = grid_size['x']-1\n",
    "        m = i \n",
    "        n = j\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (2 * alpha_x[i,n] * bc_right \n",
    "                                - (alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) + 2 * alpha_x[i,n]) * C_t[i,n]\n",
    "                                + alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * C_t[i,n-1]) \\\n",
    "                        + time_step/delta_y**2 * (2 * alpha_y[m,j] * bc_bottom \n",
    "                                - (alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) + 2 * alpha_y[m,j]) * C_t[m,j]\n",
    "                                + alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) * C_t[m-1,j])\n",
    "\n",
    "        return C_t1"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementation for closed boundary conditions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# simulate one time step\n",
    "def simulate_closed_boundary(C_t): \n",
    "        C_t1 = np.zeros((grid_size['x'],grid_size['y']))\n",
    "\n",
    "        # inner cells\n",
    "        for i in range(1, grid_size['y']-1): #rows\n",
    "                for j in range(1, grid_size['x']-1): #columns\n",
    "                        C_t1[i,j] = C_t[i,j] \\\n",
    "                                + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j]) \\\n",
    "                                + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "                        \n",
    "         # boundary conditions\n",
    "        # left without corners / looping over rows\n",
    "        for i in range(1, grid_size['y']-1):\n",
    "               j = 0\n",
    "               C_t1[i,0] = C_t[i,0] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * (C_t[i,j+1] - C_t[i,j]))\\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j])        \n",
    "\n",
    "        # right without corners / looping over rows\n",
    "        n = grid_size['x']-1 # maximum index in x-direction (columns)\n",
    "        for i in range(1, grid_size['y']-1):\n",
    "               j = n\n",
    "               C_t1[i,n] = C_t[i,n] \\\n",
    "                        + time_step/delta_x**2 * ( \n",
    "                                - (alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * (C_t[i,n] - C_t[i,n-1])))\\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) * C_t[i+1,j]\n",
    "                                        - (alpha_interblock(alpha_y[i+1,j], alpha_y[i,j]) + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_y[i-1,j], alpha_y[i,j]) * C_t[i-1,j])\n",
    "\n",
    "        # top without corners / looping over columns\n",
    "        for j in range(1, grid_size['x']-1):\n",
    "               i = 0\n",
    "               C_t1[0,j] = C_t[0,j] \\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * (C_t[1,j] - C_t[0,j])) \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "\n",
    "        # bottom without corners / looping over columns\n",
    "        m = grid_size['y']-1 # maximum index in y-direction (rows)\n",
    "        for j in range(1, grid_size['x']-1):\n",
    "               i = m\n",
    "               C_t1[m,j] = C_t[m,j] \\\n",
    "                        + time_step/delta_y**2 * ( \n",
    "                                - (alpha_interblock(alpha_y[m,j], alpha_y[m-1,j]) * (C_t[m,j] - C_t[m-1,j]))) \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * C_t[i,j+1]\n",
    "                                        - (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j])) * C_t[i,j] \n",
    "                                        + alpha_interblock(alpha_x[i,j-1], alpha_x[i,j]) * C_t[i,j-1])\n",
    "\n",
    "        # corner top left\n",
    "        i = 0\n",
    "        j = i\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * (C_t[i,j+1] - C_t[i,j]))\\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * (C_t[1,j]-C_t[0,j]))\n",
    "                        \n",
    "\n",
    "        # corner top right\n",
    "        i = 0\n",
    "        j = grid_size['x']-1\n",
    "        n = j\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (- (alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * (C_t[i,n] - C_t[i,n-1])))\\\n",
    "                        + time_step/delta_y**2 * (alpha_interblock(alpha_y[1,j], alpha_y[0,j]) * (C_t[1,j] - C_t[0,j])) \n",
    "        \n",
    "\n",
    "        # corner bottom left\n",
    "        i = grid_size['y']-1\n",
    "        m = i\n",
    "        j = 0\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (alpha_interblock(alpha_x[i,j+1], alpha_x[i,j]) * (C_t[i,j+1] - C_t[i,j])) \\\n",
    "                        + time_step/delta_y**2 * (- (alpha_interblock(alpha_y[m,j], alpha_y[m-1,j] * (C_t[m,j] - C_t[m-1,j]))))\n",
    "\n",
    "        # corner bottom right\n",
    "        i = grid_size['y']-1\n",
    "        j = grid_size['x']-1\n",
    "        m = i \n",
    "        n = j\n",
    "        C_t1[i,j] = C_t[i,j] \\\n",
    "                        + time_step/delta_x**2 * (- alpha_interblock(alpha_x[i,n-1], alpha_x[i,n]) * (C_t[i,n] - C_t[i,n-1]))\\\n",
    "                        + time_step/delta_y**2 * (- alpha_interblock(alpha_y[m,j], alpha_y[m-1,j] * (C_t[m,j] - C_t[m-1,j])))\n",
    "\n",
    "        return C_t1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/8m/wj5j2vnd3c776jn9kyzj818r0000gn/T/ipykernel_58661/2955654588.py:6: RuntimeWarning: divide by zero encountered in scalar divide\n",
      "  return 2 / ((1/alpha1) + (1/alpha2))\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 23224 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "records = []\n",
    "\n",
    "for i in range(100):\n",
    "    C_t = simulate_closed_boundary(C_t)\n",
    "    records.append(C_t)\n",
    "\n",
    "# with np.printoptions(precision=2,floatmode='fixed',linewidth=200):\n",
    "#     print(C_t)\n",
    "\n",
    "plt.imshow(C_t, vmin=0, vmax=2000)\n",
    "plt.colorbar()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 19304 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.imshow(C_t)\n",
    "plt.colorbar()\n",
    "plt.show()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Widget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "77835379be7343d0aff52d275a86be97",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=0, description='w', max=999), Output()), _dom_classes=('widget-interact'…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.update(w=1)>"
      ]
     },
     "execution_count": 127,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def update(w = 1):\n",
    "    fig = plt.figure(figsize = (10,7))\n",
    "    y = records[w]\n",
    "    plt.imshow(y, vmin=0, vmax=50)\n",
    "    \n",
    "interact(update, w = IntSlider(min=0, max = 999, step = 1, value = 0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 6024 characters]",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.animation as animation\n",
    "\n",
    "\n",
    "frames = []\n",
    "fig = plt.figure()\n",
    "for i in records:\n",
    "    frames.append([plt.imshow(i, vmin=0, vmax=10, animated=True)])\n",
    "\n",
    "ani = animation.ArtistAnimation(fig, frames, interval=50, blit=True, repeat_delay=1000)\n",
    "\n",
    "ani.save(\"/Users/hannessigner/Desktop/test.gif\")"
   ]
  }
 ],
 "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.11.4"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
