{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Ch.9: Object-oriented programming\n", "\n", " **Hans Petter Langtangen**, Simula Research Laboratory and University of Oslo, Dept. of Informatics\n", "\n", "Date: **Aug 15, 2015**\n", "\n", "# Inheritance\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "## The chapter title *Object-oriented programming* (OO) may mean two different things\n", "\n", "1. Programming with classes (better: object-*based* programming)\n", "\n", "2. Programming with class hierarchies (class families)\n", "\n", "\n", "\n", "\n", "\n", "## New concept: collect classes in families (hierarchies)\n", "\n", "**What is a class hierarchy?**\n", "\n", " * A family of closely related classes\n", "\n", " * A key concept is *inheritance*: child classes can inherit attributes and methods from parent class(es) - this saves much typing and code duplication\n", "\n", "As usual, we shall learn through examples!\n", "\n", "\n", "\n", "OO is a Norwegian invention by Ole-Johan Dahl and Kristen Nygaard in the 1960s - one of the most important inventions in computer science, because OO is used in all big computer systems today!\n", "\n", "\n", "\n", "## Warning: OO is difficult and takes time to master\n", "\n", " * Let ideas mature with time\n", "\n", " * Study many examples\n", "\n", " * OO is less important in Python than in C++, Java and C#, so the benefits of OO are less obvious in Python\n", "\n", " * Our examples here on OO employ numerical methods for $\\int_a^b f(x)dx$, $f'(x)$, $u'=f(u,t)$ - make sure you understand the simplest of these numerical methods before you study the combination of OO and numerics\n", "\n", " * Our goal: write general, reusable modules with lots of methods for numerical computing of $\\int_a^b f(x)dx$, $f'(x)$, $u'=f(u,t)$\n", "\n", "\n", "\n", "## A class for straight lines\n", "\n", "**Problem:**\n", "\n", "Make a class for evaluating lines $y=c_0 + c_1x$.\n", "\n", "\n", "\n", "**Code:**" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Line:\n", " def __init__(self, c0, c1):\n", " self.c0, self.c1 = c0, c1\n", "\n", " def __call__(self, x):\n", " return self.c0 + self.c1*x\n", "\n", " def table(self, L, R, n):\n", " \"\"\"Return a table with n points for L <= x <= R.\"\"\"\n", " s = ''\n", " for x in linspace(L, R, n):\n", " y = self(x)\n", " s += '%12g %12g\\n' % (x, y)\n", " return s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A class for parabolas\n", "\n", "**Problem:**\n", "\n", "Make a class for evaluating parabolas $y=c_0 + c_1x + c_2x^2$.\n", "\n", "\n", "\n", "**Code:**" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Parabola:\n", " def __init__(self, c0, c1, c2):\n", " self.c0, self.c1, self.c2 = c0, c1, c2\n", "\n", " def __call__(self, x):\n", " return self.c2*x**2 + self.c1*x + self.c0\n", "\n", " def table(self, L, R, n):\n", " \"\"\"Return a table with n points for L <= x <= R.\"\"\"\n", " s = ''\n", " for x in linspace(L, R, n):\n", " y = self(x)\n", " s += '%12g %12g\\n' % (x, y)\n", " return s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Observation:**\n", "\n", "This is almost the same code as class `Line`, except for the things with `c2`\n", "\n", "\n", "\n", "## Class Parabola as a subclass of Line; principles\n", "\n", " * `Parabola` code = `Line` code + a little extra with the $c_2$ term\n", "\n", " * Can we utilize class `Line` code in class `Parabola`?\n", "\n", " * This is what inheritance is about!\n", "\n", "Writing" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Parabola(Line):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "makes `Parabola` inherit all methods and attributes from `Line`, so `Parabola` has attributes `c0` and `c1` and three methods\n", "\n", "\n", "\n", " * `Line` is a *superclass*, `Parabola` is a *subclass* \n", " (parent class, base class; child class, derived class)\n", "\n", " * Class `Parabola` must add code to `Line`'s constructor (an extra `c2` attribute), `__call__` (an extra term), but `table` can be used unaltered\n", "\n", " * The principle is to reuse as much code in `Line` as possible and avoid duplicating code\n", "\n", "\n", "\n", "## Class Parabola as a subclass of Line; code\n", "\n", "A subclass method can call a superclass method in this way:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "superclass_name.method(self, arg1, arg2, ...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Class `Parabola` as a subclass of `Line`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Parabola(Line):\n", " def __init__(self, c0, c1, c2):\n", " Line.__init__(self, c0, c1) # Line stores c0, c1\n", " self.c2 = c2\n", "\n", " def __call__(self, x):\n", " return Line.__call__(self, x) + self.c2*x**2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is gained?\n", "\n", " * Class `Parabola` just adds code to the already existing code in class `Line` - no duplication of storing `c0` and `c1`, and computing $c_0+c_1x$\n", "\n", " * Class `Parabola` also has a `table` method - it is inherited\n", "\n", " * `__init__` and `__call__` are *overridden* or *redefined* in the subclass\n", "\n", "\n", "\n", "## Class Parabola as a subclass of Line; demo" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "p = Parabola(1, -2, 2)\n", "p1 = p(2.5)\n", "print p1\n", "print p.table(0, 1, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Output:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " 8.5\n", " 0 1\n", " 0.5 0.5\n", " 1 1\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "## Exercise 1: Point out the program flow" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Line:\n", " def __init__(self, c0, c1):\n", " self.c0, self.c1 = c0, c1\n", "\n", " def __call__(self, x):\n", " return self.c0 + self.c1*x\n", "\n", " def table(self, L, R, n):\n", " \"\"\"Return a table with n points for L <= x <= R.\"\"\"\n", " s = ''\n", " for x in linspace(L, R, n):\n", " y = self(x)\n", " s += '%12g %12g\\n' % (x, y)\n", " return s\n", "\n", "class Parabola(Line):\n", " def __init__(self, c0, c1, c2):\n", " Line.__init__(self, c0, c1) # Line stores c0, c1\n", " self.c2 = c2\n", "\n", " def __call__(self, x):\n", " return Line.__call__(self, x) + self.c2*x**2\n", "\n", "p = Parabola(1, -2, 2)\n", "print p(2.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "## We can check class type and class relations with `isinstance(obj, type)` and `issubclass(subclassname, superclassname)`" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Line_Parabola import Line, Parabola\n", "l = Line(-1, 1)\n", "isinstance(l, Line)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [], "source": [ "isinstance(l, Parabola)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [], "source": [ "p = Parabola(-1, 0, 10)\n", "isinstance(p, Parabola)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [], "source": [ "isinstance(p, Line)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [], "source": [ "issubclass(Parabola, Line)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [], "source": [ "issubclass(Line, Parabola)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [], "source": [ "p.__class__ == Parabola" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [], "source": [ "p.__class__.__name__ # string version of the class name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Line as a subclass of Parabola\n", "\n", " * Subclasses are often special cases of a superclass\n", "\n", " * A line $c_0+c_1x$ is a special case of a parabola $c_0+c_1x+c_2x^2$\n", "\n", " * Can `Line` be a subclass of `Parabola`?\n", "\n", " * No problem - this is up to the programmer's choice\n", "\n", " * Many will prefer this relation between a line and a parabola\n", "\n", "\n", "\n", "## Code when Line is a subclass of Parabola" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Parabola:\n", " def __init__(self, c0, c1, c2):\n", " self.c0, self.c1, self.c2 = c0, c1, c2\n", "\n", " def __call__(self, x):\n", " return self.c2*x**2 + self.c1*x + self.c0\n", "\n", " def table(self, L, R, n):\n", " \"\"\"Return a table with n points for L <= x <= R.\"\"\"\n", " s = ''\n", " for x in linspace(L, R, n):\n", " y = self(x)\n", " s += '%12g %12g\\n' % (x, y)\n", " return s\n", "\n", "class Line(Parabola):\n", " def __init__(self, c0, c1):\n", " Parabola.__init__(self, c0, c1, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: `__call__` and `table` can be reused in class `Line`!\n", "\n", "\n", "\n", "\n", "\n", "\n", "## Recall the class for numerical differentiation from Ch. 7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "f'(x) \\approx {f(x+h)-f(x)\\over h}\n", "$$" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Derivative:\n", " def __init__(self, f, h=1E-5):\n", " self.f = f\n", " self.h = float(h)\n", "\n", " def __call__(self, x):\n", " f, h = self.f, self.h # make short forms\n", " return (f(x+h) - f(x))/h\n", "\n", "def f(x):\n", " return exp(-x)*cos(tanh(x))\n", "\n", "from math import exp, cos, tanh\n", "dfdx = Derivative(f)\n", "print dfdx(2.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## There are numerous formulas numerical differentiation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{align*}\n", "f'(x) &= \\frac{f(x+h)-f(x)}{h} + {\\cal O}(h)\\\\\n", "f'(x) &= \\frac{f(x)-f(x-h)}{h} + {\\cal O}(h)\\\\\n", "f'(x) &= \\frac{f(x+h)-f(x-h)}{2h} + {\\cal O}(h^2)\\\\\n", "f'(x) &= \\frac{4}{3}\\frac{f(x+h)-f(x-h)}{2h}\n", " -\\frac{1}{3}\\frac{f(x+2h) - f(x-2h)}{4h} + {\\cal O}(h^4)\\\\\n", "f'(x) &= \\frac{3}{2}\\frac{f(x+h)-f(x-h)}{2h}\n", " -\\frac{3}{5}\\frac{f(x+2h) - f(x-2h)}{4h} + \\nonumber\\\\\n", "& \\frac{1}{10}\\frac{f(x+3h) - f(x-3h)}{6h} + {\\cal O}(h^6)\\\\\n", "f'(x) &= \\frac{1}{h}\\left(\n", "-\\frac{1}{6}f(x+2h) + f(x+h) - \\frac{1}{2}f(x) - \\frac{1}{3}f(x-h)\\right)\n", " + {\\cal O}(h^3)\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How can we make a module that offers all these formulas?\n", "\n", "**It's easy:**" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Forward1:\n", " def __init__(self, f, h=1E-5):\n", " self.f = f\n", " self.h = float(h)\n", "\n", " def __call__(self, x):\n", " f, h = self.f, self.h\n", " return (f(x+h) - f(x))/h\n", "\n", "class Backward1:\n", " def __init__(self, f, h=1E-5):\n", " self.f = f\n", " self.h = float(h)\n", "\n", " def __call__(self, x):\n", " f, h = self.f, self.h\n", " return (f(x) - f(x-h))/h\n", "\n", "class Central2:\n", " # same constructor\n", " # put relevant formula in __call__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is the problem with this type of code?\n", "\n", "All the constructors are identical so we duplicate a lot of code.\n", "\n", "\n", "\n", " * A general OO idea: place code common to many classes in a superclass and inherit that code\n", "\n", " * Here: inhert constructor from superclass,\n", " let subclasses for different differentiation formulas implement\n", " their version of `__call__`\n", "\n", "\n", "\n", "## Class hierarchy for numerical differentiation\n", "\n", "**Superclass:**" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Diff:\n", " def __init__(self, f, h=1E-5):\n", " self.f = f\n", " self.h = float(h)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Subclass for simple 1st-order forward formula:**" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Forward1(Diff):\n", " def __call__(self, x):\n", " f, h = self.f, self.h\n", " return (f(x+h) - f(x))/h" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Subclass for 4-th order central formula:**" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Central4(Diff):\n", " def __call__(self, x):\n", " f, h = self.f, self.h\n", " return (4./3)*(f(x+h) - f(x-h)) /(2*h) - \\\n", " (1./3)*(f(x+2*h) - f(x-2*h))/(4*h)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use of the differentiation classes\n", "\n", "Interactive example: $f(x)=\\sin x$, compute $f'(x)$ for $x=\\pi$" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from Diff import *\n", "from math import sin\n", "mycos = Central4(sin)\n", "# compute sin'(pi):\n", "mycos(pi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Central4(sin)` calls inherited constructor in superclass, while `mycos(pi)` calls `__call__` in the subclass `Central4`\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "## Exercise 2: Point out the program flow" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Diff:\n", " def __init__(self, f, h=1E-5):\n", " self.f = f\n", " self.h = float(h)\n", "\n", "class Forward1(Diff):\n", " def __call__(self, x):\n", " f, h = self.f, self.h\n", " return (f(x+h) - f(x))/h\n", "\n", "dfdx = Diff(lambda x: x**2)\n", "print dfdx(0.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "## A flexible main program for numerical differentiation\n", "\n", "Suppose we want to differentiate function expressions from the command line:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Terminal> python df.py 'exp(sin(x))' Central 2 3.1\n", " -1.04155573055\n", " \n", " Terminal> python df.py 'f(x)' difftype difforder x\n", " f'(x)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `eval` and the `Diff` class hierarchy this main program can be realized in a few lines (many lines in C# and Java!):" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "import sys\n", "from Diff import *\n", "from math import *\n", "from scitools.StringFunction import StringFunction\n", "\n", "f = StringFunction(sys.argv[1])\n", "difftype = sys.argv[2]\n", "difforder = sys.argv[3]\n", "classname = difftype + difforder\n", "df = eval(classname + '(f)')\n", "x = float(sys.argv[4])\n", "print df(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Investigating numerical approximation errors\n", "\n", " * We can empirically investigate the accuracy of our family of 6 numerical differentiation formulas\n", "\n", " * Sample function: $f(x)=\\exp{(-10x)}$\n", "\n", " * See the book for a little program that computes the errors:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [], "source": [ ". h Forward1 Central2 Central4\n", "6.25E-02 -2.56418286E+00 6.63876231E-01 -5.32825724E-02\n", "3.12E-02 -1.41170013E+00 1.63556996E-01 -3.21608292E-03\n", "1.56E-02 -7.42100948E-01 4.07398036E-02 -1.99260429E-04\n", "7.81E-03 -3.80648092E-01 1.01756309E-02 -1.24266603E-05\n", "3.91E-03 -1.92794011E-01 2.54332554E-03 -7.76243120E-07\n", "1.95E-03 -9.70235594E-02 6.35795004E-04 -4.85085874E-08" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Observations:\n", "\n", " * Halving $h$ from row to row reduces the errors by a factor of 2, 4 and 16, i.e, the errors go like $h$, $h^2$, and $h^4$\n", "\n", " * `Central4` has really superior accuracy compared with `Forward1`\n", "\n", "\n", "\n", "## Alternative implementations (in the book)\n", "\n", " * *Pure Python functions* \n", " downside: more arguments to transfer, cannot apply formulas twice to get 2nd-order derivatives etc.\n", "\n", " * *Functional programming*\n", " gives the same flexibility as the OO solution\n", "\n", " * *One class and one common math formula* \n", " applies math notation instead of programming techniques to generalize code\n", "\n", "\n", "\n", "These techniques are beyond scope in the course, but place OO into a bigger perspective. Might better clarify what OO is - for some.\n", "\n", "\n", "\n", "## Formulas for numerical integration\n", "\n", "There are numerous formulas for numerical integration and\n", "all of them can be put into a common notation:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\int_a^b f(x)dx \\approx \\sum_{i=0}^{n-1} w_i f(x_i)\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$w_i$: weights, $x_i$: points (specific to a certain formula)\n", "\n", "\n", "\n", "The Trapezoidal rule has $h=(b-a)/(n-1)$ and" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "x_i = a+ih, \\quad w_0=w_{n-1}={h\\over2},\\ w_i=h\\ (i\\neq 0,n-1)\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Midpoint rule has $h=(b-a)/n$ and" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "x_i = a + {h\\over 2} + ih,\\quad w_i=h\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## More formulas\n", "\n", "Simpson's rule has" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{align*}\n", "x_i &= a+ih,\\quad h={b-a\\over n-1}\\\\\n", "w_0 &=w_{n-1}={h\\over6}\\\\\n", "w_i &= {h\\over3}\\hbox{ for }i\\hbox{ even},\\quad w_i={2h\\over3}\\hbox{ for }i\\hbox{ odd}\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other rules have more complicated formulas for $w_i$ and $x_i$\n", "\n", "\n", "\n", "## Why should these formulas be implemented in a class hierarchy?\n", "\n", " * A numerical integration formula can be implemented as a class: $a$, $b$ and $n$ are attributes and an `integrate` method evaluates the formula\n", "\n", " * All such classes are quite similar: the evaluation of $\\sum_jw_jf(x_j)$ is the same, only the definition of the points and weights differ among the classes\n", "\n", " * Recall: code duplication is a bad thing!\n", "\n", " * The general OO idea: place code common to many classes in a superclass and inherit that code\n", "\n", " * Here we put $\\sum_jw_jf(x_j)$ in a superclass (method `integrate`)\n", "\n", " * Subclasses extend the superclass with code specific to a math formula, i.e., $w_i$ and $x_i$ in a class method `construct_rule`\n", "\n", "\n", "\n", "## The superclass for integration" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Integrator:\n", " def __init__(self, a, b, n):\n", " self.a, self.b, self.n = a, b, n\n", " self.points, self.weights = self.construct_method()\n", "\n", " def construct_method(self):\n", " raise NotImplementedError('no rule in class %s' % \\\n", " self.__class__.__name__)\n", "\n", " def integrate(self, f):\n", " s = 0\n", " for i in range(len(self.weights)):\n", " s += self.weights[i]*f(self.points[i])\n", " return s\n", "\n", " def vectorized_integrate(self, f):\n", " # f must be vectorized for this to work\n", " return dot(self.weights, f(self.points))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A subclass: the Trapezoidal rule" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Trapezoidal(Integrator):\n", " def construct_method(self):\n", " h = (self.b - self.a)/float(self.n - 1)\n", " x = linspace(self.a, self.b, self.n)\n", " w = zeros(len(x))\n", " w[1:-1] += h\n", " w[0] = h/2; w[-1] = h/2\n", " return x, w" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Another subclass: Simpson's rule\n", "\n", " * Simpson's rule is more tricky to implement because of different formulas for odd and even points\n", "\n", " * Don't bother with the details of $w_i$ and $x_i$ in Simpson's rule now - focus on the class design!" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [], "source": [ "class Simpson(Integrator):\n", "\n", " def construct_method(self):\n", " if self.n % 2 != 1:\n", " print 'n=%d must be odd, 1 is added' % self.n\n", " self.n += 1\n", "\n", "\n",
" return x, w"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## About the program flow\n",
"\n",
"Let us integrate $\\int_0^2 x^2dx$ using 101 points:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def f(x):\n",
" return x*x\n",
"\n",
"method = Simpson(0, 2, 101)\n",
"print method.integrate(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Important:\n",
"\n",
" * `method = Simpson(...)`: this invokes the superclass constructor, which calls `construct_method` in class `Simpson`\n",
"\n",
" * `method.integrate(f)` invokes the inherited `integrate` method, defined in class `Integrator`\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"## Exercise 3: Point out the program flow"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
" class Integrator:\n",
" def __init__(self, a, b, n):\n",
" self.a, self.b, self.n = a, b, n\n",
" self.points, self.weights = self.construct_method()\n",
"\n",
" def construct_method(self):\n",
" raise NotImplementedError('no rule in class %s' % \\\n",
" self.__class__.__name__)\n",
"\n",
" def integrate(self, f):\n",
" s = 0\n",
" for i in range(len(self.weights)):\n",
" s += self.weights[i]*f(self.points[i])\n",
" return s\n",
"\n",
"class Trapezoidal(Integrator):\n",
" def construct_method(self):\n",
" h = (self.b - self.a)/float(self.n - 1)\n",
" x = linspace(self.a, self.b, self.n)\n",
" w = zeros(len(x))\n",
" w[1:-1] += h\n",
" w[0] = h/2; w[-1] = h/2\n",
" return x, w\n",
"\n",
"def f(x):\n",
" return x*x\n",
"\n",
"method = Trapezoidal(0, 2, 101)\n",
"print method.integrate(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"\n",
"## Applications of the family of integration classes\n",
"\n",
"We can empirically test out the accuracy of different integration methods `Midpoint`, `Trapezoidal`, `Simpson`, `GaussLegendre2`, ... applied to, e.g.,"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$$\n",
"\\int\\limits_0^1 \\left(1 + {1\\over m}\\right)t^{1\\over m} dt= 1\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* This integral is \"difficult\" numerically for $m>1$.\n",
"\n",
" * Key problem: the error in numerical integration formulas is of the form $Cn^{-r}$, mathematical theory can predict $r$ (the \"order\"), but we can estimate $r$ empirically too\n",
"\n",
" * See the book for computational details\n",
"\n",
" * Here we focus on the conclusions\n",
"\n",
"\n",
"\n",
"## Convergence rates for $m < 1$ (easy case)\n",
"\n",
"Simpson and Gauss-Legendre reduce the error faster than Midpoint and Trapezoidal (plot has ln(error) versus $\\ln n$)\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"## Convergence rates for $m>1$ (problematic case)\n",
"\n",
"\n",
"Simpson and Gauss-Legendre, which are theoretically \"smarter\" than Midpoint and Trapezoidal do not show superior behavior!\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"## Summary of object-orientation principles\n",
"\n",
" * A subclass inherits everything from the superclass\n",
"\n",
" * When to use a subclass/superclass?\n",
"\n",
" * if code common to several classes can be placed in a superclass\n",
"\n",
" * if the problem has a natural child-parent concept\n",
"\n",
"\n",
" * The program flow jumps between super- and sub-classes\n",
"\n",
" * It takes time to master *when* and *how* to use OO\n",
"\n",
" * Study examples!\n",
"\n",
"\n",
"\n",
"## Recall the class hierarchy for differentiation\n",
"\n",
"**Mathematical principles:**\n",
"\n",
" Collection of difference formulas for $f'(x)$. For example,"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$$\n",
"f'(x) \\approx {f(x+h)-f(x-h)\\over 2h}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Superclass `Diff` contains common code (constructor), subclasses implement various difference formulas.\n",
"\n",
"\n",
"\n",
"**Implementation example (superclass and one subclass).**"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class Diff:\n",
" def __init__(self, f, h=1E-5):\n",
" self.f = f\n",
" self.h = float(h)\n",
"\n",
"class Central2(Diff):\n",
" def __call__(self, x):\n",
" f, h = self.f, self.h\n",
" return (f(x+h) - f(x-h))/(2*h)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Recall the class hierarchy for integration (1)\n",
"\n",
"**Mathematical principles:**\n",
"\n",
"General integration formula for numerical integration:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$$\n",
"\\int_a^b f(x)dx \\approx \\sum_{j=0}^{n-1} w_if(x_i)\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Superclass `Integrator` contains common code (constructor, $\\sum_j w_if(x_i)$), subclasses implement definition of $w_i$ and $x_i$.\n",
"\n",
"\n",
"\n",
"## Recall the class hierarchy for integration (2)\n",
"\n",
"**Implementation example (superclass and one subclass):**"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
" class Integrator:\n",
" def __init__(self, a, b, n):\n",
" self.a, self.b, self.n = a, b, n\n",
" self.points, self.weights = self.construct_method()\n",
"\n",
" def integrate(self, f):\n",
" s = 0\n",
" for i in range(len(self.weights)):\n",
" s += self.weights[i]*f(self.points[i])\n",
" return s\n",
"\n",
" class Trapezoidal(Integrator):\n",
" def construct_method(self):\n",
" x = linspace(self.a, self.b, self.n)\n",
" h = (self.b - self.a)/float(self.n - 1)\n",
" w = zeros(len(x)) + h\n",
" w[0] /= 2; w[-1] /= 2 # adjust end weights\n",
" return x, w"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A summarizing example: Generalized reading of input data\n",
"\n",
"**Write a table of $x\\in [a,b]$ and $f(x)$ to file:**"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"outfile = open(filename, 'w')\n",
"from numpy import linspace\n",
"for x in linspace(a, b, n):\n",
" outfile.write('%12g %12g\\n' % (x, f(x)))\n",
"outfile.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**We want flexible input:**\n",
"\n",
"Read `a`, `b`, `n`, `filename` and a formula for `f` from...\n",
"\n",
" * the command line\n",
"\n",
" * interactive commands like `a=0`, `b=2`, `filename=mydat.dat`\n",
"\n",
" * questions and answers in the terminal window\n",
"\n",
" * a graphical user interface\n",
"\n",
" * a file of the form"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"a = 0\n",
"b = 2\n",
"filename = mydat.dat"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Graphical user interface\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"## First we write the application code\n",
"\n",
"**Desired usage:**"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from ReadInput import *\n",
"\n",
"# define all input parameters as name-value pairs in a dict:\n",
"p = dict(formula='x+1', a=0, b=1, n=2, filename='tmp.dat')\n",
"\n",
"# read from some input medium:\n",
"inp = ReadCommandLine(p)\n",
"# or\n",
"inp = PromptUser(p) # questions in the terminal window\n",
"# or\n",
"inp = ReadInputFile(p) # read file or interactive commands\n",
"# or\n",
"inp = GUI(p) # read from a GUI\n",
"\n",
"# load input data into separate variables (alphabetic order)\n",
"a, b, filename, formula, n = inp.get_all()\n",
"\n",
"# go!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## About the implementation\n",
"\n",
" * A superclass `ReadInput` stores the dict and provides methods for getting input into program variables (`get`, `get_all`)\n",
"\n",
" * Subclasses read from different input sources\n",
"\n",
" * `ReadCommandLine`, `PromptUser`, `ReadInputFile`, `GUI`\n",
"\n",
" * See the book or `ReadInput.py` for implementation details\n",
"\n",
" * For now the ideas and principles are more important than code details!"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 0
}