{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction to Dynamical Systems\n",
    "### Reference Case Conviction Voting\n",
    "\n",
    "## A Brief Intro\n",
    "Dynamical Systems are mathematical structures which describe evolution over time. Historically they are analyzed in Continuous Time because they are used to design, test and control physical systems. Over recent decades they have also been used to design, test and control computer systems. They are powerful abstractions that have helped manage the relationships between physical and computational systems, and even social systems (as they are critical ingredients in stochastic dynamic general equilibrium models which are used in macroeconomics). Whether continuous or discrete, dynamical systems are traditional associated with real valued vectors, matrices and tensors. Moving away from real valued fields, we can define dynamical systems over arbitrary data structures to recover a broader family models including but not limited to agent based models. In the case of the conviction voting cadCAD model we have a Generalized Dynamical System whose state contains a network object encoding a range of relationships as well as both numerical and non-numerical information. For the purpose of this short explainer, we will zoom in specifically on the calculation of conviction itself, which we can understand a Discrete Linear Time Invariant (LTI) system. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Canonical Forms\n",
    "\n",
    "Canonical forms use $x_t$ for the state of the system, and $u_t$ to describe inputs from external systems.\n",
    "\n",
    "Continous-time Non-linear Dynamical System:\n",
    "\n",
    "$\\dot x_t = f(x_t, u_t)$\n",
    "\n",
    "Continuous-time Linear Time Invariant (LTI) Dynamical Systems:\n",
    "\n",
    "$\\dot x_t = A x_t + B u_t$\n",
    "\n",
    "Discrete-time Non-linear Dynamical System:\n",
    "\n",
    "$x_{k+1} = f(x_k, u_k)$\n",
    "\n",
    "Discrete-time Linear Time Invariant (LTI) Dynamical Systems:\n",
    "\n",
    "$x_{k+1} = A x_k + B u_k$\n",
    "\n",
    "For cases where the discrete time system can be embedded directly in a continuous time, the time indexes can be mapped directly:\n",
    "\n",
    "|**Time Index Mapping Table 1** |\n",
    "| ---: | :--- | :--- | :--- | :--- | :--- | :--- | :---  | :--- |\n",
    "| **discrete-time** | $0$, | $1$, | ,$\\ldots$, | $k-1$, | $k$, | $k+1$, | $k+2$, | $\\ldots$|\n",
    "| **continuous-time** | $0$, |$\\Delta t$ | ,$\\ldots$, |$t-\\Delta t$, | $t$, | $t+\\Delta t$, |$t+2\\Delta t$, |$\\ldots$| \n",
    "\n",
    "For systems comprised of discrete events with strict ordering (eg state machines), we have any mapping that respects the order\n",
    "\n",
    "|**Time Index Mapping Table 2** |\n",
    "| ---: | :--- | :--- | :--- | :--- | :--- | :--- | :---  | :--- |\n",
    "| **discrete-time** | $0$, | $1$, | ,$\\ldots$, | $k-1$, | $k$, | $k+1$, | $k+2$, | $\\ldots$|\n",
    "| **continuous-time** | $t_0$, |$t_1$ | ,$\\ldots$, |$t_{k-1}$, | $t_k$, | $t_{k+1}$, |$t_{k+2}$, |$\\ldots$| \n",
    "| **time delta** |  | $t_1-t_0$ |  ,$\\ldots$, | $t_{k-1} -t_{k-2}$, | $t_{k} -t_{k-1}$, | $t_{k+1} -t_{k}$, |$t_{k+2} - t_{k+1}$, |$\\ldots$| \n",
    "\n",
    "noting that the time deltas can be further denoted $\\Delta t_k =t_{k} -t_{k-1}$, such that $t_k = t_0+\\sum_{\\kappa =0}^{k-1} \\Delta t_k$ for all $k>0$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Translating the Simplified Conviction Voting Example\n",
    "\n",
    "Suppose we start by only considering a single proposal and a single participant voting for that proposal. The conviction voting algorithm is **not** presented in canonical notion. In our case the state we are interested in is the conviction $y_t$ and the inputs from the external system are the actions of partipants staking a quantity of tokens $x_t$. Based on the notes above the canonical form would call conviction $x_k$ and the tokens staked $u_k$, but the table below includes the definitions that match the conviction voting documentation:\n",
    "\n",
    "$y_{t+1} = \\alpha y_t + x_t$\n",
    "\n",
    "| Term  | Canonical Notation | Defintion| Alternative notation|\n",
    "| :--:| :--: | :--:| :--: |\n",
    "| Conviction | $x_k$ | the accumulated support for the proposal at epoch $k$ | $y_t$|\n",
    "| Support | $u_k$ | the number of tokens staked in support of the proposal at epoch $k$| $x_t $ |\n",
    "\n",
    "Expressed in the Canonical form we have:\n",
    "\n",
    "$x_{k+1} = A x_k + B u_k$\n",
    "\n",
    "with $A = \\alpha$ and $B=1$, yielding\n",
    "\n",
    "$x_{t+k} = \\alpha x_k + u_k$\n",
    "\n",
    "For ease of reading we will stick to the notation in the other conviction voting documentation for the rest of this note but we recommend using the translation to the canonical form when refering to literature on dynamical systems to learn more.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Properties via the Simplified Example\n",
    "\n",
    "A one dimension Discrete-time LTI systems with initial condition $y_0$\n",
    "\n",
    "$y_{t+1} = \\alpha y_t + x_t$\n",
    "\n",
    "which can also be expressed as\n",
    "\n",
    "$y_{t+1}-y_t=\\Delta y_t = -(1-\\alpha) y_t + x_t$\n",
    "\n",
    "and as\n",
    "\n",
    "$y_T = y_0 +\\sum_{t=0}^{T-1} \\Delta y_t = \\alpha^T y_0  + \\sum_{t=0}^{T-1} \\alpha^t \\bar x$ when $x_t = \\bar x$ is a constant, thanks to telescoping series. we can also project the eventual conviction for any constant $x$ as\n",
    "\n",
    "$\\hat y(x) = \\lim_{T\\rightarrow \\infty} \\left( \\alpha^T y_0+\\sum_{t=0}^{T-1} \\alpha^t x\\right) = \\frac{x}{1-\\alpha}$ using geometric series.\n",
    "\n",
    "The first most important property is \"stability\". This is an entire topic in its own right but let's suffice to observe that as long as $\\alpha \\in [0,1)$ then growth in $y_t$ is bounded. In particular, for any $x$ this system is stable around the equilibrium $\\hat y(x_t) = \\frac{x_t}{(1-\\alpha)}$, even if $y_t \\neq \\hat y(x_t)$. In this way, our algorithm is tracking the signal $y_t$ can be thought of tracking the signal $\\frac{x_t}{1-\\alpha}$ with some intertia muting the volatility.\n",
    "\n",
    "The equation $\\hat y(x) = \\frac{x}{(1-\\alpha)}$ tells us precisely that each token in a conviction voting system contributes at most $\\frac{1}{1-\\alpha}$ \"conviction\" toward passing the proposal.\n",
    "\n",
    "Clearly, this expression goes unstable when $\\alpha =1$, in which case conviction does not decay and our algorithm diverges. Consider the sum over time to see the instability:\n",
    "\n",
    "$\\lim_{T\\rightarrow \\infty} y_T \\ge \\lim_{T\\rightarrow \\infty} \\sum_{t=0}^{T-1} \\bar x = \\infty$\n",
    "\n",
    "for strictly positive values $x_t\\ge \\bar x >0$ as $T\\rightarrow \\infty$.\n",
    "\n",
    "So in conviction voting we require that $\\alpha\\in [0,1)$. Technically it is legal to set $\\alpha = 0$ at which point this system simply $y_{t+1} = x_t$ sets the conviction to the amount of tokens staked at the current timestep. Although $x_t$ can still vary overtime, there is no accumulation. The choice of $\\alpha>0$ is made to create a \"leaky integrator\" which takes its name from a leaky bucket analogy.\n",
    "![](https://i.imgur.com/JemMMlo.png)\n",
    "\n",
    "The leaky bucket analogy also helps us understand $\\alpha$ in terms of its relationship to the \"half-life\" of the fluid in the bucket when we turn off the inflow. In this case there is some previously accumated conviction in our initial condition $y_0 >0$ but $x_t = 0$. In this case the dynamics are simply:\n",
    "\n",
    "$y_{t+1} = \\alpha y_t$ \n",
    "\n",
    "which is equivalent to \n",
    "\n",
    "$y_T = \\alpha^T y_0$\n",
    "\n",
    "The half-life of this system is defined by $T$ such that $y_T = y_0/2$ which satisfies the equation\n",
    "\n",
    "$\\frac{1}{2}  = \\alpha^T$\n",
    "\n",
    "thus the Half life in epochs for a given $\\alpha$ is\n",
    "\n",
    "$T = \\log_\\alpha \\frac{1}{2} = -\\log_\\alpha 2$\n",
    "\n",
    "and conversely the $\\alpha$ which achieves a desired half-life $T$ is\n",
    "\n",
    "$\\alpha = 2^{-1/T} = \\frac{1}{\\sqrt[T]{2}}$\n",
    "\n",
    "Further note that the relationship between $T$ and $\\alpha$ is sensative to timescaling. Suppose we wanted a half-life of 3 days but our discrete time scale has 1 hour epochs, then we must use $T = 24 \\times 3 = 72$ in the above equation.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9057236642639067\n",
      "0.9958826236582974\n"
     ]
    }
   ],
   "source": [
    "#consider a desired half life of 7 periods (day)\n",
    "# then alpha is given by\n",
    "alpha = 2**(-1/7)\n",
    "print(alpha)\n",
    "#note that if the implementation has epochs of such that there are 24 updates per period (day)\n",
    "#then the implementation alpha would be\n",
    "alpha = 2**(-1/(7*24))\n",
    "print(alpha)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "alpha=.9\n",
    "\n",
    "time = np.arange(0,200,1)\n",
    "\n",
    "K = len(time)\n",
    "\n",
    "step_up = np.zeros(K)\n",
    "step_down = np.ones(K)\n",
    "noisy = np.random.rand(K)\n",
    "\n",
    "for k in range(K): \n",
    "    if k>K/2:\n",
    "        step_up[k]=1\n",
    "        step_down[k]=0\n",
    "\n",
    "step_up_response = np.empty(K)\n",
    "step_up_response[0]=0\n",
    "\n",
    "step_down_response = np.empty(K)\n",
    "step_down_response[0]=0#1/(1-alpha)\n",
    "\n",
    "noisy_response = np.empty(K)\n",
    "noisy_response[0] = 0\n",
    "for k in range(K-1):\n",
    "    step_up_response[k+1] = alpha*step_up_response[k] + step_up[k]\n",
    "    step_down_response[k+1] = alpha*step_down_response[k] + step_down[k]\n",
    "    noisy_response[k+1] = alpha*noisy_response[k] + noisy[k]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Comparing Single Token Staked to Conviction Accumulation Over Time')"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 19296 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, step_up, 'b-')\n",
    "ax2.plot(time, step_up_response, 'g-')\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='b')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='g')\n",
    "plt.title('Comparing Single Token Staked to Conviction Accumulation Over Time')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Comparing Single Token Unstaked to Conviction Decay Over Time')"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 22920 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, step_down, 'b-')\n",
    "ax2.plot(time, step_down_response, 'g-')\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='b')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='g')\n",
    "plt.title('Comparing Single Token Unstaked to Conviction Decay Over Time')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.0, 10.480907796413126)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 58652 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, noisy, 'b-')\n",
    "ax2.plot(time, noisy_response, 'g-')\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='b')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='g')\n",
    "plt.title('Comparing Multiple Tokens Staked to Conviction Accumulation Over Time')\n",
    "\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above plot shows the signal processing power of the conviction voting algorithm. It effectively computes a time average scaled by a factor $\\frac{1}{1-\\alpha}$. In fact, one variant of conviction voting would include this normalization, creating an exponentially smoothed weighted average. However, in the basic version we choose to define it as a leaky integrator because we wanted to make explicit the value of ones votes accumulates over time to more than what one would attain in a standard voting method: $x< \\frac{x}{1-\\alpha}$ whenenver $\\alpha\\in (0,1)$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multidimensional Example\n",
    "\n",
    "Now in order to proced with this example we need to make it a little less simple by considering a case where there are $n$ participants and $m$ proposals. In this case the signals are distributed across the agents. That is to say each agent $i$ will get to choose their stake for each proposal $j$ based on their private preferences. They can make that decision however they want, as long as the action they take is valid and allowed by the system.\n",
    "\n",
    "Suppose participant $i$ has $h_t[i]$ tokens at time $t$, then they may choose to stake them across the available proposals $\\vec x_t[i] = \\big[x_t[i,0], \\ldots, x_t[i,m] \\big]$ where $x_t[i,j]$ is the number of tokens staked by participant $i$ in support of proposal $j$.  The staking action is valid as long as $\\sum_{j =0}^{m-1} x_t[i,j] \\le h_t[i]$.\n",
    "\n",
    "The information used to decide on $\\vec x_t[i]$ is private to agent $i$. Furthermore, in cases where $h_t[i]$ is variable (due to transferability of tokens), the quantity $h_t[i]$ itself is a decision as a consequence of private information related to agent $i$. For the purpose of this discussion, we don't worry about how agent $i$ decides to hold $h[i]$ or chooses to support proposals according to $\\vec x_t [i]$ but we do assume that in doing so she is expressing some of her private information, both magnitude $h_t[i]$ and direction $\\vec x_t[i]$.\n",
    "\n",
    "Given the choices for all participants we have a time varying matrix made up of terms $x_t[i,j]$:\n",
    "\n",
    "$X_t = \\left[\\begin{array}{c}\\vec x_t[0]\\\\ \\vdots \\\\ \\vec x_t[n-1]   \\end{array}\\right] \\in \\mathbb{R}_+^{n \\times m}$\n",
    "\n",
    "satisfying the condition that $X_t \\vec{\\mathbf{1}} \\le \\vec h_t$ for all time $t$ where $\\vec{\\mathbf{1}}$ is the $m$ dimensional column vector of ones and $\\vec{h}_t$ is the $n$ dimensional column vector of holdings. \n",
    "\n",
    "The most interesting thing about building this matrix view of the system is how it unifies the local and global states of the system. Surely each agent will make their own choices about $x_t[i,j]$ and $h_t[t]$ and those choices will vary in time based on a wide range of factors that we cannot hope to totally understand, but we do know that even as it varies in time our actors are using the freedoms afforded to them by this policy to move the system towards outcomes they percieve as best relative to their private preferences and information. The key to our algorithm is to compile that spatially distributed time varying singal into a discrete decision. We do this by restating our conviction voting algorithm in its matrix form:\n",
    "\n",
    "$\\vec{y}_{t+1} = \\alpha \\vec{y}_t  + X_t' \\vec{\\mathbb{1}}$ where $X_t'$ is the transpose of $X_t$ and $\\vec{\\mathbb{1}}$ is the $n$ dimensional column vector of ones. This expression is equivalent to\n",
    "\n",
    "$y_{t+1}[j] = \\alpha y_{t} + \\sum_{i} x_t[i,j]$ where $\\vec{y}_t = \\left[\\begin{array}{c}y_t[0]\\\\ \\vdots \\\\ \\vec y_t[m-1]   \\end{array}\\right] \\in \\mathbb{R}_+^{m}$\n",
    "\n",
    "This mathematical structure is actually the same as the one we demonstrated in the one dimensional case so we will review some the same checks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "n = 3\n",
    "m = 2\n",
    "\n",
    "mimo_step_up = np.zeros(shape=(K,n,m))\n",
    "mimo_step_down = np.zeros(shape=(K,n,m))\n",
    "mimo_noisy = np.random.rand(K*m*n).reshape((K,n,m))\n",
    "\n",
    "for k in range(K): \n",
    "    for i in range(n):\n",
    "        for j in range(m):\n",
    "            if k>2*K/(i+3):\n",
    "                mimo_step_up[k,i,j]= (1+i)*(1+j)\n",
    "                mimo_step_down[K-k,i,j]= (1+i)*(1+j)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "mimo_step_up_response = np.empty(shape=(K,m))\n",
    "mimo_step_up_response[0,:]=0\n",
    "\n",
    "mimo_step_down_response = np.empty(shape=(K,m))\n",
    "mimo_step_down_response[0,:]= 1/(1-alpha)*np.sum(mimo_step_down[0,:,:])\n",
    "\n",
    "mimo_noisy_response = np.empty(shape=(K,m))\n",
    "mimo_noisy_response[0,:]=0\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "for k in range(K-1):\n",
    "    mimo_step_up_response[k+1,:] = alpha*mimo_step_up_response[k,:] + np.sum(mimo_step_up[k,:,:], 0)\n",
    "    mimo_step_down_response[k+1,:] = alpha*mimo_step_down_response[k,:] + np.sum(mimo_step_down[k,:,:],0)\n",
    "    mimo_noisy_response[k+1,:] = alpha*mimo_noisy_response[k,:] + np.sum(mimo_noisy[k,:,:],0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test Series 1: Filling up the Proposal Support Tank\n",
    "\n",
    "A set of tests where the participants start supporting proposals, but their support starts at different times\n",
    "- Plot series 1: granular input signals (tokens staked) from 3 participants\n",
    "- Plot series 2: granular input signals (tokens staked) with outputs (conviction accumulated) from 3 participants\n",
    "- Plot 3: aggregate inputs (tokens staked) with outputs (conviction accumulated) for multiple proposals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'step_up tokens staked for proposal 1')"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 15116 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 15340 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "pd.DataFrame(mimo_step_up[:,:,0], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool')\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 0')\n",
    "pd.DataFrame(mimo_step_up[:,:,1], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool')\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot series 1 above, we can see the cumulative effect of multiple participants staking tokens on proposals, without the half life decay factor. Participant 2 stakes 3 tokens towards proposal 0 and 6 tokens towards proposal 1 around timestep 80, followed by participant 1 and participant 0 with their own staking amounts for each proposal at later timesteps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Support for Proposal 1')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 24312 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 24652 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_step_up[:,:,0], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_step_up[:,:,0],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('stake')\n",
    "plt.title('step_up tokens staked for proposal 0')\n",
    "ax2.plot(time, mimo_step_up_response[:,0], 'k-')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction accumulated')\n",
    "plt.title('Support for Proposal 0')\n",
    "\n",
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_step_up[:,:,1], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_step_up[:,:,1],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('stake')\n",
    "plt.title('step_up tokens staked for proposal 1')\n",
    "\n",
    "ax2.plot(time, mimo_step_up_response[:,1], 'k-')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction accumulated')\n",
    "plt.title('Support for Proposal 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot series 2, we add the output of participant staking (i.e. conviction accumulated) to our plots to show the impact of the time delay introduced by the half life decay parameter $alpha$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Conviction and tokens staked for proposals 0 and 1')"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 25544 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, np.sum(mimo_step_up, 1), 'b--')\n",
    "ax2.plot(time, mimo_step_up_response, 'g-')\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='b')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='g')\n",
    "\n",
    "plt.title('Conviction and tokens staked for proposals 0 and 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot 3, we compare tokens staked and conviction accumulated for both proposals 0 and 1 that were examined above."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test Series 2: Emptying Out the Proposal Support Tank\n",
    "\n",
    "A set of tests where the participants stop supporting a proposal but their support stops at different times\n",
    "- Plot series 4: granular input signals (tokens unstaked) from 3 participants\n",
    "- Plot series 5: granular input signals (tokens unstaked) with outputs (conviction decay) from 3 participants\n",
    "- Plot 6: aggregate inputs (tokens unstaked) with outputs (conviction decay) for multiple proposals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'step_down tokens staked for proposal 1')"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 16688 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 16920 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "pd.DataFrame(mimo_step_down[:,:,0], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool')\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_down tokens staked for proposal 0')\n",
    "pd.DataFrame(mimo_step_down[:,:,1], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool')\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_down tokens staked for proposal 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot series 4 above, we can see the cumulative effect of multiple participants unstaking tokens on proposals, without the half life decay factor. Participant 0 unstakes 1 tokens from proposal 0 and 2 tokens from proposal 1 around timestep 70, followed by participant 1 and participant 2 with their own unstaking amounts for each proposal at later timesteps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Support for Proposal 1')"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 29604 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 30220 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_step_down[:,:,0], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_step_down[:,:,0],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 0')\n",
    "ax2.plot(time, mimo_step_down_response[:,0], 'k-')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction')\n",
    "plt.title('Support for Proposal 0')\n",
    "\n",
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_step_down[:,:,1], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_step_down[:,:,1],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 1')\n",
    "\n",
    "ax2.plot(time, mimo_step_down_response[:,1], 'k-')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction')\n",
    "plt.title('Support for Proposal 1')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot series 5, we add the output of participant unstaking (i.e. conviction decay) to our plots to show the impact of the time delay introduced by the half life decay parameter $alpha$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Conviction and tokens staked for proposals 0 and 1')"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 33568 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, np.sum(mimo_step_down, 1), 'b--')\n",
    "ax2.plot(time, mimo_step_down_response, 'g-')\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='b')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='g')\n",
    "\n",
    "plt.title('Conviction and tokens staked for proposals 0 and 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot 6, we compare tokens unstaked and conviction decay for both proposals 0 and 1 that were examined above."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test Series 3: Noisy inputs - Constantly Filling and Emptying at the Same Time\n",
    "\n",
    "A set of tests where the participants start supporting a proposal but their support starts at different times\n",
    "- Plot series 7: granular input signals (tokens staked/unstaked) with outputs (conviction accumulated/decayed) with 3 participants\n",
    "- Plot series 8: aggregate input signals (tokens staked/unstaked) and outputs (conviction accumulated/decayed)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Support for Proposal 1')"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 118016 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 120608 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_noisy[:,:,0], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_noisy[:,:,0],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 0')\n",
    "\n",
    "ax2.plot(time, mimo_noisy_response[:,0], 'k')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction accumulated')\n",
    "plt.title('Support for Proposal 0')\n",
    "\n",
    "fig, ax1 = plt.subplots()\n",
    "ax2 = ax1.twinx()\n",
    "\n",
    "pd.DataFrame(mimo_noisy[:,:,1], columns = ['participant 0', 'participant 1', 'participant 2']).plot(kind='area', alpha = .5, cmap = 'cool', ax=ax1)\n",
    "ax1.plot(time, np.sum(mimo_noisy[:,:,1],1), 'k-.', alpha=.5)\n",
    "plt.xlabel('time')\n",
    "plt.ylabel('total tokens staked')\n",
    "plt.title('step_up tokens staked for proposal 1')\n",
    "\n",
    "ax2.plot(time, mimo_noisy_response[:,1], 'k')\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))\n",
    "\n",
    "plt.xlabel('time')\n",
    "ax1.set_ylabel('total tokens staked')\n",
    "ax2.set_ylabel('conviction accumulated')\n",
    "plt.title('Support for Proposal 1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot series 7 above, we can see the granular dynamic inputs of 3 participants staking & unstaking tokens, and the resulting dynamic conviction acting as an aggregator of collective preference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.0, 28.590199392003907)"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 107216 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax1 = plt.subplots()\n",
    "\n",
    "ax2 = ax1.twinx()\n",
    "ax1.plot(time, np.sum(mimo_noisy, 1), '.--', alpha=.3)\n",
    "ax2.plot(time, mimo_noisy_response)\n",
    "\n",
    "ax1.set_xlabel('time')\n",
    "ax1.set_ylabel('Tokens Staked', color='blue')\n",
    "ax2.set_ylabel('Conviction Accumulated', color='orange')\n",
    "\n",
    "plt.title('Conviction and tokens staked for proposals 0 and 1')\n",
    "\n",
    "\n",
    "ylim= ax1.get_ylim()\n",
    "ax2.set_ylim(0, ylim[1]/(1-alpha))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In plot 8 above, we can see the aggregate inputs (tokens staked/unstaked) and the aggregate outputs (conviction accumulation/decay) of a noisy input process, more accurately representing the behavior of these systems in live deployment."
   ]
  }
 ],
 "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.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
