{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with Symbolic Expressions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 0.1 Finding an exact derivative with a computer algebra system"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 0.2 Doing symbolic algebra in Python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from math import sin\n",
    "def f(x):\n",
    "    return (3*x**2 + x) * sin(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1 Modeling algebraic expressions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.1 Breaking an expression into pieces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.2 Building an expression tree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.3 Translating the expression tree to Python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Power():\n",
    "    def __init__(self,base,exponent):\n",
    "        self.base = base\n",
    "        self.exponent = exponent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number():\n",
    "    def __init__(self,number):\n",
    "        self.number = number\n",
    "\n",
    "class Variable():\n",
    "    def __init__(self,symbol):\n",
    "        self.symbol = symbol"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This represents $x^2$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Power at 0x2d030c86710>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Power(Variable(\"x\"),Number(2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Product():\n",
    "    def __init__(self, exp1, exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This represents $3x^2$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Product at 0x2d030c86dd8>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Product(Number(3),Power(Variable(\"x\"),Number(2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Sum():\n",
    "    def __init__(self, *exps): #<1>\n",
    "        self.exps = exps\n",
    "\n",
    "class Function(): #<2>\n",
    "    def __init__(self,name):\n",
    "        self.name = name\n",
    "\n",
    "class Apply(): #<3>\n",
    "    def __init__(self,function,argument):\n",
    "        self.function = function\n",
    "        self.argument = argument\n",
    "\n",
    "f_expression = Product( #<4>\n",
    "                Sum(\n",
    "                    Product(\n",
    "                        Number(3),\n",
    "                        Power(\n",
    "                            Variable(\"x\"),\n",
    "                            Number(2))), \n",
    "                    Variable(\"x\")), \n",
    "                Apply(\n",
    "                    Function(\"sin\"),\n",
    "                    Variable(\"x\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This represents $\\cos(x^3 + -5)$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Apply at 0x2d030ca1390>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Apply(Function(\"cos\"),Sum(Power(Variable(\"x\"),Number(\"3\")), Number(-5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.4 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Draw the expression $\\ln(y^z)$ as a tree built out of elements and combinators from this section."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Translate the expression from the previous exercise to Python code.  Write it both as a Python function and as a data structure built from elements and combinators."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution:** Here's the ordinary Python function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from math import log\n",
    "def f(y,z):\n",
    "    return log(y**z)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the data structure:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Apply at 0x2d030ca17f0>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Apply(Function(\"ln\"), Power(Variable(\"y\"), Variable(\"z\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Implement a “Quotient” combinator representing one expression divided by another.  How do you represent the following expression? $$\\frac{a+b}{2}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Quotient():\n",
    "    def __init__(self,numerator,denominator):\n",
    "        self.numerator = numerator\n",
    "        self.denominator = denominator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the representation of $(a+b)/2$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Quotient at 0x2d030ca1a20>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Quotient(Sum(Variable(\"a\"),Variable(\"b\")),Number(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Implement a `Difference` combinator representing one expression subtracted from another.  How can you represent the expression $b^2 - 4ac$?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Difference():\n",
    "    def __init__(self,exp1,exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$b^2 - 4ac$ is then represented by:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Difference at 0x2d030ca8240>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Difference(\n",
    "    Power(Variable('b'),Number(2)),\n",
    "    Product(Number(4),Product(Variable('a'), Variable('c'))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Implement a `Negative` combinator, representing the negation of an expression.  For example, the negation of $x^2 + y$  is $-(x^2 + y)$.  Represent the latter expression in code using your new combinator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Negative():\n",
    "    def __init__(self,exp):\n",
    "        self.exp = exp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$-(x^2 + y)$ is represented by:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Negative at 0x2d030ca83c8>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Negative(Sum(Power(Variable(\"x\"),Number(2)),Variable(\"y\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Add a Function called `\"sqrt\"` representing a square root, and use it to encode the following formula:\n",
    "\n",
    "$$\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = Variable('a')\n",
    "B = Variable('b')\n",
    "C = Variable('c')\n",
    "Sqrt = Function('sqrt')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Quotient at 0x2d030ca8d30>"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Quotient(\n",
    "    Sum(\n",
    "        Negative(B),\n",
    "        Apply(\n",
    "            Sqrt, \n",
    "            Difference(\n",
    "                Power(B,Number(2)),\n",
    "                Product(Number(4), Product(A,C))))),\n",
    "    Product(Number(2), A))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Mini-project:** Create an abstract base class called Expression and make all of the elements and combinators inherit from it.  For instance, class Variable() should become class Variable(Expression).  Then, overload the Python arithmetic operations +, -, *, and / so they produce Expression objects.  For instance, the code 2 * Variable(“x”) + 3 should yield: Sum(Product(Number(2), Variable(“x”)), Number(3))."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution:** see \"expressions.py\" file, and section 2.2 and beyond below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2 Putting a symbolic expression to work"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.1 Finding all the variables in an expression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def distinct_variables(exp):\n",
    "    if isinstance(exp, Variable):\n",
    "        return set(exp.symbol)\n",
    "    elif isinstance(exp, Number):\n",
    "        return set()\n",
    "    elif isinstance(exp, Sum):\n",
    "        return set().union(*[distinct_variables(exp) for exp in exp.exps])\n",
    "    elif isinstance(exp, Product):\n",
    "        return distinct_variables(exp.exp1).union(distinct_variables(exp.exp2))\n",
    "    elif isinstance(exp, Power):\n",
    "        return distinct_variables(exp.base).union(distinct_variables(exp.exponent))\n",
    "    elif isinstance(exp, Apply):\n",
    "        return distinct_variables(exp.argument)\n",
    "    else:\n",
    "        raise TypeError(\"Not a valid expression.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'z'}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distinct_variables(Variable(\"z\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "set()"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distinct_variables(Number(3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'x'}"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distinct_variables(f_expression)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.2 Evaluating an expression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "from abc import ABC, abstractmethod\n",
    "\n",
    "class Expression(ABC):\n",
    "    @abstractmethod\n",
    "    def evaluate(self, **bindings):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: we are redefining these classes now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Number(Expression):\n",
    "    def __init__(self,number):\n",
    "        self.number = number\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.number\n",
    "    \n",
    "class Variable(Expression):\n",
    "    def __init__(self,symbol):\n",
    "        self.symbol = symbol\n",
    "    def evaluate(self, **bindings):\n",
    "        try:\n",
    "            return bindings[self.symbol]\n",
    "        except:\n",
    "            raise KeyError(\"Variable '{}' is not bound.\".format(self.symbol))\n",
    "            \n",
    "class Product(Expression):\n",
    "    def __init__(self, exp1, exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.exp1.evaluate(**bindings) * self.exp2.evaluate(**bindings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Product(Variable(\"x\"), Variable(\"y\")).evaluate(x=2,y=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "from math import sin, cos, log\n",
    "\n",
    "_function_bindings = {\n",
    "    \"sin\": math.sin,\n",
    "    \"cos\": math.cos,\n",
    "    \"ln\": math.log\n",
    "}\n",
    "\n",
    "class Apply(Expression):\n",
    "    def __init__(self,function,argument):\n",
    "        self.function = function\n",
    "        self.argument = argument\n",
    "    def evaluate(self, **bindings):\n",
    "        return _function_bindings[self.function.name](self.argument.evaluate(**bindings))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From the text: *... Similarly, we can add an “evaluate” method to the Sum, Power, Difference, or Quotient combinators....*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Sum(Expression):\n",
    "    def __init__(self, *exps):\n",
    "        self.exps = exps\n",
    "    def evaluate(self, **bindings):\n",
    "        return sum([exp.evaluate(**bindings) for exp in self.exps])\n",
    "    \n",
    "class Power(Expression):\n",
    "    def __init__(self,base,exponent):\n",
    "        self.base = base\n",
    "        self.exponent = exponent\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.base.evaluate(**bindings) ** self.exponent.evaluate(**bindings)\n",
    "    \n",
    "class Difference(Expression):\n",
    "    def __init__(self,exp1,exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.exp1.evaluate(**bindings) - self.exp2.evaluate(**bindings)\n",
    "    \n",
    "class Quotient(Expression):\n",
    "    def __init__(self,numerator,denominator):\n",
    "        self.numerator = numerator\n",
    "        self.denominator = denominator\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.numerator.evaluate(**bindings) / self.denominator.evaluate(**bindings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Redefine `f_expression` in light of the new class definitions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "f_expression = Product( #<4>\n",
    "                Sum(\n",
    "                    Product(\n",
    "                        Number(3),\n",
    "                        Power(\n",
    "                            Variable(\"x\"),\n",
    "                            Number(2))), \n",
    "                    Variable(\"x\")), \n",
    "                Apply(\n",
    "                    Function(\"sin\"),\n",
    "                    Variable(\"x\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-76.71394197305108"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f_expression.evaluate(x=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-76.71394197305108"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from math import sin\n",
    "def f(x):\n",
    "    return (3*x**2 + x) * sin(x)\n",
    "\n",
    "f(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.3 Expanding an expression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Expression(ABC):\n",
    "    @abstractmethod\n",
    "    def evaluate(self, **bindings):\n",
    "        pass\n",
    "    @abstractmethod\n",
    "    def expand(self):\n",
    "        pass\n",
    "    \n",
    "    # Printing expressions legibly in REPL (See first mini project in 2.4)\n",
    "    @abstractmethod\n",
    "    def display(self):\n",
    "        pass\n",
    "    def __repr__(self):\n",
    "        return self.display()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Sum(Expression):\n",
    "    def __init__(self, *exps):\n",
    "        self.exps = exps\n",
    "    def evaluate(self, **bindings):\n",
    "        return sum([exp.evaluate(**bindings) for exp in self.exps])\n",
    "    def expand(self):\n",
    "        return Sum(*[exp.expand() for exp in self.exps])\n",
    "    def display(self):\n",
    "        return \"Sum({})\".format(\",\".join([e.display() for e in self.exps]))\n",
    "    \n",
    "class Product(Expression):\n",
    "    def __init__(self, exp1, exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.exp1.evaluate(**bindings) * self.exp2.evaluate(**bindings)\n",
    "    def expand(self):\n",
    "        expanded1 = self.exp1.expand()\n",
    "        expanded2 = self.exp2.expand()\n",
    "        if isinstance(expanded1, Sum):\n",
    "            return Sum(*[Product(e,expanded2).expand() for e in expanded1.exps])\n",
    "        elif isinstance(expanded2, Sum):\n",
    "            return Sum(*[Product(expanded1,e) for e in expanded2.exps])\n",
    "        else:\n",
    "            return Product(expanded1,expanded2)\n",
    "    def display(self):\n",
    "        return \"Product({},{})\".format(self.exp1.display(),self.exp2.display())\n",
    "        \n",
    "class Difference(Expression):\n",
    "    def __init__(self,exp1,exp2):\n",
    "        self.exp1 = exp1\n",
    "        self.exp2 = exp2\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.exp1.evaluate(**bindings) - self.exp2.evaluate(**bindings)\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Difference({},{})\".format(self.exp1.display(), self.exp2.display())\n",
    "    \n",
    "class Quotient(Expression):\n",
    "    def __init__(self,numerator,denominator):\n",
    "        self.numerator = numerator\n",
    "        self.denominator = denominator\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.numerator.evaluate(**bindings) / self.denominator.evaluate(**bindings)\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Quotient({},{})\".format(self.numerator.display(),self.denominator.display())\n",
    "    \n",
    "class Negative(Expression):\n",
    "    def __init__(self,exp):\n",
    "        self.exp = exp\n",
    "    def evaluate(self, **bindings):\n",
    "        return - self.exp.evaluate(**bindings)\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Negative({})\".format(self.exp.display())\n",
    "    \n",
    "class Number(Expression):\n",
    "    def __init__(self,number):\n",
    "        self.number = number\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.number\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Number({})\".format(self.number)\n",
    "    \n",
    "class Power(Expression):\n",
    "    def __init__(self,base,exponent):\n",
    "        self.base = base\n",
    "        self.exponent = exponent\n",
    "    def evaluate(self, **bindings):\n",
    "        return self.base.evaluate(**bindings) ** self.exponent.evaluate(**bindings)\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Power({},{})\".format(self.base.display(),self.exponent.display())\n",
    "    \n",
    "class Variable(Expression):\n",
    "    def __init__(self,symbol):\n",
    "        self.symbol = symbol\n",
    "    def evaluate(self, **bindings):\n",
    "        return bindings[self.symbol]\n",
    "    def expand(self):\n",
    "        return self\n",
    "    def display(self):\n",
    "        return \"Variable(\\\"{}\\\")\".format(self.symbol)\n",
    "    \n",
    "class Function():\n",
    "    def __init__(self,name,make_latex=None):\n",
    "        self.name = name\n",
    "        self.make_latex = make_latex\n",
    "    def latex(self,arg_latex):\n",
    "        if self.make_latex:\n",
    "            return self.make_latex(arg_latex)\n",
    "        else:\n",
    "            return \" \\\\operatorname{{ {} }} \\\\left( {} \\\\right)\".format(self.name, arg_latex)\n",
    "  \n",
    "class Apply(Expression):\n",
    "    def __init__(self,function,argument):\n",
    "        self.function = function\n",
    "        self.argument = argument\n",
    "    def evaluate(self, **bindings):\n",
    "        return _function_bindings[self.function.name](self.argument.evaluate(**bindings))\n",
    "    def expand(self):\n",
    "        return Apply(self.function, self.argument.expand())\n",
    "    def display(self):\n",
    "        return \"Apply(Function(\\\"{}\\\"),{})\".format(self.function.name, self.argument.display())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Product(Sum(Variable(\"a\"),Variable(\"b\")),Sum(Variable(\"y\"),Variable(\"z\")))"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Y = Variable('y')\n",
    "Z = Variable('z')\n",
    "A = Variable('a')\n",
    "B = Variable('b')\n",
    "Product(Sum(A,B),Sum(Y,Z))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Sum(Sum(Product(Variable(\"a\"),Variable(\"y\")),Product(Variable(\"a\"),Variable(\"z\"))),Sum(Product(Variable(\"b\"),Variable(\"y\")),Product(Variable(\"b\"),Variable(\"z\"))))"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Product(Sum(A,B),Sum(Y,Z)).expand()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "f_expression = Product( #<4>\n",
    "                Sum(\n",
    "                    Product(\n",
    "                        Number(3),\n",
    "                        Power(\n",
    "                            Variable(\"x\"),\n",
    "                            Number(2))), \n",
    "                    Variable(\"x\")), \n",
    "                Apply(\n",
    "                    Function(\"sin\"),\n",
    "                    Variable(\"x\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Sum(Product(Product(Number(3),Power(Variable(\"x\"),Number(2))),Apply(Function(\"sin\"),Variable(\"x\"))),Product(Variable(\"x\"),Apply(Function(\"sin\"),Variable(\"x\"))))"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f_expression.expand()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.4 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function `contains(expression, variable)` which checks whether the given expression contains any occurence of the specified variable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "def contains(exp, var):\n",
    "    if isinstance(exp, Variable):\n",
    "        return exp.symbol == var.symbol\n",
    "    elif isinstance(exp, Number):\n",
    "        return False\n",
    "    elif isinstance(exp, Sum):\n",
    "        return any([contains(e,var) for e in exp.exps])\n",
    "    elif isinstance(exp, Product):\n",
    "        return contains(exp.exp1,var) or contains(exp.exp2,var)\n",
    "    elif isinstance(exp, Power):\n",
    "        return contains(exp.base, var) or contains(exp.exponent, var)\n",
    "    elif isinstance(exp, Apply):\n",
    "        return contains(exp.argument, var)\n",
    "    else:\n",
    "        raise TypeError(\"Not a valid expression.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a “distinct_functions” function which takes an expression as an argument and returns the distinct, named functions like “sin” or “ln” that appear in the expression."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def distinct_functions(exp):\n",
    "    if isinstance(exp, Variable):\n",
    "        return set()\n",
    "    elif isinstance(exp, Number):\n",
    "        return set()\n",
    "    elif isinstance(exp, Sum):\n",
    "        return set().union(*[distinct_functions(exp) for exp in exp.exps])\n",
    "    elif isinstance(exp, Product):\n",
    "        return distinct_functions(exp.exp1).union(distinct_functions(exp.exp2))\n",
    "    elif isinstance(exp, Power):\n",
    "        return distinct_functions(exp.base).union(distinct_functions(exp.exponent))\n",
    "    elif isinstance(exp, Apply):\n",
    "        return set([exp.function.name]).union(distinct_functions(exp.argument))\n",
    "    else:\n",
    "        raise TypeError(\"Not a valid expression.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function contains_sum which takes an expression and returns True if it contains a Sum and returns False otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "def contains_sum(exp):\n",
    "    if isinstance(exp, Variable):\n",
    "        return False\n",
    "    elif isinstance(exp, Number):\n",
    "        return False\n",
    "    elif isinstance(exp, Sum):\n",
    "        return True\n",
    "    elif isinstance(exp, Product):\n",
    "        return contains_sum(exp.exp1) or contains_sum(exp.exp2)\n",
    "    elif isinstance(exp, Power):\n",
    "        return contains_sum(exp.base) or contains_sum(exp.exponent)\n",
    "    elif isinstance(exp, Apply):\n",
    "        return contains_sum(exp.argument)\n",
    "    else:\n",
    "        raise TypeError(\"Not a valid expression.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE:** For the rest of the mini-projects, consult \"expressions.py\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3 Finding the derivative of a function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For the rest of the notebook, I'll use the complete implementations from `expressions.py` so I don't have to re-implement every time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "from expressions import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$$x ^ { 2 } \\operatorname{ sin } \\left( y \\right)$$"
      ],
      "text/plain": [
       "Product(Power(Variable(\"x\"),Number(2)),Apply(Function(\"sin\"),Variable(\"y\")))"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Product(Power(Variable(\"x\"),Number(2)),Apply(Function(\"sin\"),Variable(\"y\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.1 Derivatives of powers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.2 Derivatives of transformed functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.3 Derivatives of some special functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.4 Derivatives of products and compositions\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.5 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4 Taking derivatives automatically"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1 Implementing a derivative method for expressions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$$1 + 0 + 0$$"
      ],
      "text/plain": [
       "Sum(Number(1),Number(0),Number(0))"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Sum(Variable(\"x\"),Variable(\"c\"),Number(1)).derivative(Variable(\"x\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2 Implementing the product rule and chain rule"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$$c\\cdot 1$$"
      ],
      "text/plain": [
       "Product(Variable(\"c\"),Number(1))"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Product(Variable(\"c\"),Variable(\"x\")).derivative(Variable(\"x\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$$1\\cdot 2x ^ { 1 } \\operatorname{ cos } \\left( x ^ { 2 } \\right)$$"
      ],
      "text/plain": [
       "Product(Product(Number(1),Product(Number(2),Power(Variable(\"x\"),Number(1)))),Apply(Function(\"cos\"),Power(Variable(\"x\"),Number(2))))"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Apply(Function(\"sin\"),Power(Variable(\"x\"),Number(2))).derivative(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.3 Implementing the power rule"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "f_expression = Product( #<4>\n",
    "                Sum(\n",
    "                    Product(\n",
    "                        Number(3),\n",
    "                        Power(\n",
    "                            Variable(\"x\"),\n",
    "                            Number(2))), \n",
    "                    Variable(\"x\")), \n",
    "                Apply(\n",
    "                    Function(\"sin\"),\n",
    "                    Variable(\"x\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$$\\left( 3\\cdot 1\\cdot 2x ^ { 1 } + 1 \\right) \\operatorname{ sin } \\left( x \\right) + \\left( 3x ^ { 2 } + x \\right)\\cdot 1 \\operatorname{ cos } \\left( x \\right)$$"
      ],
      "text/plain": [
       "Sum(Product(Sum(Product(Number(3),Product(Number(1),Product(Number(2),Power(Variable(\"x\"),Number(1))))),Number(1)),Apply(Function(\"sin\"),Variable(\"x\"))),Product(Sum(Product(Number(3),Power(Variable(\"x\"),Number(2))),Variable(\"x\")),Product(Number(1),Apply(Function(\"cos\"),Variable(\"x\")))))"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f_expression.derivative(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.4 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5 Integrating functions symbolically "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5.1 Integrals as antiderivatives\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5.2 Introducing the SymPy library"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "y*(x + 3)"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sympy import *\n",
    "from sympy.core.core import *\n",
    "Mul(Symbol('y'),Add(3,Symbol('x')))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = Symbol('y')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = Symbol('x')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "y*(x + 3)"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y*(3+x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4*y"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y*(3+x).subs(x,1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2*x"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(x**2).diff(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "x**3"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(3*x**2).integrate(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5.3 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** What is the integral of $f(x) = 0$?  Confirm your answer with SymPy, remembering that SymPy does not automatically include a constant of integration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Integer(0).integrate(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** What is the integral of $x\\cdot \\cos(x)$?  Hint: look at the derivative of $x\\sin(x)$.  Confirm your answer with SymPy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "x*sin(x) + cos(x)"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(x*cos(x)).integrate(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** What is the integral of $x^2$?  Confirm your answer with SymPy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "x**3/3"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(x**2).integrate(x)"
   ]
  }
 ],
 "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.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
