{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tests of velocities of haloes in CSiBORG"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import csiborgtools\n",
    "\n",
    "from scipy.stats import spearmanr, binned_statistic\n",
    "\n",
    "%matplotlib inline\n",
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## FoF vs SPH velocity"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kind = \"main\"\n",
    "nsnap = 99\n",
    "nsim = 17417\n",
    "\n",
    "field_reader = csiborgtools.read.CSiBORG2Field(nsim, kind)\n",
    "catalogue = csiborgtools.read.CSiBORG2Catalogue(nsim, nsnap, kind)\n",
    "boxsize = csiborgtools.simname2boxsize(\"csiborg2_main\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "velocity_field = field_reader.velocity_field(\"SPH\", 1024)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pos = catalogue[\"cartesian_pos\"] / boxsize\n",
    "vel = catalogue[\"cartesian_vel\"]\n",
    "mass = catalogue[\"totmass\"]\n",
    "\n",
    "spherical_pos = catalogue[\"spherical_pos\"]\n",
    "RA = np.deg2rad(spherical_pos[:, 1])\n",
    "dec = np.deg2rad(spherical_pos[:, 2])\n",
    "\n",
    "def project_radial(vx, vy, vz, RA, dec):\n",
    "    return vx * np.cos(dec) * np.cos(RA) + vy * np.cos(dec) * np.sin(RA) + vz * np.sin(dec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vx, vy, vz = csiborgtools.field.evaluate_cartesian_cic(velocity_field[0], velocity_field[1], velocity_field[2], pos=pos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axs = plt.subplots(1, 4, figsize=(15, 4))\n",
    "fig.suptitle(\"Comparison of FoF to SPH velocities at the same position\")\n",
    "\n",
    "axs[0].hexbin(vel[:, 0], vx, gridsize=50, bins=\"log\", mincnt=1)\n",
    "axs[0].set_xlabel(r\"FoF $v_x ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[0].set_ylabel(r\"SPH $v_x ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "axs[1].hexbin(vel[:, 1], vy, gridsize=50, bins=\"log\", mincnt=1)\n",
    "axs[1].set_xlabel(r\"FoF $v_y ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[1].set_ylabel(r\"SPH $v_y ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "axs[2].hexbin(vel[:, 2], vz, gridsize=50, bins=\"log\", mincnt=1)\n",
    "axs[2].set_xlabel(r\"FoF $v_z ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[2].set_ylabel(r\"SPH $v_z ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "\n",
    "vr_fof = project_radial(vel[:, 0], vel[:, 1], vel[:, 2], RA, dec)\n",
    "vr_sph = project_radial(vx, vy, vz, RA, dec)\n",
    "axs[3].hexbin(vr_fof, vr_sph, gridsize=50, bins=\"log\", mincnt=1)\n",
    "axs[3].set_xlabel(r\"FoF $v_r ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[3].set_ylabel(r\"SPH $v_r ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "for i in range(4):\n",
    "    axs[i].axline([0, 0], [1, 1], color=\"red\", ls=\"--\")\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(\"../plots/fof_to_sph_velocity_comparison.png\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Correlation of the peculiar velocity and total mass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kind = \"main\"\n",
    "nsnap = 99\n",
    "nsim = 17417"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### FoF haloes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "catalogue = csiborgtools.read.CSiBORG2Catalogue(nsim, nsnap, kind)\n",
    "\n",
    "vel = catalogue[\"cartesian_vel\"]\n",
    "mass = catalogue[\"totmass\"]\n",
    "velmag = np.linalg.norm(vel, axis=1)\n",
    "\n",
    "spearmanr(mass, velmag)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure()\n",
    "plt.title(\"FoF velocity as a function of mass\")\n",
    "plt.hexbin(np.log10(mass), velmag, mincnt=1, bins=\"log\")\n",
    "\n",
    "\n",
    "plt.xlim(np.log10(np.min(mass)))\n",
    "plt.ylim(0)\n",
    "plt.xlabel(r\"$\\log M_{\\rm FoF} ~ [M_\\odot / h]$\")\n",
    "plt.ylabel(r\"$V_{\\rm pec} ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "plt.savefig(\"../plots/fof_velocity_to_mass.png\", dpi=300, bbox_inches=\"tight\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Subfind subhaloes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "catalogue = csiborgtools.read.CSiBORG2SUBFINDCatalogue(nsim, nsnap, kind)\n",
    "\n",
    "mass = catalogue[\"totmass\"]\n",
    "vec = np.linalg.norm(catalogue[\"cartesian_vel\"], axis=1)\n",
    "is_central = catalogue[\"Central\"]\n",
    "\n",
    "def get_binned_trend(x, y, nbins=20):\n",
    "    median, bin_edges, __ = binned_statistic(x, y, bins=nbins, statistic=\"median\")\n",
    "    lower, __, __ = binned_statistic(x, y, bins=nbins, statistic=lambda x: np.percentile(x, 16))\n",
    "    upper, __, __ = binned_statistic(x, y, bins=nbins, statistic=lambda x: np.percentile(x, 84))\n",
    "    std, __, __ = binned_statistic(x, y, bins=nbins, statistic=\"std\")\n",
    "    xrange = (bin_edges[1:] + bin_edges[:-1]) / 2\n",
    "    std = (upper - lower) / 2\n",
    "    return xrange, median, lower, upper, std"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nbins = 15\n",
    "\n",
    "fig, axs = plt.subplots(1, 2, figsize=(10, 4))\n",
    "\n",
    "# Centrals\n",
    "mask = is_central & (mass > 1e12)\n",
    "x, y = np.log10(mass[mask]), vec[mask]\n",
    "xrange, median, lower, upper, std = get_binned_trend(x, y, nbins=nbins)\n",
    "sigma_v, e_sigma_v = np.mean(std), np.std(std)\n",
    "axs[0].set_title(r\"Centrals, $\\sigma_v = {:.1f} \\pm {:.1f} ~ \\mathrm{{km}} / \\mathrm{{s}}$\".format(sigma_v, e_sigma_v))\n",
    "axs[0].hexbin(x, y, mincnt=1, bins=\"log\")\n",
    "\n",
    "axs[0].plot(xrange, median, color=\"red\")\n",
    "axs[0].fill_between(xrange, lower, upper, color=\"red\", alpha=0.3)\n",
    "\n",
    "# Satellites\n",
    "mask = ~is_central & (mass > 1e12)\n",
    "x, y = np.log10(mass[mask]), vec[mask]\n",
    "xrange, median, lower, upper, std = get_binned_trend(x, y, nbins=nbins)\n",
    "sigma_v, e_sigma_v = np.mean(std), np.std(std)\n",
    "axs[1].set_title(r\"Satellites, $\\sigma_v = {:.1f} \\pm {:.1f} ~ \\mathrm{{km}} / \\mathrm{{s}}$\".format(sigma_v, e_sigma_v))\n",
    "axs[1].hexbin(x, y, mincnt=1, bins=\"log\")\n",
    "axs[1].plot(xrange, median, color=\"red\")\n",
    "axs[1].fill_between(xrange, lower, upper, color=\"red\", alpha=0.3)\n",
    "\n",
    "for i in range(2):\n",
    "    axs[i].set_xlim(np.log10(1e12))\n",
    "    axs[i].set_ylim(0)\n",
    "    axs[i].set_xlabel(r\"$\\log M_{\\rm tot} ~ [M_\\odot / h]$\")\n",
    "    axs[i].set_ylabel(r\"$V_{\\rm pec} ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(\"../plots/velocity_to_mass_subhaloes.png\", dpi=300, bbox_inches=\"tight\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### SUBFIND haloes but subtracting the velocity of the velocity field"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "field_reader = csiborgtools.read.CSiBORG2Field(nsim, kind)\n",
    "catalogue = csiborgtools.read.CSiBORG2SUBFINDCatalogue(nsim, nsnap, kind)\n",
    "boxsize = csiborgtools.simname2boxsize(\"csiborg2_main\")\n",
    "\n",
    "mass = catalogue[\"totmass\"]\n",
    "pos = catalogue[\"cartesian_pos\"] / boxsize\n",
    "subfind_vel = catalogue[\"cartesian_vel\"]\n",
    "is_central = catalogue[\"Central\"]\n",
    "\n",
    "parent_mass = catalogue[\"ParentMass\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "velocity_field = field_reader.velocity_field(\"SPH\", 1024)\n",
    "vx, vy, vz = csiborgtools.field.evaluate_cartesian_cic(velocity_field[0], velocity_field[1], velocity_field[2], pos=pos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vx, vy, vz = csiborgtools.field.evaluate_cartesian_cic(velocity_field[0], velocity_field[1], velocity_field[2], pos=pos)\n",
    "\n",
    "residual_vel = np.copy(subfind_vel)\n",
    "residual_vel[:, 0] -= vx\n",
    "residual_vel[:, 1] -= vy\n",
    "residual_vel[:, 2] -= vz\n",
    "\n",
    "residual_vel_mag = np.linalg.norm(residual_vel, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nbins = 15\n",
    "\n",
    "fig, axs = plt.subplots(1, 2, figsize=(10, 4))\n",
    "fig.suptitle(\"Residual velocity not accounted for by the SPH field.\")\n",
    "# Centrals\n",
    "mask = is_central & (mass > 1e12)\n",
    "x, y = np.log10(mass[mask]), residual_vel_mag[mask]\n",
    "xrange, median, lower, upper, std = get_binned_trend(x, y, nbins=nbins)\n",
    "sigma_v, e_sigma_v = np.mean(std), np.std(std)\n",
    "axs[0].set_title(r\"Centrals, $\\sigma_v = {:.1f} \\pm {:.1f} ~ \\mathrm{{km}} / \\mathrm{{s}}$\".format(sigma_v, e_sigma_v))\n",
    "axs[0].hexbin(x, y, mincnt=1, bins=\"log\")\n",
    "\n",
    "axs[0].plot(xrange, median, color=\"red\")\n",
    "axs[0].fill_between(xrange, lower, upper, color=\"red\", alpha=0.3)\n",
    "\n",
    "# Satellites\n",
    "mask = ~is_central & (mass > 1e12)\n",
    "x, y = np.log10(mass[mask]), residual_vel_mag[mask]\n",
    "xrange, median, lower, upper, std = get_binned_trend(x, y, nbins=nbins)\n",
    "sigma_v, e_sigma_v = np.mean(std), np.std(std)\n",
    "axs[1].set_title(r\"Satellites, $\\sigma_v = {:.1f} \\pm {:.1f} ~ \\mathrm{{km}} / \\mathrm{{s}}$\".format(sigma_v, e_sigma_v))\n",
    "axs[1].hexbin(x, y, mincnt=1, bins=\"log\")\n",
    "axs[1].plot(xrange, median, color=\"red\")\n",
    "axs[1].fill_between(xrange, lower, upper, color=\"red\", alpha=0.3)\n",
    "\n",
    "for i in range(2):\n",
    "    axs[i].set_xlim(np.log10(1e12))\n",
    "    axs[i].set_ylim(0)\n",
    "    axs[i].set_xlabel(r\"$\\log M_{\\rm tot} ~ [M_\\odot / h]$\")\n",
    "    axs[i].set_ylabel(r\"$\\Delta V_{\\rm pec} ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(\"../plots/residual_velocity_to_mass_subhaloes.png\", dpi=300, bbox_inches=\"tight\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nbins = 15\n",
    "\n",
    "fig, axs = plt.subplots(1, 1)\n",
    "axs = [axs]\n",
    "fig.suptitle(\"Residual velocity not accounted for by the SPH field as a function of parent mass\")\n",
    "\n",
    "# Satellites\n",
    "mask = ~is_central & (parent_mass > 1e12)\n",
    "x, y = np.log10(parent_mass[mask]), residual_vel_mag[mask]\n",
    "xrange, median, lower, upper, std = get_binned_trend(x, y, nbins=nbins)\n",
    "sigma_v, e_sigma_v = np.mean(std), np.std(std)\n",
    "axs[0].set_title(r\"Satellites, $\\sigma_v = {:.1f} \\pm {:.1f} ~ \\mathrm{{km}} / \\mathrm{{s}}$\".format(sigma_v, e_sigma_v))\n",
    "axs[0].hexbin(x, y, mincnt=1, bins=\"log\")\n",
    "axs[0].plot(xrange, median, color=\"red\")\n",
    "axs[0].fill_between(xrange, lower, upper, color=\"red\", alpha=0.3)\n",
    "\n",
    "axs[0].set_xlim(np.log10(1e12))\n",
    "axs[0].set_ylim(0)\n",
    "axs[0].set_xlabel(r\"$\\log M_{\\rm parent} ~ [M_\\odot / h]$\")\n",
    "axs[0].set_ylabel(r\"$\\Delta V_{\\rm pec} ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig(\"../plots/residual_velocity_to_mass_subhaloes_parent.png\", dpi=300, bbox_inches=\"tight\")\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Calibration of $\\sigma_{\\rm sat}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "kind = \"main\"\n",
    "nsnap = 99\n",
    "nsim = 17417\n",
    "boxsize = csiborgtools.simname2boxsize(\"csiborg2_main\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "field_reader = csiborgtools.read.CSiBORG2Field(nsim, kind)\n",
    "catalogue = csiborgtools.read.CSiBORG2SUBFINDCatalogue(nsim, nsnap, kind)\n",
    "\n",
    "mass = catalogue[\"totmass\"]\n",
    "parent_mass = catalogue[\"ParentMass\"]\n",
    "pos = catalogue[\"cartesian_pos\"] / boxsize\n",
    "subfind_vel = catalogue[\"cartesian_vel\"]\n",
    "is_central = catalogue[\"Central\"]\n",
    "\n",
    "velocity_field = field_reader.velocity_field(\"SPH\", 1024)\n",
    "vx, vy, vz = csiborgtools.field.evaluate_cartesian_cic(velocity_field[0], velocity_field[1], velocity_field[2], pos=pos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "RA = np.deg2rad(catalogue[\"spherical_pos\"][:, 1])\n",
    "dec = np.deg2rad(catalogue[\"spherical_pos\"][:, 2])\n",
    "\n",
    "\n",
    "dvx = subfind_vel[:, 0] - vx\n",
    "dvy = subfind_vel[:, 1] - vy\n",
    "dvz = subfind_vel[:, 2] - vz\n",
    "\n",
    "dvx_projected = csiborgtools.flow.project_Vext(dvx, dvy, dvz, RA, dec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sigma_vir(Mh):\n",
    "    \"\"\"Suvodip's sigma_vir at z = 0\"\"\"\n",
    "    Omega_m = 0.3111\n",
    "    gv = 0.9\n",
    "    Delta_nl = 18 * np.pi**2 - 60 * (1 - Omega_m) - 32 * (1 - Omega_m)**2\n",
    "    return 476 * gv * Delta_nl**(1 / 6) * (Mh / 1e15)**(1/3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.925519739350855 0.3163983444613365\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 166524 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "bins = np.arange(np.log10(np.min(parent_mass)), np.log10(np.max(parent_mass)), 0.2)\n",
    "xbins = (bins[1:] + bins[:-1]) / 2\n",
    "bins = 10**bins\n",
    "xbins = 10**xbins\n",
    "\n",
    "\n",
    "fig, axs = plt.subplots(\n",
    "    nrows=2, ncols=2, figsize=(10, 8), sharey=\"row\", sharex=\"col\", gridspec_kw={\"height_ratios\": [3, 1]})\n",
    "cols = plt.rcParams[\"axes.prop_cycle\"].by_key()[\"color\"]\n",
    "fig.subplots_adjust(wspace=0.0)\n",
    "\n",
    "# Plot the projected velocity difference\n",
    "axs[0, 0].scatter(parent_mass[is_central], dvx_projected[is_central], s=0.5)\n",
    "axs[0, 1].scatter(parent_mass[~is_central], dvx_projected[~ is_central], s=0.5)\n",
    "plt.plot(xrange, sigma_vir(xrange), color=\"black\")\n",
    "axs[1, 0].plot(xrange, sigma_vir(xrange), color=\"black\")\n",
    "\n",
    "# Plot the standard deviation in a bin\n",
    "xrange = np.logspace(np.log10(np.min(parent_mass)), np.log10(np.max(parent_mass)), 100)\n",
    "stat, __, __ = binned_statistic(parent_mass[is_central], dvx_projected[is_central], statistic=\"std\", bins=bins)\n",
    "axs[1, 0].plot(xbins, stat, color=cols[0], label=r\"$\\sigma_{(\\mathbf{V} - \\mathbf{V}_{\\rm SPH}) \\cdot \\widehat{\\mathbf{r}}}$\")\n",
    "axs[1, 0].plot(xrange, sigma_vir(xrange), color=\"black\", label=r\"$\\sigma_{\\rm vir}$\")\n",
    "\n",
    "# Plot the standard deviation in a bin\n",
    "stat, __, __ = binned_statistic(parent_mass[~is_central], dvx_projected[~is_central], statistic=\"std\", bins=bins)\n",
    "axs[1, 1].plot(xbins, stat, color=cols[0])\n",
    "axs[1, 1].plot(xrange, sigma_vir(xrange), color=\"black\")\n",
    "m = np.isfinite(stat)\n",
    "beta, alpha = np.polyfit(np.log10(xbins[m] / 1e15), np.log10(stat[m]), 1)\n",
    "print(alpha, beta)\n",
    "axs[1, 1].plot(xbins, 10**alpha * (xbins / 1e15)**beta, color=\"red\", ls=\"--\", label=r\"$\\beta = {:.2f}$\".format(beta))\n",
    "\n",
    "\n",
    "axs[0, 0].set_ylabel(r\"$\\mathbf{V} \\cdot \\widehat{\\mathbf{r}}  ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[0, 0].set_ylabel(r\"$(\\mathbf{V} - \\mathbf{V}_{\\rm SPH}) \\cdot \\widehat{\\mathbf{r}}  ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[1, 0].set_ylabel(r\"$\\sigma  ~ [\\mathrm{km} / \\mathrm{s}]$\")\n",
    "axs[0, 0].set_title(\"Centrals\")\n",
    "axs[0, 1].set_title(\"Satellites\")\n",
    "for i in range(2):\n",
    "    axs[0, i].axhline(0, color=\"red\", ls=\"--\", alpha=0.5)\n",
    "    axs[1, i].set_xscale(\"log\")\n",
    "    # axs[1, i].set_yscale(\"log\")\n",
    "    axs[1, i].set_xlabel(r\"Parent mass $[M_\\odot / h]$\")\n",
    "\n",
    "axs[1, 0].legend()\n",
    "axs[1, 1].legend()\n",
    "\n",
    "\n",
    "fig.tight_layout(w_pad=0)\n",
    "fig.savefig(\"../plots/projected_residual_velocity_to_parent_mass.png\", dpi=300, bbox_inches=\"tight\")\n",
    "# fig.savefig(\"../plots/projected_velocity_to_parent_mass.png\", dpi=300, bbox_inches=\"tight\")\n",
    "\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv_csiborg",
   "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.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
