{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cae010ce-7df5-4e2e-b89c-16ef35618b26",
   "metadata": {},
   "source": [
    "# Reaction Rates\n",
    "\n",
    "In this section, we will teach how to assign custom rates based on the meta-species states and how to write custom rate expressions other than mass action kinetics. \n",
    "Let us start with some meta-species definitions. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "98a8ca7b-c908-494b-86d8-c2fac76dcd7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<mobspy.modules.meta_class.Reactions at 0x7f887ca82dc0>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from mobspy import *\n",
    "\n",
    "Age, Mortal, Infectable, Virus = BaseSpecies()\n",
    "Replicator = New(Age)\n",
    "\n",
    "Age.young >> Age.old [1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7329cac4-8f9f-45a0-aa10-19021f9000ff",
   "metadata": {},
   "source": [
    "For any meta-reaction, if a number is used inside the brackets next to a meta-reaction, MobsPy will automatically construct the reaction rate using mass-action kinetics using the number as a reaction rate constant \n",
    "if the model is deterministic. If the model is stochastic, we use the propensity expression, which considers the factorial division, with the number in the brackets multiplying the propensity expression. \n",
    "The expression used for each reaction can be verified using the compile method for the simulation object.\n",
    "\n",
    "MobsPy also allows the user to assign rates according to the meta-species states. To do so, the user can pass a function as a meta-reaction rate by either defining it inside the brackets with Python's innate lambda notation or writing its name when previously defined. \n",
    "If a function is used, MobsPy will pass the states of the meta-species of the reactants to the arguments of the function in matching order for all reactions defined by that meta-reaction.\n",
    "The states can then be queried for characteristics and return different values based on the result of the query. \n",
    "\n",
    "For example, we define a duplication reaction that depends on age. In the reaction below, meta-species with the old state have a higher reaction rate constant:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "da60cebb-cd22-4ccc-8e43-5639175caf60",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<mobspy.modules.meta_class.Reactions at 0x7f887cab6fd0>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Replicator >> 2*Replicator.young [lambda r1: 2 if r1.old else 1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "455b3040-4ec6-41e8-bf20-b70ac57c87e0",
   "metadata": {},
   "source": [
    "To continue we will define three tipes of Cells - C1, C2, and C3 - and two types of viruses - V1, V2. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "55a45c34-4078-4121-bec6-a684fde13cad",
   "metadata": {},
   "outputs": [],
   "source": [
    "Cell = Infectable*Age*Mortal\n",
    "C1, C2, C3 = New(Cell)\n",
    "V1, V2 = New(Virus)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f08fa35c-a4ff-4aee-ae80-a3ed613ccd17",
   "metadata": {},
   "source": [
    "We now define an infection reaction with rates that depend on the type of Cell and Virus used. \n",
    "\n",
    "In this case, the user has two options, and we will list both here. \n",
    "The first one uses the method .is_a(meta species) applied to the arguments of a function used as a rate. This method returns true if the reactant is or inherits from the given meta-species passed as an argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "208e7573-f40e-472c-9a2f-92562fa50cac",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Compiling model\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The reactants C1.not_infect.old and V1 yield a rate constant of: 0.8800000000000001\n",
      "The reactants C1.not_infect.young and V1 yield a rate constant of: 0.8800000000000001\n",
      "The reactants C1.not_infect.old and V2 yield a rate constant of: 1.2100000000000002\n",
      "The reactants C1.not_infect.young and V2 yield a rate constant of: 1.2100000000000002\n",
      "The reactants C3.not_infect.old and V1 yield a rate constant of: 0.96\n",
      "The reactants C3.not_infect.young and V1 yield a rate constant of: 0.96\n",
      "The reactants C3.not_infect.old and V2 yield a rate constant of: 1.32\n",
      "The reactants C3.not_infect.young and V2 yield a rate constant of: 1.32\n",
      "The reactants C2.not_infect.old and V1 yield a rate constant of: 0.96\n",
      "The reactants C2.not_infect.young and V1 yield a rate constant of: 0.96\n",
      "The reactants C2.not_infect.old and V2 yield a rate constant of: 1.32\n",
      "The reactants C2.not_infect.young and V2 yield a rate constant of: 1.32\n"
     ]
    }
   ],
   "source": [
    "\n",
    "def infection_rate_1(r1, r2): \n",
    "    factor = 1\n",
    "    if r1.is_a(C1):\n",
    "        factor = 1.1*factor\n",
    "    else:\n",
    "        factor = 1.2*factor\n",
    "    \n",
    "    if r2.is_a(V1):\n",
    "        factor = 0.8*factor\n",
    "    elif r2.is_a(V2):\n",
    "        factor = 1.1*factor\n",
    "    else:\n",
    "        factor = 1.2*factor\n",
    "    \n",
    "    print(f'The reactants {r1.get_state()} and {r2.get_state()} yield a rate constant of: {factor}')\n",
    "    return factor\n",
    "    \n",
    "\n",
    "Infectable.not_infect + Virus >> Infectable.infected [infection_rate_1]\n",
    "\n",
    "S_test = Simulation(C1 | C2 | C3 | V1 | V2)\n",
    "S_test.compile(verbose=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eaf8faf1-2846-461e-b853-5f85e91db0a2",
   "metadata": {},
   "source": [
    "The other way is to use the get_state method to return the state of the meta-species currently used to construct the reaction.\n",
    "With this string, one can find the meta-species name and use a Python dictionary to define the rates. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5302997a-8195-46f1-abc9-49537adf5fd4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Compiling model\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The reactants C1.not_infect.old and V1 yield a rate constant of: 0.8800000000000001\n",
      "The reactants C1.not_infect.young and V1 yield a rate constant of: 0.8800000000000001\n",
      "The reactants C1.not_infect.old and V2 yield a rate constant of: 1.2100000000000002\n",
      "The reactants C1.not_infect.young and V2 yield a rate constant of: 1.2100000000000002\n",
      "The reactants C3.not_infect.old and V1 yield a rate constant of: 0.96\n",
      "The reactants C3.not_infect.young and V1 yield a rate constant of: 0.96\n",
      "The reactants C3.not_infect.old and V2 yield a rate constant of: 1.32\n",
      "The reactants C3.not_infect.young and V2 yield a rate constant of: 1.32\n",
      "The reactants C2.not_infect.old and V1 yield a rate constant of: 0.96\n",
      "The reactants C2.not_infect.young and V1 yield a rate constant of: 0.96\n",
      "The reactants C2.not_infect.old and V2 yield a rate constant of: 1.32\n",
      "The reactants C2.not_infect.young and V2 yield a rate constant of: 1.32\n"
     ]
    }
   ],
   "source": [
    "\n",
    "infection_dict = {'C1':{'V1':1.1 , 'V2':1.3 , 'V3':1.7}, \n",
    "                  'C2':{'V1':1.4 , 'V2':1.1 , 'V3':1.9},\n",
    "                  'C3':{'V1':1.9 , 'V2':1.2 , 'V3':1.6}} \n",
    "\n",
    "\n",
    "def infection_rate_2(r1, r2): \n",
    "    r1_name, r2_name = r1.get_state().split('.')[-1], r2.get_state().split('.')[-1]\n",
    "    factor = infection_dict[r1_name][r2_name]\n",
    "    print(f'The reactants {r1.get_state()} and {r2.get_state()} yield a rate constant of: {factor}')\n",
    "    return factor\n",
    "\n",
    "\n",
    "S_test_2 = Simulation(C1 | C2 | C3 | V1 | V2)\n",
    "S_test_2.compile(verbose=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "413474b5-eb34-411b-9947-3bcad377bdc6",
   "metadata": {},
   "source": [
    "## MobsPy Expressions\n",
    "\n",
    "If one wishes to assign rates other than mass action kinetics, one can use MobsPy Expressions. \n",
    "The arguments passed to a rate function can be used to construct rate expressions by operating on them and storing the operations performed upon them to return a new expression to the compiler. \n",
    "For instance, the code below shows how to write a Hill function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c3ae8073-28da-4a54-a9a5-9b0c8619bb32",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Compiling model\n",
      "Starting Simulator\n",
      "Simulation is Over\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 16168 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "A, P = BaseSpecies()\n",
    "\n",
    "Zero >> A [1]\n",
    "A >> A + P [lambda r1: r1/(1 + (10/r1)**4)]\n",
    "\n",
    "S = Simulation(A | P)\n",
    "S.duration = 10\n",
    "S.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2e2e53d-d6e0-493a-845f-61a7cc6ee1f0",
   "metadata": {},
   "source": [
    "Here is another expression example with a meta-species with characteristics: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "2034da00-29b5-42ef-84ba-c951bebb83f6",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Compiling model\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Species\n",
      "B.b1,0\n",
      "B.b2,0\n",
      "B.b3,0\n",
      "P,0\n",
      "\n",
      "Mappings\n",
      "B :\n",
      "B.b1\n",
      "B.b2\n",
      "B.b3\n",
      "P :\n",
      "P\n",
      "\n",
      "Parameters\n",
      "volume,1\n",
      "\n",
      "Reactions\n",
      "reaction_0,{'re': [(1, 'B.b1'), (1, 'P')], 'pr': [(1, 'B.b1'), (2, 'P')], 'kin': '(B.b1*(100-P))'}\n",
      "reaction_1,{'re': [(1, 'B.b2'), (1, 'P')], 'pr': [(1, 'B.b2'), (2, 'P')], 'kin': '(B.b2*(100-P))'}\n",
      "reaction_2,{'re': [(1, 'B.b3'), (1, 'P')], 'pr': [(1, 'B.b3'), (2, 'P')], 'kin': '(B.b3*(100-P))'}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "B, P = BaseSpecies()\n",
    "B.b1, B.b2, B.b3\n",
    "\n",
    "B + P >> B + 2*P [lambda r1, r2: r1*(100-r2)]\n",
    "\n",
    "S = Simulation(B | P)\n",
    "print(S.compile())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a3ff17f-fed4-4c8f-9c86-15d32e3465a9",
   "metadata": {},
   "source": [
    "Units are fully compatible with expressions. \n",
    "MobsPy executes all unit operations written inside the expression and checks if the resulting unit is indeed a reaction rate for the user, yielding a compilation error if they are not. \n",
    "\n",
    "Furthermore, reactants have units. \n",
    "Reactants can either be concentrations, with units of 1/[Volume], or they can represent counts (total amount) and have no units. \n",
    "The user can specify if the reactants are written in terms of concentration or counts using the resulting unit of the expression. \n",
    "When the resulting unit of expression is  1/([Time]*[Volume]), then the reactants are considered to be a concentration. \n",
    "Otherwise, if it is  1/([Time]), then the reactants are considered to represent the total amount.  \n",
    "\n",
    "Below, we show an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "728a8fd4-d6bf-4168-814f-cd8901dbce4c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Compiling model\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Species\n",
      "A_Con,0\n",
      "A_Count,0\n",
      "P,0\n",
      "\n",
      "Mappings\n",
      "A_Con :\n",
      "A_Con\n",
      "A_Count :\n",
      "A_Count\n",
      "P :\n",
      "P\n",
      "\n",
      "Parameters\n",
      "volume,1\n",
      "\n",
      "Reactions\n",
      "reaction_0,{'re': [(1, 'A_Con')], 'pr': [(1, 'A_Con'), (1, 'P')], 'kin': '(((100.00000000000003-(A_Con/volume))/1))*volume'}\n",
      "reaction_1,{'re': [(1, 'A_Count')], 'pr': [(1, 'A_Count'), (1, 'P')], 'kin': '((100-A_Count)/1)'}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "A_Count, A_Con, P = BaseSpecies()\n",
    "\n",
    "A_Count >> A_Count + P [lambda r: (100-r)/u.s]\n",
    "A_Con >> A_Con + P [lambda r: (100/u.l-r)/u.s]\n",
    "\n",
    "S = Simulation(A_Count | A_Con | P)\n",
    "print(S.compile())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b64f12c-8d13-4a83-a21a-8a714fb459f5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
