{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# cadCAD Tutorials: The Robot and the Marbles, part 3\n",
    "In parts [1](../robot-marbles-part-1/robot-marbles-part-1.ipynb) and [2](../robot-marbles-part-2/robot-marbles-part-2.ipynb) we introduced the 'language' in which a system must be described in order for it to be interpretable by cadCAD and some of the basic concepts of the library:\n",
    "* State Variables\n",
    "* Timestep\n",
    "* State Update Functions\n",
    "* Partial State Update Blocks\n",
    "* Simulation Configuration Parameters\n",
    "* Policies\n",
    "\n",
    "In this notebook we'll look at how subsystems within a system can operate in different frequencies. But first let's copy the base configuration with which we ended Part 2. Here's the description of that system:\n",
    "\n",
    "__The robot and the marbles__  \n",
    "* Picture a box (`box_A`) with ten marbles in it; an empty box (`box_B`) next to the first one; and __two__ robot arms capable of taking a marble from any one of the boxes and dropping it into the other one. \n",
    "* The robots are programmed to take one marble at a time from the box containing the largest number of marbles and drop it in the other box. They repeat that process until the boxes contain an equal number of marbles.\n",
    "* The robots act simultaneously; in other words, they assess the state of the system at the exact same time, and decide what their action will be based on that information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture\n",
    "\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# List of all the state variables in the system and their initial values\n",
    "initial_conditions = {\n",
    "    'box_A': 10, # as per the description of the example, box_A starts out with 10 marbles in it\n",
    "    'box_B': 0 # as per the description of the example, box_B starts out empty\n",
    "}\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "\n",
    "\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# Settings of general simulation parameters, unrelated to the system itself\n",
    "# `T` is a range with the number of discrete units of time the simulation will run for;\n",
    "# `N` is the number of times the simulation will be run (Monte Carlo runs)\n",
    "# In this example, we'll run the simulation once (N=1) and its duration will be of 10 timesteps\n",
    "# We'll cover the `M` key in a future article. For now, let's leave it empty\n",
    "simulation_parameters = {\n",
    "    'T': range(10),\n",
    "    'N': 1,\n",
    "    'M': {}\n",
    "}\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# We specify the robot arm's logic in a Policy Function\n",
    "def robot_arm(params, step, sL, s):\n",
    "    add_to_A = 0\n",
    "    if (s['box_A'] > s['box_B']):\n",
    "        add_to_A = -1\n",
    "    elif (s['box_A'] < s['box_B']):\n",
    "        add_to_A = 1\n",
    "    return({'add_to_A': add_to_A, 'add_to_B': -add_to_A})\n",
    "    \n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# We make the state update functions less \"intelligent\",\n",
    "# ie. they simply add the number of marbles specified in _input \n",
    "# (which, per the policy function definition, may be negative)\n",
    "def increment_A(params, step, sL, s, _input):\n",
    "    y = 'box_A'\n",
    "    x = s['box_A'] + _input['add_to_A']\n",
    "    return (y, x)\n",
    "\n",
    "def increment_B(params, step, sL, s, _input):\n",
    "    y = 'box_B'\n",
    "    x = s['box_B'] + _input['add_to_B']\n",
    "    return (y, x)\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "\n",
    "\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# In the Partial State Update Blocks, \n",
    "# the user specifies if state update functions will be run in series or in parallel\n",
    "# and the policy functions that will be evaluated in that block\n",
    "partial_state_update_blocks = [\n",
    "    { \n",
    "        'policies': { # The following policy functions will be evaluated and their returns will be passed to the state update functions\n",
    "            'robot_arm_1': robot_arm,\n",
    "            'robot_arm_2': robot_arm\n",
    "        },\n",
    "        'variables': { # The following state variables will be updated simultaneously\n",
    "            'box_A': increment_A,\n",
    "            'box_B': increment_B\n",
    "        }\n",
    "    }\n",
    "]\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "from cadCAD.configuration import Configuration\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# The configurations above are then packaged into a `Configuration` object\n",
    "config = Configuration(initial_state=initial_conditions, #dict containing variable names and initial values\n",
    "                       partial_state_update_blocks=partial_state_update_blocks, #dict containing state update functions\n",
    "                       sim_config=simulation_parameters #dict containing simulation parameters\n",
    "                      )\n",
    "\n",
    "from cadCAD.engine import ExecutionMode, ExecutionContext, Executor\n",
    "exec_mode = ExecutionMode()\n",
    "exec_context = ExecutionContext(exec_mode.single_proc)\n",
    "executor = Executor(exec_context, [config]) # Pass the configuration object inside an array\n",
    "raw_result, tensor = executor.execute() # The `execute()` method returns a tuple; its first elements contains the raw results\n",
    "\n",
    "%matplotlib inline\n",
    "import pandas as pd\n",
    "df = pd.DataFrame(raw_result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 25460 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "df.plot('timestep', ['box_A', 'box_B'], grid=True, \n",
    "        xticks=list(df['timestep'].drop_duplicates()), \n",
    "        colormap = 'RdYlGn',\n",
    "        yticks=list(range(1+(df['box_A']+df['box_B']).max())));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Asynchronous Subsystems\n",
    "We have defined that the robots operate simultaneously on the boxes of marbles. But it is often the case that agents within a system operate asynchronously, each having their own operation frequencies or conditions.\n",
    "\n",
    "Suppose that instead of acting simultaneously, the robots in our examples operated in the following manner:\n",
    "* Robot 1: acts once every 2 timesteps\n",
    "* Robot 2: acts once every 3 timesteps\n",
    "\n",
    "One way to simulate the system with this change is to introduce a check of the current timestep before the robots act, with the definition of separate policy functions for each robot arm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# We specify each of the robots logic in a Policy Function\n",
    "robots_periods = [2,3] # Robot 1 acts once every 2 timesteps; Robot 2 acts once every 3 timesteps\n",
    "\n",
    "def get_current_timestep(cur_substep, s):\n",
    "    if cur_substep == 1:\n",
    "        return s['timestep']+1\n",
    "    return s['timestep']\n",
    "\n",
    "def robot_arm_1(params, step, sL, s):\n",
    "    _robotId = 1\n",
    "    if get_current_timestep(step, s)%robots_periods[_robotId-1]==0: # on timesteps that are multiple of 2, Robot 1 acts\n",
    "        return robot_arm(params, step, sL, s)\n",
    "    else:\n",
    "        return({'add_to_A': 0, 'add_to_B': 0}) # for all other timesteps, Robot 1 doesn't interfere with the system\n",
    "\n",
    "def robot_arm_2(params, step, sL, s):\n",
    "    _robotId = 2\n",
    "    if get_current_timestep(step, s)%robots_periods[_robotId-1]==0: # on timesteps that are multiple of 3, Robot 2 acts\n",
    "        return robot_arm(params, step, sL, s)\n",
    "    else:\n",
    "        return({'add_to_A': 0, 'add_to_B': 0}) # for all other timesteps, Robot 2 doesn't interfere with the system\n",
    "\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# In the Partial State Update Blocks, \n",
    "# the user specifies if state update functions will be run in series or in parallel\n",
    "# and the policy functions that will be evaluated in that block\n",
    "partial_state_update_blocks = [\n",
    "    { \n",
    "        'policies': { # The following policy functions will be evaluated and their returns will be passed to the state update functions\n",
    "            'robot_arm_1': robot_arm_1,\n",
    "            'robot_arm_2': robot_arm_2\n",
    "        },\n",
    "        'variables': { # The following state variables will be updated simultaneously\n",
    "            'box_A': increment_A,\n",
    "            'box_B': increment_B\n",
    "        }\n",
    "    }\n",
    "]\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "\n",
    "\n",
    "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # \n",
    "# The configurations above are then packaged into a `Configuration` object\n",
    "config = Configuration(initial_state=initial_conditions, #dict containing variable names and initial values\n",
    "                       partial_state_update_blocks=partial_state_update_blocks, #dict containing state update functions\n",
    "                       sim_config=simulation_parameters #dict containing simulation parameters\n",
    "                      )\n",
    "\n",
    "exec_mode = ExecutionMode()\n",
    "exec_context = ExecutionContext(exec_mode.single_proc)\n",
    "executor = Executor(exec_context, [config]) # Pass the configuration object inside an array\n",
    "raw_result, tensor = executor.execute() # The `execute()` method returns a tuple; its first elements contains the raw results\n",
    "\n",
    "df = pd.DataFrame(raw_result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x104c52320>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 14704 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "df.plot('timestep', ['box_A', 'box_B'], \n",
    "        grid=True, \n",
    "        xticks=list(df['timestep'].drop_duplicates()), \n",
    "        yticks=list(range(1+max(df['box_A'].max(),df['box_B'].max()))),\n",
    "        colormap = 'RdYlGn'\n",
    "       )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a step-by-step look at what the simulation tells us:\n",
    "* Timestep 1: the number of marbles in the boxes does not change, as none of the robots act\n",
    "* Timestep 2: Robot 1 acts, Robot 2 doesn't; resulting in one marble being moved from box A to box B\n",
    "* Timestep 3: Robot 2 acts, Robot 1 doesn't; resulting in one marble being moved from box A to box B\n",
    "* Timestep 4: Robot 1 acts, Robot 2 doesn't; resulting in one marble being moved from box A to box B\n",
    "* Timestep 5: the number of marbles in the boxes does not change, as none of the robots act\n",
    "* Timestep 6: Robots 1 __and__ 2 act, as 6 is a multiple of 2 __and__ 3; resulting in two marbles being moved from box A to box B and an equilibrium being reached."
   ]
  }
 ],
 "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.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
