{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "49636d51-6002-49cd-b7a5-436f48e00c7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from notebook_env import *\n",
    "\n",
    "from collections import namedtuple\n",
    "\n",
    "from sklearn.svm import LinearSVC\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "import evoml.game"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "4b04014b-2951-4d7b-b1ee-cf924cc4009c",
   "metadata": {},
   "outputs": [],
   "source": [
    "p_vec = np.linspace(0,1,41)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8660c385-bdf1-4564-9851-6b32396d3147",
   "metadata": {},
   "outputs": [],
   "source": [
    "RANDOM_SEED = 123\n",
    "random_state = np.random.default_rng(RANDOM_SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "45c41bb7-df4e-4d68-92b8-a0ecd25d3187",
   "metadata": {},
   "outputs": [],
   "source": [
    "Result = namedtuple('Result',[\n",
    "    'mechanism',\n",
    "    'learner',\n",
    "    'x',\n",
    "    'population',\n",
    "    'game',\n",
    "    'equilibria',\n",
    "])\n",
    "\n",
    "results = {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d1826477-bc8a-4b80-a8b8-a5faa4be39a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_1d_dist(*,ax,x,f):\n",
    "    colors = ['tab:blue','tab:red']\n",
    "    for i in range(2):\n",
    "        ax.fill_between(\n",
    "            x,f[i],\n",
    "            alpha=0.3,\n",
    "            color=colors[i],\n",
    "        )\n",
    "    ax.set(\n",
    "        ylim=(0,None),\n",
    "    )\n",
    "    return axs\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64dabdee-760e-48de-aab1-965372471c03",
   "metadata": {},
   "source": [
    "## Soft-SVM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c390a078-aebd-4d48-abe2-339585022750",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: title={'center': 'Population game'}, xlabel='p_B'>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 104172 characters]",
      "text/plain": [
       "<Figure size 1000x230 with 3 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 241,
       "width": 989
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "build_svm_pop = lambda pmaj: evoml.Population(\n",
    "    groups=[\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                scipy.stats.triang(c=0,loc=-1,scale=1),\n",
    "                scipy.stats.triang(c=0,loc=0,scale=1),\n",
    "            ],\n",
    "            p=[pmaj,1-pmaj],\n",
    "        ),\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                scipy.stats.triang(c=1,loc=-1,scale=1),\n",
    "                scipy.stats.triang(c=1,loc=0,scale=1),\n",
    "            ],\n",
    "            p=[1-pmaj,pmaj],\n",
    "        ),\n",
    "    ],\n",
    ")\n",
    "svm_pop = build_svm_pop(0.6)\n",
    "\n",
    "x = np.linspace(-1,1,2000)*1.2\n",
    "svm_x = x\n",
    "\n",
    "svm_game_df = evoml.game.from_population_1d_rv(\n",
    "    pop=svm_pop,\n",
    "    clf_factory=lambda: LinearSVC(loss='hinge',penalty='l2',C=1e-3),\n",
    "    # clf_factory=lambda: LinearSVC(loss='hinge',penalty='l2',C=1e-2),\n",
    "    # clf_factory=lambda: LinearSVC(C=1e-3),\n",
    "    x=svm_x,\n",
    "    p_vec=p_vec,\n",
    ")\n",
    "fig,axs = evoml.create_figure(1,3,figsize=(10,2.3))\n",
    "for i in [0,1]:\n",
    "    axs[i].plot(svm_x,svm_pop.groups[i].pdf(x).T)\n",
    "    axs[i].set_title(f'$D_{[\"A\",\"B\"][i]}$')\n",
    "\n",
    "svm_game_df.iloc[:,-3:].plot.line(ax=axs[2], title='Population game')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cf3c1238-5f8e-46d0-a572-4480ced62b22",
   "metadata": {},
   "outputs": [],
   "source": [
    "results['svm'] = Result(\n",
    "    mechanism='Proxy Loss',\n",
    "    learner='Soft-SVM',\n",
    "    population=svm_pop,\n",
    "    x=svm_x,\n",
    "    game=svm_game_df,\n",
    "    equilibria=[\n",
    "        (0.5,True),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "27b00ef0-9279-4826-b05a-2b38c3e84f09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "svm_regularization_experiment_alpha=0.75\n",
      "Figure saved as ./output/soft_svm_regularization.pdf\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<a href=\"data:text/plain;base64,[encoded data, 506428 characters]\" download=\"soft_svm_regularization.pdf\">Download soft_svm_regularization.pdf</a>"
      ],
      "text/plain": [
       "<evoml.analysis.DownloadableIO at 0x16b3f9d00>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 125880 characters]",
      "text/plain": [
       "<Figure size 1000x180 with 5 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 200,
       "width": 990
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig,axs = plt.subplot_mosaic(\n",
    "    mosaic=[['da','c1','c2','c3'],['db','c1','c2','c3']],\n",
    "    figsize=(10,1.8),\n",
    "    constrained_layout=dict(\n",
    "        w_pad=0.15,\n",
    "        hspace=0.0,\n",
    "        h_pad=0.0,\n",
    "    ),\n",
    "    # sharex=True,\n",
    "    # width_ratios=[1,1,0.5,1],\n",
    ")\n",
    "\n",
    "svm_C_pop = build_svm_pop(param(0.75,'svm_regularization_experiment_alpha'))\n",
    "\n",
    "\n",
    "\n",
    "for ax,C,eq in zip(\n",
    "    [axs['c1'],axs['c2'],axs['c3']],\n",
    "    [1e-1,1e-2,1e-3],\n",
    "    [\n",
    "        [(0.0,True),(0.5,False),(1.0,True)],\n",
    "        [(0.5,True)],\n",
    "        [(0.0,True),(0.2,False),(0.5,True),(0.8,False),(1.0,True)],\n",
    "    ]\n",
    "):\n",
    "    evoml.plot_population_game(\n",
    "        game_df=evoml.game.from_population_1d_rv(\n",
    "            pop=svm_C_pop,\n",
    "            clf_factory=lambda: LinearSVC(loss='hinge',penalty='l2',C=C),\n",
    "            x=svm_x,\n",
    "            p_vec=p_vec,\n",
    "        ),\n",
    "        ax=ax,\n",
    "        equilibria=eq,\n",
    "    )\n",
    "    ax.set(\n",
    "        title=f'Soft-SVM ($\\\\lambda={1/C:.0f}$)',\n",
    "    )\n",
    "    if C<1e-1:\n",
    "        ax.set(\n",
    "            ylabel=None,\n",
    "        )\n",
    "    ax.yaxis.set_major_formatter(\n",
    "        matplotlib.ticker.PercentFormatter(xmax=1,decimals=0),\n",
    "    )\n",
    "\n",
    "axs['da'].get_xaxis().set_visible(False)\n",
    "plot_1d_dist(\n",
    "    ax=axs['da'],\n",
    "    x=x,\n",
    "    f=svm_C_pop.mixture_pdf(x,p=[1,0]),\n",
    ")\n",
    "axs['da'].set(\n",
    "    title='Data Distributions',\n",
    "    ylabel='$D_A$',\n",
    ")\n",
    "plot_1d_dist(\n",
    "    ax=axs['db'],\n",
    "    x=x,\n",
    "    f=svm_C_pop.mixture_pdf(x,p=[0,1]),\n",
    ")\n",
    "axs['db'].set(\n",
    "    ylabel='$D_B$',\n",
    ")\n",
    "evoml.analysis.save_and_download_fig(fig, './output/soft_svm_regularization.pdf', bbox_inches='tight')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb4feca6-62a8-4ea9-995d-c6a198b7a951",
   "metadata": {},
   "source": [
    "## k-NN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ebdd4b46-93b4-4f28-9755-9bcf72bb8cbb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def knn_groups(alpha, beta):\n",
    "    d = 0.6\n",
    "    w = 0.6\n",
    "    a = b = 5\n",
    "    pos = scipy.stats.beta(a,b,loc=d/2,scale=w)\n",
    "    neg = scipy.stats.beta(a,b,loc=-d/2-w,scale=w)\n",
    "    return [\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                evoml.MixtureRandomVariable(\n",
    "                    rv_vec=[\n",
    "                        neg,\n",
    "                        pos,\n",
    "                    ],\n",
    "                    p=[\n",
    "                        (1-beta)/(1-beta+beta*alpha),\n",
    "                        (beta*alpha)/(1-beta+beta*alpha),\n",
    "                    ],\n",
    "                ),\n",
    "                pos,\n",
    "            ],\n",
    "            p = [\n",
    "                1-beta*(1-alpha),\n",
    "                beta*(1-alpha),\n",
    "            ]\n",
    "        ),\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                neg,\n",
    "                evoml.MixtureRandomVariable(\n",
    "                    rv_vec=[\n",
    "                        pos,\n",
    "                        neg,\n",
    "                    ],\n",
    "                    p=[\n",
    "                        (1-beta)/(1-beta+beta*alpha),\n",
    "                        (beta*alpha)/(1-beta+beta*alpha),\n",
    "                    ],\n",
    "                ),\n",
    "            ],\n",
    "            p = [\n",
    "                beta*(1-alpha),\n",
    "                1-beta*(1-alpha),\n",
    "            ]\n",
    "        ),\n",
    "    ]\n",
    "\n",
    "knn_pop = evoml.Population(\n",
    "    groups=knn_groups(\n",
    "        alpha=0.2,\n",
    "        beta=0.8,\n",
    "    )\n",
    ")\n",
    "\n",
    "knn_x = x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d4ba23e7-a9cd-48cd-ae65-aaecf444c592",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "af2ad644179e4e5eb0625a66eb5f1134",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Population game:   0%|          | 0/41 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "knn_game_df = evoml.game.from_population_sample(\n",
    "    pop=knn_pop,\n",
    "    clf_factory=lambda: KNeighborsClassifier(n_neighbors=1),\n",
    "    p_vec=p_vec,\n",
    "    n_train=100,\n",
    "    n_test=100,\n",
    "    n_reps=2000,\n",
    "    random_state=random_state,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7a4aa051-8433-46a4-b55f-89414643005e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: xlabel='p_B'>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 98612 characters]",
      "text/plain": [
       "<Figure size 1000x230 with 3 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 241,
       "width": 989
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig,axs = evoml.create_figure(1,3,figsize=(10,2.3))\n",
    "for i in [0,1]:\n",
    "    axs[i].plot(knn_x,knn_pop.groups[i].pdf(knn_x).T)\n",
    "    axs[i].set_title(f'$D_{[\"A\",\"B\"][i]}$')\n",
    "\n",
    "knn_game_df.iloc[:,-3:].groupby(level=0).mean().plot.line(ax=axs[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2b679a33-9c78-461d-b25e-4bd24c6de2bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "results['knn'] = Result(\n",
    "    mechanism='Interpolation',\n",
    "    learner='1-NN',\n",
    "    population=knn_pop,\n",
    "    x=knn_x,\n",
    "    game=knn_game_df,\n",
    "    equilibria=[\n",
    "        (0.5,True),\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "6a6ba2c9-b7a0-48c3-9e69-6cbee9f27643",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 125344 characters]",
      "text/plain": [
       "<Figure size 1000x220 with 3 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 231,
       "width": 989
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "k_vec = [1,3,5]\n",
    "\n",
    "fig,axs=evoml.create_figure(\n",
    "    1,3,\n",
    "    figsize=(10,2.2),\n",
    "    # sharey=True,\n",
    ")\n",
    "\n",
    "alpha = 0.2\n",
    "beta = 0.8\n",
    "\n",
    "for k,ax in zip(k_vec,axs):\n",
    "    ## Theory\n",
    "    p = np.linspace(0,1,100)\n",
    "    binom_cdf = lambda p: scipy.stats.binom.cdf(n=k,k=k//2,p=p)\n",
    "    gamma = p - 2*p*beta + beta\n",
    "    noise_coeff_pos = beta*(1-p)/gamma\n",
    "    noise_coeff_neg = beta*p/(1-gamma)\n",
    "    # exp_yhat_pos = 1-2*binom_cdf(1-noise_coeff_pos*alpha)\n",
    "    # exp_yhat_neg = -1+2*binom_cdf(1-noise_coeff_neg*alpha)\n",
    "    exp_yhat_pos = -1+2*binom_cdf(noise_coeff_pos*alpha)\n",
    "    exp_yhat_neg = 1-2*binom_cdf(noise_coeff_neg*alpha)\n",
    "    acc_pos = (1+(1-2*alpha)*exp_yhat_pos)/2\n",
    "    acc_neg = (1+(-1)*exp_yhat_neg)/2\n",
    "    acc_A = beta*acc_pos + (1-beta)*acc_neg\n",
    "    acc_B = acc_A[::-1]\n",
    "    line_params = {\n",
    "        # 'linestyle': '--',\n",
    "        'zorder': -1,\n",
    "        # 'alpha': 0.4,\n",
    "    }\n",
    "    ax.plot(p, acc_A, label='acc_A',color=evoml.group_colors[0], **line_params)\n",
    "    ax.plot(p, acc_B, label='acc_B',color=evoml.group_colors[1], **line_params)\n",
    "    ax.plot(p, (1-p)*acc_A+p*acc_B, color='tab:cyan', **line_params)\n",
    "    ax.set_title(f'k-NN with $k={k}$')\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60509a69-1abc-4258-9546-130120bafe6e",
   "metadata": {},
   "source": [
    "## Hard-SVM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "9b36d84e-b07f-4644-823b-f2c9ec1817d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def hardsvm_groups():\n",
    "    w = 0.2\n",
    "    pmaj = 0.5\n",
    "    return [\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                scipy.stats.uniform(-w,w),\n",
    "                scipy.stats.uniform(0,1),\n",
    "            ],\n",
    "            p=[1-pmaj,pmaj],\n",
    "            \n",
    "        ),\n",
    "        evoml.RandomVariablePopulationGroup(\n",
    "            rv_vec=[\n",
    "                scipy.stats.uniform(-1,1),\n",
    "                scipy.stats.uniform(0,w),\n",
    "            ],\n",
    "            p=[pmaj,1-pmaj],\n",
    "        ),\n",
    "    ]\n",
    "\n",
    "hardsvm_pop = evoml.Population(groups=hardsvm_groups())\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b67cde1a-a8ef-4ef7-905f-23212e6c8696",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7a589e707dd447d4ba1db79ebbd69c45",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Population game:   0%|          | 0/21 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "hardsvm_game_df = evoml.game.from_population_sample(\n",
    "    pop=hardsvm_pop,\n",
    "    clf_factory=lambda: evoml.OneDimensionalHardSVM(),\n",
    "    p_vec=p_vec[::2],\n",
    "    n_train=20,\n",
    "    n_test=200,\n",
    "    # n_reps=100,\n",
    "    n_reps=2000,\n",
    "    random_state=random_state,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "8bc4f90e-94f3-4341-8414-00a4482dccf4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: xlabel='p_B'>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 61396 characters]",
      "text/plain": [
       "<Figure size 1000x230 with 3 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 241,
       "width": 989
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig,axs = evoml.create_figure(1,3,figsize=(10,2.3))\n",
    "for i in [0,1]:\n",
    "    axs[i].plot(x,hardsvm_pop.groups[i].pdf(x).T)\n",
    "    axs[i].set_title(f'$D_{[\"A\",\"B\"][i]}$')\n",
    "hardsvm_game_df.iloc[:,-3:].groupby(level=0).mean().plot.line(ax=axs[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "2e9b65ac-4377-4a49-9c94-05ba78c79f8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "results['finite'] = Result(\n",
    "    mechanism='Finite Data',\n",
    "    learner='Hard-SVM',\n",
    "    population=hardsvm_pop,\n",
    "    x=x,\n",
    "    game=hardsvm_game_df,\n",
    "    equilibria=[\n",
    "        (0.0,True),\n",
    "        (1.0,True),\n",
    "        (0.5,False),\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2602b282-dc12-47f2-94f2-0af8e4adf84d",
   "metadata": {},
   "source": [
    "## Summary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "7fe65cb0-6c10-434d-a230-6c7ffc2939c7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Figure saved as ./output/coexistence.pdf\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<a href=\"data:text/plain;base64,[encoded data, 553544 characters]\" download=\"coexistence.pdf\">Download coexistence.pdf</a>"
      ],
      "text/plain": [
       "<evoml.analysis.DownloadableIO at 0x379351260>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 142640 characters]",
      "text/plain": [
       "<Figure size 950x260 with 9 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 270,
       "width": 940
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig,axs=evoml.create_figure(\n",
    "    3,3,\n",
    "    figsize=(9.5,2.6),\n",
    "    height_ratios=[1,1,2],\n",
    "    constrained_layout=dict(\n",
    "        w_pad=0.15,\n",
    "        h_pad=0.05,\n",
    "    ),\n",
    ")\n",
    "\n",
    "for ax in axs[:2].ravel():\n",
    "    ax.set(\n",
    "        xticklabels=[],\n",
    "        yticklabels=[],\n",
    "    )\n",
    "\n",
    "for j in range(3):\n",
    "    axs[0,j].set(\n",
    "        ylabel='$D_A$',\n",
    "    )\n",
    "    axs[1,j].set(\n",
    "        title=None,\n",
    "        # xlabel='',\n",
    "        ylabel='$D_B$',\n",
    "    )\n",
    "\n",
    "for j,res in enumerate(results.values()):\n",
    "    axs[0,j].get_xaxis().set_visible(False)\n",
    "    axs[0,j].set(\n",
    "        title=res.mechanism,\n",
    "        ylabel='$D_A$',\n",
    "    )\n",
    "    axs[2,j].set(\n",
    "        title=res.learner,\n",
    "    )\n",
    "    # Plot data\n",
    "    plot_1d_dist(\n",
    "        ax=axs[0,j],\n",
    "        x=res.x,\n",
    "        f=res.population.mixture_pdf(res.x,p=[1,0]),\n",
    "    )\n",
    "    plot_1d_dist(\n",
    "        ax=axs[1,j],\n",
    "        x=res.x,\n",
    "        f=res.population.mixture_pdf(res.x,p=[0,1]),\n",
    "    )\n",
    "    # Plot game\n",
    "    evoml.plot_population_game(\n",
    "        game_df=res.game,\n",
    "        ax=axs[2,j],\n",
    "        equilibria=res.equilibria,\n",
    "        equilibria_kwargs=dict(\n",
    "            d=0.07,\n",
    "        )\n",
    "    )\n",
    "    axs[2,j].yaxis.set_major_formatter(\n",
    "        matplotlib.ticker.PercentFormatter(xmax=1,decimals=0),\n",
    "    )\n",
    "    if j>0:\n",
    "        axs[2,j].set(\n",
    "            ylabel=None,\n",
    "        )\n",
    "\n",
    "fig.add_artist(matplotlib.text.Text(\n",
    "    x=0.015,\n",
    "    y=0.75,\n",
    "    text='Distributions',\n",
    "    # ha='center',\n",
    "    va='center',\n",
    "    rotation=90,\n",
    "    fontsize=10,\n",
    "))\n",
    "\n",
    "evoml.analysis.save_and_download_fig(fig, './output/coexistence.pdf', bbox_inches='tight')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "6df4ff17-a4be-4956-99c4-35c72cfb14b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parameters saved as: ./output/coexistence.json\n"
     ]
    }
   ],
   "source": [
    "param_tracker.save('./output/coexistence.json')"
   ]
  }
 ],
 "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.12.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
