diff --git a/.gitignore b/.gitignore index 75f7e821..5a43ef5b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,24 @@ html/reveal/assets/callgrind.png html/reveal/assets/coverage.png html/reveal/assets/jenkins.png tdd.png +.ipynb_checkpoints +session01/analyzer.py +session01/eight +session01/eight.py +session01/module1/ +session01/pretty.py +combined.ipynb +combined_files/ +notebooks.zip +notes.pdf +notes.tex +session04/greeter.py +session04/map.png +session04/midvals.yaml +session05/anotherfile.py +session05/config.yaml +session05/context.py +session06/fixed.png +session07/datasource2.yaml +session07/datasource3.yaml +session07/example.yaml diff --git a/Makefile b/Makefile index f3df512c..7a28983d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,14 @@ PANDOCARGS=-t revealjs -s -V theme=night --css=http://lab.hakim.se/reveal-js/css --css=$(ROOT)/css/ucl_reveal.css --css=$(ROOT)/site-styles/reveal.css \ --default-image-extension=png --highlight-style=zenburn --mathjax -V revealjs-url=http://lab.hakim.se/reveal-js +NOTEBOOKS=$(filter-out %.v2.ipynb %.nbconvert.ipynb,$(wildcard session*/*.ipynb)) + +HTMLS=$(NOTEBOOKS:.ipynb=.html) + +EXECUTED=$(NOTEBOOKS:.ipynb=.nbconvert.ipynb) + +NBV2=$(NOTEBOOKS:.ipynb=.v2.ipynb) + default: _site %/slides.html: %/*.md Makefile @@ -21,18 +29,37 @@ default: _site dot $< -T png -o $@ %.png: %.uml Makefile - plantuml -p < $< > $@ + java -Djava.awt.headless=true -jar plantuml.jar -p < $< > $@ + +%.html: %.nbconvert.ipynb Makefile jekyll.tpl + ipython nbconvert --to html --template jekyll.tpl --stdout $< > $@ + +%.v2.ipynb: %.nbconvert.ipynb + ipython nbconvert --to notebook --nbformat 2 --stdout $< > $@ + +%.nbconvert.ipynb: %.ipynb + ipython nbconvert --to notebook --ExecutePreprocessor.timeout=120 --execute --stdout $< > $@ -%.html: %.ipynb Makefile jekyll.tpl - ipython nbconvert --to html --template jekyll.tpl --execute --stdout $< > $@ +notes.pdf: combined.ipynb Makefile + ipython nbconvert --to pdf --template latex.tplx $< + mv combined.pdf notes.pdf -%.html: %.ipyhtml Makefile - yamlheader +combined.ipynb: $(EXECUTED) + python nbmerge.py $^ $@ + +notes.tex: combined.ipynb Makefile + ipython nbconvert --to latex --template latex.tplx $< + mv combined.tex notes.tex + +notebooks.zip: ${NBV2} + zip -r notebooks $^ master.zip: Makefile rm -f master.zip wget https://github.com/UCL-RITS/indigo-jekyll/archive/master.zip +ready: indigo $(HTMLS) notes.pdf notebooks.zip + indigo-jekyll-master: Makefile master.zip rm -rf indigo-jekyll-master unzip master.zip @@ -47,16 +74,41 @@ indigo: indigo-jekyll-master Makefile cp -r indigo-jekyll-master/indigo/favicon* . touch indigo -_site: indigo \ - session01/01data.html session01/03control.html session01/04functions.html \ - session01/05modules.html session01/00pythons.html session01/02types.html \ - session01/06exercise.html +plantuml.jar: + wget http://sourceforge.net/projects/plantuml/files/plantuml.jar/download -O plantuml.jar + +.PHONY: ready + +_site: ready jekyll build +preview: ready + jekyll serve + clean: rm -f session*/generated/*.png rm -rf session*/*.html + rm -f session*/*.pyc rm -f index.html rm -rf _site rm -rf images js css _includes _layouts favicon* master.zip indigo-jekyll-master - + rm -f indigo + rm -f session01/analyzer.py + rm -f session01/eight + rm -f session01/eight.py + rm -rf session01/module1/ + rm -f session01/pretty.py + rm -f session*/*.nbconvert.ipynb + rm -rf session*/*.v2.ipynb + rm -rf combined* + rm -f notes.pdf + rm -f notes.tex + rm -f session04/greeter.py + rm -f session04/map.png + rm -f session05/anotherfile.py + rm -f session05/config.yaml + rm -f session05/context.py + rm -f session06/fixed.png + rm -f session07/datasource*.yaml + rm -f session07/example.yaml + rm -f notebooks.zip diff --git a/all.md b/all.md deleted file mode 100644 index 4afbefcd..00000000 --- a/all.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Research Software Engineering with Python -author: James Hetherington and Matt Clarkson ---- - -\chapter{Introduction} -\tableofcontents -{{d['intro.md']}} -\chapter{Python} -{{d['session01/index.md']}} -{{d['session01/01pythons.md']}} -{{d['session01/02plots.md']}} -{{d['session01/03types.md']}} -{{d['session01/04flow.md']}} -{{d['session01/05functions.md']}} -{{d['session01/06modules.md']}} -{{d['session01/07exercise.md']}} -\chapter{Version Control} -{{d['session02/index.md']}} -{{d['session02/00git.md']}} -{{d['session02/02licensing.md']}} -{{d['session02/03issues.md']}} -{{d['session02/04exercises.md']}} -\chapter{Testing} -{{d['session03/index.md']}} -{{d['session03/01primer.md']}} -{{d['session03/02HowToTest.md']}} -{{d['session03/03Frameworks.md']}} -{{d['session03/04floatingPoints.md']}} -{{d['session03/05nose_exercise.md']}} -{{d['session03/06mocking.md']}} -{{d['session03/07debugging.md']}} -{{d['session03/08Methodology.md']}} -{{d['session03/09otherTools.md']}} -{{d['session03/10MonteCarlo.md']}} -\chapter{Packaging} -{{d['session04/index.md']}} -{{d['session04/01libraries.md']}} -{{d['session04/02greengraph.md']}} -{{d['session04/03packaging.md']}} -{{d['session04/05exercises.md']}} -\chapter{Construction} -{{d['session05/index.md']}} -{{d['session05/01CodingConventions.md']}} -{{d['session05/02comments.md']}} -{{d['session05/03documentation.md']}} -{{d['session05/04refactoring.md']}} -{{d['session05/05objects.md']}} -{{d['session05/06exercise.md']}} -\chapter{Design} -{{d['session06/index.md']}} -{{d['session06/01design.md']}} -{{d['session06/02patterns.md']}} -{{d['session06/03process.md']}} -{{d['session06/04exercises.md']}} -\chapter{DRY Programming Tricks} -{{d['session07/index.md']}} -{{d['session07/01functional.md']}} -{{d['session07/02iterators.md']}} -{{d['session07/03exceptions.md']}} -{{d['session07/04context.md']}} -{{d['session07/05meta.md']}} -{{d['session07/06operator.md']}} -{{d['session07/07excercise.md']}} -\chapter{Performance programming}} -This lecture is not yet available in PDF form. -Please refer to the online notes. -\chapter{Further Git} -{{d['session09/index.md']}} -{{d['session09/01advanced.md']}} -\chapter{Recap} -{{d['session10/index.md']}} diff --git a/dexy.yaml b/dexy.yaml deleted file mode 100644 index ebdb53ae..00000000 --- a/dexy.yaml +++ /dev/null @@ -1,138 +0,0 @@ -.md|yamlargs|hd|jinja|resub|pandoc: - - inputs - - except: "(LICENSE|README|slides|notebook|all|CITATION)" - - hd: - header: macros/header.jinja - - resub: - expressions: - - ['session../python/','python/'] - - ['session../figures/','figures/'] - - pandoc: - args: --default-image-extension=png - - -"**/slides.md|jinja|hd|jinja|resub|pandoc|-reveal|resub|h": - - inputs - - hd: - header: macros/header.jinja - - resub: - expressions: - - ['session../python/','python/'] - - ['session../figures/','figures/'] - - ['{{root}}',''] - - ['^##','#'] - - output-name: "{dirname}/slides.html" - - pandoc: - args: -t revealjs -V theme=night - --css=http://lab.hakim.se/reveal-js/css/theme/night.css - --css={{root}}/css/ucl_reveal.css - --css={{root}}/css/slidetheme.css - --default-image-extension=png - --highlight-style=zenburn - --mathjax - -V revealjs-url=http://lab.hakim.se/reveal-js - - -"**/notebook.md|jinja|hd|jinja|resub|mdjup": - - inputs - - mdjup: - python_only: True - - hd: - header: macros/header.jinja - - output-name: "{dirname}/python/{dirname}.ipynb" - - resub: - expressions: - - ['session../python/','../python/'] - - ['session../figures/','../figures/'] - -all.md|jinja|hd|jinja|pandoc|p: - - inputs - - output-name: notes.pdf - - hd: - header: macros/header.jinja - - pandoc: - args: - -V documentclass=book - -V links-as-notes - --default-image-extension=png - -V linkcolor="uclmidgreen" - --number-sections - -H report.latex - -assets: - - .css - - .png - - .svg - - .pdf - - .ipynb - - .js - -pythons: - - .py|idio|pycon: - - except: setup.py|greeter.py|conf.py - - data - - session04/python/greetings/scripts/greet - - .py - - .py|idio|t - - .py: - output: False - - pycon: - timeout: 20 - -bashes: - - .sh|idio|bash: - - .py - - .csv - - except: analyze.sh|sphinx.sh - - .sh|idio|t - -session05/python/sphinx.sh|bash: - - .py - - .rst - - bash: - add-new-files: True - -data: - - .csv - - .yaml - - .yaml|idio|t - -sources: - - .md|yamlargs: - - output: False - - .rst|idio|t - -jinjas: - - .jinja: - - output: False - -sequence_charts: - - .wsd|wsd: - - wsd: - style: napkin - -uml: - - .yuml|yuml - -graphs: - - .dot|dot: - - assets - - - .nto|graphviz: - - assets - - graphviz: - args: -Kneato - -inputs: - - sources - - pythons - - bashes - - assets - - data - - sequence_charts - - graphs - - report.latex - - uml - - jinjas - - session04/python/greetings/scripts/greet - diff --git a/dexyplugin.yaml b/dexyplugin.yaml deleted file mode 100644 index 41682a97..00000000 --- a/dexyplugin.yaml +++ /dev/null @@ -1,11 +0,0 @@ -reporter:supplementary: - supplementary-location: ../../indigo - site_title: 'Research Software Development With Python' - crumbs: - - ['ISD', 'http://ucl.ac.uk/isd'] - - ['OUR SERVICES', 'http://ucl.ac.uk/isd/services'] - - ['RESEARCH IT', 'http://www.ucl.ac.uk/isd/services/research-it/'] - - ['TRAINING', 'http://www.ucl.ac.uk/isd/services/research-it/training'] - - ['RESOURCES', 'http://development.rc.ucl.ac.uk/training'] - include_leftnav: True - default: True diff --git a/jekyll.tpl b/jekyll.tpl index 53e5d1bb..fb220cbf 100644 --- a/jekyll.tpl +++ b/jekyll.tpl @@ -1,7 +1,8 @@ {%- extends 'basic.tpl' -%} {%- block header -%} --- -title: {{resources['metadata']['name']}} +title: {% if 'jekyll' in nb['metadata'] %} {{nb['metadata']['jekyll']['display_name']}} {% endif %} +nblink: True --- {{super()}} {%- endblock header -%} diff --git a/latex.tplx b/latex.tplx new file mode 100644 index 00000000..6878bfe9 --- /dev/null +++ b/latex.tplx @@ -0,0 +1,33 @@ + +% Default to the notebook output style +((* if not cell_style is defined *)) + ((* set cell_style = 'style_ipython.tplx' *)) +((* endif *)) + +% Inherit from the specified cell style. +((* extends cell_style *)) + +((* block title *)) +\title{An introduction to Python Programming for Research} +((* endblock title *)) + +((* block author *)) +\author{James Hetherington} +((* endblock author *)) + +%=============================================================================== +% Latex Book +%=============================================================================== + +((* block predoc *)) + ((( super() ))) + ((* block tableofcontents *))\tableofcontents((* endblock tableofcontents *)) +((* endblock predoc *)) + +((* block docclass *)) +\documentclass{report} +((* endblock docclass *)) + +((* block markdowncell scoped *)) +((( cell.source | citation2latex | strip_files_prefix | markdown2latex(extra_args=["--chapters"]) ))) +((* endblock markdowncell *)) diff --git a/nbmerge.py b/nbmerge.py new file mode 100644 index 00000000..67d52dad --- /dev/null +++ b/nbmerge.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# From https://gist.github.com/fperez/e2bbc0a208e82e450f69 +# Note, updated version of +# https://github.com/ipython/ipython-in-depth/blob/master/tools/nbmerge.py +""" +usage: +python nbmerge.py A.ipynb B.ipynb C.ipynb > merged.ipynb +""" +from __future__ import print_function + +import io +import os +import sys + +from IPython import nbformat + +def merge_notebooks(filenames, outfile): + merged = None + for fname in filenames: + with io.open(fname, 'r', encoding='utf-8') as f: + nb = nbformat.read(f, as_version=4) + if merged is None: + merged = nb + else: + # TODO: add an optional marker between joined notebooks + # like an horizontal rule, for example, or some other arbitrary + # (user specified) markdown cell) + merged.cells.extend(nb.cells) + if not hasattr(merged.metadata, 'name'): + merged.metadata.name = '' + merged.metadata.name += "_merged" + result=nbformat.writes(merged) + with io.open(outfile, 'w', encoding='utf-8') as out: + out.write(result) + +if __name__ == '__main__': + notebooks = sys.argv[1:-1] + outfile = sys.argv[-1] + if not notebooks: + print(__doc__, file=sys.stderr) + sys.exit(1) + + merge_notebooks(notebooks, outfile) diff --git a/report.latex b/report.latex deleted file mode 100644 index 717eff30..00000000 --- a/report.latex +++ /dev/null @@ -1,48 +0,0 @@ -\let\oldlongtable\longtable -\def\longtable{\tiny \oldlongtable} -\usepackage[svgnames]{xcolor} -\usepackage{libertine} -\usepackage{framed} -\let\oldquote\quote -\let\endoldquote\endquote -\definecolor{verypalegreen}{RGB}{252,255,250} -\colorlet{shadecolor}{verypalegreen} - -\makeatletter -\def\shadequote{\begin{snugshade}\begin{oldquote}} -\def\endshadequote{% - \end{oldquote}\end{snugshade}} -\makeatother -\renewenvironment{quote}{\begin{shadequote}}{\end{shadequote}} -\usepackage{lettrine} % Package to accentuate the first letter of the text -\usepackage{fix-cm} % Custom font sizes - used for the initial letter in the document - -\definecolor{uclmidgreen}{RGB}{130,141,55} - -\newcommand{\initial}[1]{ % Defines the command and style for the first letter -\lettrine[lines=3,lhang=0.3,nindent=0em]{ -\color{DarkGreen} -{\textsf{#1}}}{}} - -\usepackage{sectsty} % Enables custom section titles -\sectionfont{\color{uclmidgreen} \usefont{OT1}{phv}{b}{n}} % Change the font of all section commands -\subsectionfont{\color{DarkSeaGreen} \usefont{OT1}{phv}{b}{n}} % Change the font of all section commands - -\usepackage{titling} % Allows custom title configuration -\newcommand{\HorRule}{\color{DarkSeaGreen} \rule{\linewidth}{1pt}} % Defines the gold horizontal rule around the title - -\pretitle{ -\vspace{-30pt} \begin{flushleft} \HorRule \fontsize{38}{38} \usefont{OT1}{phv}{b}{n} \color{uclmidgreen} \selectfont} % Horizontal rule before the title - -\posttitle{\par\end{flushleft}\begin{flushleft}\fontsize{35}{35} \usefont{OT1}{phv}{b}{n} \color{Black} \selectfont Tools for reproducible computational research \end{flushleft}\vskip 0.5em} % Whitespace under the title and subtitle - -\preauthor{\begin{flushleft}\large \lineskip 0.5em \usefont{OT1}{phv}{b}{sl} \color{uclmidgreen}} % Author font configuration - -\postauthor{\footnotesize \usefont{OT1}{phv}{m}{sl} \color{Black} % Configuration for the institution name - - -University College London% Your institution - -\par\end{flushleft}\HorRule} % Horizontal rule after the title - -\hypersetup{linkcolor=black} diff --git a/session01/00pythons.ipynb b/session01/00pythons.ipynb index da431f9e..ac2207c1 100644 --- a/session01/00pythons.ipynb +++ b/session01/00pythons.ipynb @@ -70,8 +70,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Garbage in Garbage out is not the real problem for science:\n", "\n", - "* ~~Garbage in Garbage out~~\n", "* Sensible input\n", "* Reasonable output\n", "\n" @@ -112,15 +112,17 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ + "```\n", "$ python\n", "Python 2.7.8 (default, Sep 19 2014, 17:15:25)\n", "[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] on darwin\n", "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n", ">>> print 2*4\n", - "8" + "8\n", + "```" ] }, { @@ -168,7 +170,7 @@ "source": [ "%%bash\n", "echo \"print 2*4\" > eight.py\n", - "python eight.py\n" + "python eight.py" ] }, { @@ -192,8 +194,7 @@ "echo '#!/usr/bin/env python' > eight\n", "echo \"print 2*4\" >> eight\n", "chmod u+x eight\n", - "./eight\n", - "\n" + "./eight" ] }, { @@ -204,9 +205,10 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ + "```\n", "$ ipython\n", "Python 2.7.8 (default, Sep 19 2014, 17:15:25)\n", "Type \"copyright\", \"credits\" or \"license\" for more information.\n", @@ -219,7 +221,8 @@ "\n", "In [1]: 2*4\n", "Out[1]: 8\n", - "In [2]: exit" + "In [2]: exit\n", + "```" ] }, { @@ -230,17 +233,19 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ + "```\n", "$ git clone https://github.com/UCL/rsd-engineeringcourse.git\n", "$ cd rsd-engineeringcourse/session01\n", - "$ ipython notebook" + "$ ipython notebook\n", + "```" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -248,10 +253,10 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" }, @@ -259,7 +264,7 @@ "data": { "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, diff --git a/session01/01data.ipynb b/session01/01data.ipynb index 36687199..48959a8f 100644 --- a/session01/01data.ipynb +++ b/session01/01data.ipynb @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -146,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -200,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -281,7 +281,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -342,7 +342,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -393,7 +393,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -453,7 +453,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -471,7 +471,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -480,7 +480,7 @@ "data": { "image/png": "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\nPtJcgaSUrgAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -510,7 +510,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -519,7 +519,7 @@ "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAEACAYAAACuzv3DAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAEv5JREFUeJzt3W+MZXdBxvHvrG1joTVNq7YVGrcBo7wglmhQtlSvBElr\ntGJi1FANAVx5YaSRVgq+6RhfWBsbfKVhhSYtyhrTBtLFN5QICRq3BO0/wLaCtAFbdhttjMQXkHB8\nce7O3pmds3Pun3N+zznn+0kme+fO7NwnJ9und545cw9IkiRJkiRJkiRJkiRJkiRt1DXAZ4AvAV8E\n3jO//3LgYeAZ4FPAZUXSSZIOdBVw3fz2JcDTwGuAu4H3ze+/A7ir/2iSpFV8Angz8BRw5fy+q+bv\nS5LCHQaeAy4FXlq4f2vP+5KkQJcA/wK8df7+3uL+737jSJLOuKDF51wIPAh8lHpaAThFPal8E7ga\nOL3P3/sK8KoNZJSkKfkq8OpNfsEt4H7gg3vuv5v6h5wA72f/H3ZWmwzSoe3SAVraLh2gpe3SAZpV\nR6A6DdUPw5sehOoZqC4tneoA26UDtLRdOkAL26UDtLR0dx70jPx64DeBJ4BH5/d9gLq4/w54F/As\n8GvLPrDUr+oK4Djw27D1HPAk8F/Ah6C6BbaG8sRDOsdBRf6PwKGGj715w1mkjlSHgPuAB2DroYUP\n3Ao8AhwFjpVIJqUbyjOcWekALc1KB2hpVjrAuarbofpnqC5cuHM2/9iPQvUiVD9eIlkLs9IBWpqV\nDtDCrHSAlqK6MyqMpqo6AtWpehdv/JxbBrKXaxqiujMqjKaougKq56D6pRafewyqj0G11X0u6byi\nujMqjKamOgTVJ6H6s5affzFUT0D1O93mkg4U1Z1RYTQ1++7iB/2d9L1c0xDVnVFhNCVtdvHGv+te\nrtKiujMqjKZimV288Wscg+q4e7kKierOqDCagp1d/J41v87FUD3uXq5CorozKoymYJVdvPFruZer\nlKjujAqjsVtnF2/8mu7lKiGqO6PCaMw2sYs3fm3PL1fforozKozGatnzxZf++p5frr5FdWdUGI3V\nJnfxxsdwL1eforozKozGqHrD5nfxxsdyL1dforozKozGprq8u1288THdy9WHqO6MCqMxqbagOtHd\nLt74uGf28qP9Pq4mJqo7o8JoTKrbut/FGx/bvVxdi+rOqDAaiz538cYMt0D1tHu5OhLVnVFhNAY7\nu/jNpZO4l6tDUd0ZFUZDV2oXb+Jers5EdWdUGA1dyV28iXu5OhHVnVFhNGQJu3gT93JtXFR3RoXR\nUJU4X3xZ7uXaqKjujAqjIUrbxZu4l2ujorozKoyGKHEXb+Jero2J6s6oMBqa5F28iXu5NiKqO6PC\naEiGsIs3cS/X2qK6MyqMhmIou3gT93KtLao7o8JoKPp4ffGuuZdrLVHdGRVGQ9DFdTdLqd7m65dr\nRVHdGRVG6bq87mYp7uVaSVR3RoVRsq6vu1mK1/vUSqK6MyqMko1hF2/iXq6lRXVnVBilGtMu3sTr\nfWopUd0ZFUaJxriLN3EvV2tR3RkVRmnGuos3cS9Xa1HdGRVGaca8izdxL1crUd0ZFUZJprCLN3Ev\n14GiujMqjFJMaRdv4l6u84rqzqgwSjD011HZlJ29/N2lkyhSVHdGhVGC6naoTk5rF2/iXq5GUd0Z\nFUalTXkXb+Jern1FdWdUGJXkLt7MvVzniOrOqDAqxV38/Dy/XOeI6s6oMCplSNfdLMW9XLtEdWdU\nGJUwxOtuluJerh1R3RkVRn0b8nU3S3EvF9BRd94LnAKeXLhvG/gG8Oj87ca+wmgI3MVX414uoKPu\nvAF4HbuL/E7gvSXCaAjcxVfnXq7uuvMw5xb5baXCKJm7+Prcyyeu1yJ/Fngc+AhwWZ9hlMpdfHPc\nyyds6e5s+4/kMHACeO38/R8EXpzf/mPgauBd+4T5o4X3Pzt/0yhVW8BDwDOwddB3azpQdTFwEvgL\n2PpQ6TTq1Gz+dsadtO/mpRxm9zPyNh/zGfmkuItvnnv5RPU2rVy9cPv3gY/1GUZp3MW7414+QZ10\n53HgeeDbwNeBdwL3A09Qb+SfAK7sK4zSuIt3z718YqK6MyqMuuD54v3w/PKJierOqDDqgrt4f9zL\nJySqO6PCaNPcxfvnXj4RUd0ZFUab5C5ejnv5BER1Z1QYbYq7eFk7e/nR0knUmajujAqjTXEXL8+9\nfOSiujMqjDbBXTxHdQtUT7uXj1JUd0aF0bp2dvGbSyfRGe7lIxXVnVFhtA538Uzu5SMV1Z1RYbQO\nd/Fc7uUjFNWdUWG0KnfxfO7lIxPVnVFhtArPFx8O9/IRierOqDBalrv4sLiXj0hUd0aF0bLcxYfH\nvXwkorozKoyW4S4+XO7lIxDVnVFh1Ja7+PC5lw9cVHdGhVEb7uLj4F4+cFHdGRVGbbiLj4d7+YBF\ndWdUGB3EXXx83MsHKqo7o8LofKorfB2VsXIvH6Co7owKoybVIag+6S4+Vl7vc4CiujMqjJpUt7uL\nj517+cBEdWdUGO2nOuIuPhVe73NAorozKoz22tnFPV98MtzLByKqO6PCaJG7+DS5lw9EVHdGhdEi\nd/Hpci8fgKjujAqjM9zF5V4eLqo7o8II3MV1lnt5sKjujAojd3Etci8PFtWdUWHkLq69dvby60on\n0S5R3RkVZtrcxdWkept7eZyo7owKM13u4jqIe3mYqO6MCjNN7uJqw708TFR3RoWZJndxteX55UGi\nujMqzPS4i2tZnl8eIqo7o8JMi7u4VuVeHiCqO6PCTIe7uNbhXh4gqjujwkxHdTtUJ93FtTr38sKi\nujMqzDRUR6A67S6u9bmXFxTVnVFhxs/rbmrT3MsLierOqDDjVh2C6gRU95ROojHZ2cvfXTrJxER1\nZ1SYcdvZxS8qnURj415eQFR3RoUZL3dxdc29vGdR3RkVZpzcxdUX9/IeRXVnVJjx2Tlf3F1cPfD8\n8h5FdWdUmPFxF1ff3Mt7EtWdUWHGxV1cpbiX9yCqO6PCjIe7uEpzL+9YJ915L3AKeHLhvsuBh4Fn\ngE8Bl/UVZtrcxZXAvbxjnXTnDcDr2F3kdwPvm9++A7irrzDT5i6uFO7lHeqsOw+zu8ifAq6c375q\n/n5vYabJXVxp3Ms70luRv7Rwe2vP+52HmR53caWqjkF13L18o5buzgs29KBND7y9cPuz8zctpToE\n3Ac8AFsPlU4j7XEr8AhwFDhWOMtQzeZvnTvMudPKVfPbV+O00iGvu6l07uUb1tu0cjf1DzkB3o8/\n7OyI193UULiXb1An3XkceB74NvB14B3Upx9+Gk8/7JDX3dTQeH75hkR1Z1SYYfG6mxoizy/fkKju\njAozLO7iGir38g2I6s6oMMPhLq6hcy9fU1R3RoUZBndxjYV7+RqiujMqTD53cY2Je/kaorozKkw+\nd3GNjXv5iqK6MypMNndxjZV7+QqiujMqTC5fR0Vj5+uxLCmqO6PCZHIX1xS4ly8pqjujwmRyF9dU\nuJcvIao7o8LkcRfX1LiXtxTVnVFhsni+uKbK88tbiOrOqDA53MU1Ze7lLUR1Z1SYHO7imjr38gNE\ndWdUmAzu4lLNvfw8orozKkx57uLSbu7lDaK6MypMWe7i0rncyxtEdWdUmLLcxaX9uZfvI6o7o8KU\nU73BXVw6n+pt7uW7RHVnVJgy3MWldtzLF0R1Z1SY/rmLS+25ly+I6s6oMP1zF5eW414+F9WdUWH6\n5fni0mo8v5yw7owK0x93cWk9k9/Lo7ozKkw/3MWl9U1+L4/qzqgw/XAXlzZj0nt5VHdGhemeu7i0\nWZPdy6O6MypMt7zuptSNSe7lUd0ZFaY7O7v4PaWTSOMzyb08qjujwnSnuh2qk1BdVDqJNE6T28uj\nujMqTDeqI1CddheXujapvTyqO6PCbJ67uNSvyezlUd0ZFWaz3MWl/k1mL4/qzqgwm+UuLpUxib08\nqjujwmyOu7hU1uj38qjujAqzGe7iUoZR7+VR3RkVZn3u4lKOUe/lUd0ZFWZ97uJSltHu5VHdGRVm\nPe7iUqZR7uVR3RkVZnXu4lK20e3lUd0ZFWY11SGoTriLS8l29vJ3l06yIVHdGRVmNe7i0jCMai+P\n6s6oMMvz9cWlYRnNXh7VnVFhluN1N6VhGsVeHtWdUWHa87qb0nCN4vzyqO6MCtOe192Uhm3we3lU\nd0aFacddXBqHQe/lUd0ZFeZg7uLSuAx2L++9O58FngAeBT5fOszq3MWl8RnsXt57d34NuLzhY0Mq\ncndxaZQGuZcXKfIrGj42kCJ3F5fGbXB7ee/d+R/Us8oXgKOlwyzPXVyahuoYVMcHspcv3Z0XrPmA\n1wMvAD8APAw8BXxu4ePbC7c/O38LUR0C7gMegK0TpdNI6tStwCPUTziPFc6y12z+FuFO4LaF98Of\nkbuLS9MymL281+58GXBmc3o58E/AW0qFWY67uDRNg9jLe+3Oa4HH5m9fBD5QMkx77uLStMWfXx7V\nnVFhap4vLin+/PKo7owKU3MXlwThe3lUd0aFcReXtFvsXh7VnUFh3MUl7SdyLw/qzpgw7uKSmkTu\n5SHdWQsJ4y4u6Xzi9vKQ7qwFhKne4C4u6WA7e/n3lU5CRHeeVTjMzi5+c9kckoYhZi+3yOcP7S4u\naUkxe7lFPn9od3FJK4jYyy1yzxeXtJ7i55dPvcg9X1zSJhTdy6dc5O7ikjal6F4+6SJ3F5e0QcX2\n8qkWubu4pC4U2cunWOSeLy6pS9Vf9byXT63Id3bxe7p/LEnT1PtePrkivx2qk1Bd1P1jSZqu6sd6\n3MunVOS+joqkPvW2l0+lyN3FJZXQy14+hSJ3F5dUSi97+SSK3F1cUkGd7+VjL/LqCFSn3cUlldXp\nXj7mIncXl5Sks718rEXuLi4pTWd7+WiL3F1cUqBO9vIxFrm7uKRkG9/Lx1bk7uKShmCje/mYitxd\nXNJQbHQvH1WRu4tLGpCd1y+/bt0vtJE4G7JGGF9fXNIQbWQvH0ORe91NSUO29vU+h17kXndT0tCt\nvZcPvsi97qakEVjrep9DLnJ3cUljsvJePtQidxeXNEYr7eVDLPJqC6oT7uKSxmelvXyQRX6bu7ik\n8Vp6Lx9akXvdTUlTsNRePqQiry53F5c0Ha338qEU+c4u7uuoSJqInb38Vw/6xF7itHS+IncXlzRB\n1Q+1eP2oIRS5u7gknUd6kbuLS9IBkovc88UlqYXoIvd1VCTpYKlF7uuoSFJLvRb5jcBTwL8DdzSH\n8XVUJGkJvRX59wBfAQ4DFwKPAa85N8wgXl98VjpAS7PSAVqalQ7Q0qx0gJZmpQO0NCsdoIVZ6QAt\nLV3kh1Z8oNdTF/mzwHeAvwV+eZ/Pey9wBfCBFR+nD7PSAVqalQ7Q0qx0gJZmpQO0NCsdoKVZ6QAt\nzEoH6MoFK/69VwBfX3j/G8BP7fN5fwC8Hra+s+LjSJIOsOoz8rZP/Y/C1nMrPoYkqYVVLw7608A2\n9Q88oZ5Ovgv86cLnfAV41crJJGmavgq8uo8HumD+YIeBi9j/h52SpHA3AU9TP/NO/mGmJEmSND0H\n/bJQimeBJ4BHgc+XjbLLvcAp4MmF+y4HHgaeAT4FXFYg11775dymPovp0fnbjef+tV5dA3wG+BLw\nReA98/vTjmdTzm2yjuf3Ao9Qz6lfBv5kfn/a8WzKuU3W8YT693IeBU7M3484lm1+WSjF16gPWpob\ngNexuyDvBt43v30HcFffofaxX847qX9/IMVVwHXz25dQz4GvIe94NuVMO54AL5v/eQFwEngjeccT\n9s+ZeDzfC/wN8ND8/aWP5aqnH55P218WSrHqmTtd+hzw0p77bgbum9++D3hrr4n2t19OyDqm36R+\nMgHwLeDfqH8PIu14NuWErOMJ8H/zPy+ifuL2EnnHE/bPCVnH85XALwAf5myupY9lF0W+3y8LvaLh\nc0urgE8DXwCOFs5ykCupZwzmf15ZMMtBfg94HPgI5b/FXnSY+juIR8g+noepc56cv592PA9R/0/n\nFGfnoMTjuV9OyDqeH6T+xcnvLty39LHsosijXoLxANdT/wdzE/C71FPBEFTkHue/BK6lngleAFKu\ny3oJ8CBwK/C/ez6WdDwvAR6gzvktMo/nd6nzvBL4GeDn9nw85XjuzTkj63j+InCaeh9v+i6h1bHs\nosj/k/oHN2dcQ/2sPNEL8z9fBD5OPQulOkW9owJcTf0PINFpzv7j+zAZx/RC6hL/KPCJ+X2Jx/NM\nzr/mbM7E43nG/wB/D/wEmcfzjDM5f5Ks43mEekb5GnAceBP1v9Glj2UXRf4F4Ec4+8tCv87ZET/J\ny4BL57dfDryF3T+0S/MQ8Pb57bdz9j/0NFcv3P4Vyh/TLepvob8M/PnC/WnHsyln2vH8fs7OERcD\nP0/9jDLteDblvGrhc0ofzz+kfqJ7LfAbwD8Av0XQsRzCLwtdS72fPUZ9uldSzuPA88C3qX/e8A7q\ns2s+Tc7pXXBuzncC91Of0vk49T/A0lvpG6m/xX6M3aecpR3P/XLeRN7xfC3wr9Q5n6DedyHveDbl\nTDueZ/wsZ5/wph1LSZIkSZIkSZIkSZIkSZIkSZIkSZv0/+s4VBa17qKUAAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -560,7 +560,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -637,7 +637,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -646,7 +646,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing analyzer.py\n" + "Overwriting analyzer.py\n" ] } ], @@ -679,7 +679,9 @@ "def generate(filename, output=False):\n", " if not output:\n", " output = os.path.splitext(filename)[0] + '.png'\n", - " analyze(filename).savefig(output)\n", + " fig=analyze(filename)\n", + " fig.savefig(output)\n", + " plt.close(fig)\n", "\n", "def bulk_generate(sources):\n", " for source in sources:\n", @@ -716,13 +718,26 @@ ] }, { - "cell_type": "raw", - "metadata": {}, + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Appending to analyzer.py\n" + ] + } + ], "source": [ - "#!/usr/bin/env python\n", + "%%writefile -a analyzer.py\n", + "\n", "import sys\n", "if __name__ == \"__main__\":\n", - " bulk_generate(sys.argv[1:])\n" + " bulk_generate(sys.argv[1:])" ] }, { @@ -735,7 +750,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 28, "metadata": { "collapsed": false }, @@ -759,13 +774,27 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nIz87Fo75kflR7fxoUhSNF/XpRFhYGNatW4cxY8bAbDZj2bJlOHToELZv345nnnlG1bLbt28PLy8v\ntG7dGpUrV8bPP/+Mzz77DO7u7khMTES9evXsVtb8+fORmZmJ9PR0LFy4EC+//HJeD8+oUaPg6+uL\n1NRUNG3aFH5+fhg9ejSys7Px0UcfoXr16jh48GCppms8qvwrV66gadOm6Nu3b169t27dis2bN6Nz\n587YtGlTicqNiIjAvHnz0L17d/Tp06fA8/369QMA1epuS/lnz55Vpe49e/ZEdnY2nnvuOQQFBeHC\nhQtYuXIlTp8+jaVLl6J///6q1t2W8tWqe1FCQkLwxx9/4NixY3nX1LzvHZmz5EZAbn5kbmRuZG50\nPM6SH/nZkfmR+VFyfrRbU9/B5OTkKOPHj1cCAwMVDw8PpWXLlsq3336rSdnz5s1TWrZsqfj7+ytl\nypRRqlatqvTv319JSkqye1k1a9ZUTCaTYjKZFBcXF8XFxSXv7ykpKXmvO3HihNKpUyfF29tbqVix\novLaa68V2ptl7/IzMzOV1157TalTp47i7e2teHh4KI0aNVJiYmLyeutKIiQkJK+sBx8uLi75XqtG\n3W0pX626r1q1SunYsaNSpUoVpUyZMoq/v7/SpUsXZdu2bQVeq0bdbSlfrboXJSQkJN9OnFZq3feO\nzFlyo6LIzY/MjcyNzI2Ox1nyIz87Mj8yP8rNj047gk5ERERERESkJ065SRwRERERERGR3rCBTkRE\nRERERKQDbKATERERERER6QAb6EREREREREQ6wAY6ERERERERkQ6wgU5ERERERESkA2ygExERERER\nEekAG+hEREREREREOsAGOhEREREREZEOsIFOREREREREpANsoBMRERERERHpABvoRERERERERDrA\nBjoRERERERGRDrCBTkRERERERKQDbKATERERERER6QAb6EREREREREQ6wAY6ERERERERkQ6wgU5E\nRERERESkA2ygExEREREREenA/wOaZw38O9Rm0gAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "![A figure](data/inflammation-01.png)\n", - "\n", - "\n", - "\n" + "import IPython\n", + "IPython.core.display.Image(open('data/inflammation-01.png').read())" ] } ], diff --git a/session01/02types.ipynb b/session01/02types.ipynb index 1f416713..b2b29767 100644 --- a/session01/02types.ipynb +++ b/session01/02types.ipynb @@ -51,14 +51,6 @@ "print type(one/ten), type(tenth)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -99,14 +91,6 @@ "print full.upper()\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -143,14 +127,6 @@ "print float(str(ten)+str(one))\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -190,14 +166,6 @@ "print \" \".join(name)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -243,14 +211,6 @@ "print count_to_five[0:2]*3\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -288,14 +248,6 @@ "print one\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -357,14 +309,6 @@ "print \" \".join(name)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -458,14 +402,6 @@ "print [1] is [1]\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -507,14 +443,6 @@ "print x, y, z\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -559,14 +487,6 @@ "print count_to_five[0:2]*3\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -612,14 +532,6 @@ "illegal = {[1,2]: 3}\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -661,14 +573,6 @@ "print my_dict\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -712,6 +616,9 @@ } ], "metadata": { + "jekyll": { + "display_name": "Introduction" + }, "kernelspec": { "display_name": "Python 2", "language": "python", diff --git a/session01/03control.ipynb b/session01/03control.ipynb index 58306440..c69ccf16 100644 --- a/session01/03control.ipynb +++ b/session01/03control.ipynb @@ -50,14 +50,6 @@ " print \"x is positive\"\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -102,14 +94,6 @@ "'1' < 2\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -151,14 +135,6 @@ "print x\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -232,15 +208,6 @@ "print \"Hello\"\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -372,14 +339,6 @@ " print thing, \" is \", current_year - founded[thing], \"years old.\"\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -461,14 +420,6 @@ " print thing, \" is \", current_year - year, \"years old.\"\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -519,14 +470,6 @@ " print n\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -571,14 +514,6 @@ "print first_negative\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -702,6 +637,9 @@ } ], "metadata": { + "jekyll": { + "display_name": "Control Flow" + }, "kernelspec": { "display_name": "Python 2", "language": "python", diff --git a/session01/04functions.ipynb b/session01/04functions.ipynb index c3f5275f..60bc3de8 100644 --- a/session01/04functions.ipynb +++ b/session01/04functions.ipynb @@ -46,14 +46,6 @@ "print double(5), double([5]), double('five')\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -96,14 +88,6 @@ "print z\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -157,14 +141,6 @@ "print x" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -241,14 +217,6 @@ " print arrow(*particle)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -289,14 +257,6 @@ "print doubler(1,2,3)\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -341,6 +301,9 @@ } ], "metadata": { + "jekyll": { + "display_name": "Functions" + }, "kernelspec": { "display_name": "Python 2", "language": "python", diff --git a/session01/05modules.ipynb b/session01/05modules.ipynb index aaca3172..5ed1e5fb 100644 --- a/session01/05modules.ipynb +++ b/session01/05modules.ipynb @@ -34,7 +34,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing pretty.py\n" + "Overwriting pretty.py\n" ] } ], @@ -102,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -151,7 +151,7 @@ "1.2246467991473532e-16" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -186,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -197,7 +197,7 @@ "1.2246467991473532e-16" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -237,23 +237,34 @@ ] }, { - "cell_type": "raw", + "cell_type": "markdown", "metadata": {}, "source": [ + "```\n", "module1\n", "|-- __init__.py\n", "|-- module2.py\n", "`-- module3\n", - " `-- __init__.py" + " `-- __init__.py\n", + "```" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "mkdir: module1: File exists\n", + "mkdir: module1/module3: File exists\n" + ] + } + ], "source": [ "%%bash\n", "mkdir module1\n", @@ -262,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -271,7 +282,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing module1/__init__.py\n" + "Overwriting module1/__init__.py\n" ] } ], @@ -282,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -291,7 +302,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing module1/module2.py\n" + "Overwriting module1/module2.py\n" ] } ], @@ -302,7 +313,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -322,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -359,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -368,7 +379,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Writing module1/module3/__init__.py\n" + "Overwriting module1/module3/__init__.py\n" ] } ], @@ -403,6 +414,9 @@ } ], "metadata": { + "jekyll": { + "display_name": "Modules" + }, "kernelspec": { "display_name": "Python 2", "language": "python", diff --git a/session01/06exercise.ipynb b/session01/06exercise.ipynb index d4af19b4..a610961b 100644 --- a/session01/06exercise.ipynb +++ b/session01/06exercise.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Week 1 Exercise" + "# Exercise" ] }, { @@ -58,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 1, "metadata": { "collapsed": false }, @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -100,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -109,7 +109,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[master (root-commit) 716e2d2] Example commit\n", + "[master (root-commit) de38d01] Example commit\n", " 1 file changed, 0 insertions(+), 0 deletions(-)\n", " create mode 100644 test.txt\n" ] @@ -126,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -169,6 +169,9 @@ } ], "metadata": { + "jekyll": { + "display_name": "Introduction" + }, "kernelspec": { "display_name": "Python 2", "language": "python", diff --git a/session02/slides.md b/session02/slides.md deleted file mode 100644 index 2d97a889..00000000 --- a/session02/slides.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Version Control in Research Software -author: James Hetherington and Matt Clarkson ---- - -{{d['session02/index.md']}} -{{d['session02/00git.md']}} -{{d['session02/02licensing.md']}} -{{d['session02/03issues.md']}} -{{d['session02/04exercises.md']}} diff --git a/session03/slides.md b/session03/slides.md deleted file mode 100644 index 0326c09c..00000000 --- a/session03/slides.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Research Software Engineering with Python -author: James Hetherington and Mayeul d'Avezac ---- - -{{d['session03/01primer.md']}} -{{d['session03/02HowToTest.md']}} -{{d['session03/03Frameworks.md']}} -{{d['session03/04floatingPoints.md']}} -{{d['session03/05nose_exercise.md']}} -{{d['session03/06mocking.md']}} -{{d['session03/07debugging.md']}} -{{d['session03/08Methodology.md']}} -{{d['session03/09otherTools.md']}} -{{d['session03/10MonteCarlo.md']}} diff --git a/session04/figures/tree.foo b/session04/figures/tree.foo deleted file mode 100644 index 0558040d..00000000 --- a/session04/figures/tree.foo +++ /dev/null @@ -1,14 +0,0 @@ -├── README.md -├── LICENSE.md -├── setup.py -├── scripts -│   └── greengraph -└── greengraph - ├── __init__.py - ├── __main__.py - ├── greengraph.py - └── test - ├── __init__.py - ├── fixtures - │   └── london.png - └── test_greengraph.py \ No newline at end of file diff --git a/session04/python/greetings/CITATION.md b/session04/greetings/CITATION.md similarity index 88% rename from session04/python/greetings/CITATION.md rename to session04/greetings/CITATION.md index 5e548cf0..a7d51b16 100644 --- a/session04/python/greetings/CITATION.md +++ b/session04/greetings/CITATION.md @@ -1,5 +1,6 @@ + If you wish to refer to this course, please cite the URL http://development.rc.ucl.ac.uk/training/engineering Portions of the material are taken from Software Carpentry -http://swcarpentry.org +http://swcarpentry.org \ No newline at end of file diff --git a/session04/python/greetings/LICENSE.md b/session04/greetings/LICENSE.md similarity index 96% rename from session04/python/greetings/LICENSE.md rename to session04/greetings/LICENSE.md index a50b99d9..377f6781 100644 --- a/session04/python/greetings/LICENSE.md +++ b/session04/greetings/LICENSE.md @@ -1,3 +1,4 @@ + (C) University College London 2014 -This "greetings" example package is granted into the public domain. +This "greetings" example package is granted into the public domain. \ No newline at end of file diff --git a/session04/python/greetings/README.md b/session04/greetings/README.md similarity index 80% rename from session04/python/greetings/README.md rename to session04/greetings/README.md index d7221910..09ad3445 100644 --- a/session04/python/greetings/README.md +++ b/session04/greetings/README.md @@ -1,5 +1,6 @@ + Greetings! ========== This is a very simple example package used as part of the UCL -[Research Software Engineering with Python](development.rc.ucl.ac.uk/training/engineering) course. +[Research Software Engineering with Python](development.rc.ucl.ac.uk/training/engineering) course. \ No newline at end of file diff --git a/session04/greetings/conf.py b/session04/greetings/conf.py new file mode 100644 index 00000000..5b1631ba --- /dev/null +++ b/session04/greetings/conf.py @@ -0,0 +1,41 @@ + +import sys +import os + +extensions = [ + 'sphinx.ext.autodoc', # Support automatic documentation + 'sphinx.ext.coverage', # Automatically check if functions are documented + 'sphinx.ext.mathjax', # Allow support for algebra + 'sphinx.ext.viewcode', # Include the source code in documentation + 'numpydoc' # Support NumPy style docstrings +] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +project = u'Greetings' +copyright = u'2014, James Hetherington' +version = '0.1' +release = '0.1' +exclude_patterns = ['_build'] +pygments_style = 'sphinx' +html_theme = 'default' +html_static_path = ['_static'] +htmlhelp_basename = 'Greetingsdoc' +latex_elements = { +} + +latex_documents = [ + ('index', 'Greetings.tex', u'Greetings Documentation', + u'James Hetherington', 'manual'), +] + +man_pages = [ + ('index', 'greetings', u'Greetings Documentation', + [u'James Hetherington'], 1) +] + +texinfo_documents = [ + ('index', 'Greetings', u'Greetings Documentation', + u'James Hetherington', 'Greetings', 'One line description of project.', + 'Miscellaneous'), +] \ No newline at end of file diff --git a/session04/greetings/doc/.buildinfo b/session04/greetings/doc/.buildinfo new file mode 100644 index 00000000..4d0edb3f --- /dev/null +++ b/session04/greetings/doc/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: cec9e78b8523f1e9e0606cab09d77a06 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/session04/greetings/doc/.doctrees/environment.pickle b/session04/greetings/doc/.doctrees/environment.pickle new file mode 100644 index 00000000..0630d2cb Binary files /dev/null and b/session04/greetings/doc/.doctrees/environment.pickle differ diff --git a/session04/greetings/doc/.doctrees/index.doctree b/session04/greetings/doc/.doctrees/index.doctree new file mode 100644 index 00000000..7cc31e22 Binary files /dev/null and b/session04/greetings/doc/.doctrees/index.doctree differ diff --git a/session04/greetings/doc/_modules/greetings/greeter.html b/session04/greetings/doc/_modules/greetings/greeter.html new file mode 100644 index 00000000..3807ed72 --- /dev/null +++ b/session04/greetings/doc/_modules/greetings/greeter.html @@ -0,0 +1,116 @@ + + + + + + + + greetings.greeter — Greetings 0.1 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for greetings.greeter

+### "Def"
+
[docs]def greet(personal, family, title="", polite=False): +### "Doc" + """ Generate a greeting string for a person. + + Parameters + ---------- + personal: str + A given name, such as Will or Jean-Luc + family: str + A family name, such as Riker or Picard + title: str + An optional title, such as Captain or Reverend + polite: bool + True for a formal greeting, False for informal. + + Returns + ------- + string + An appropriate greeting + """ + +### "Content" + greeting= "How do you do, " if polite else "Hey, " + if title: + greeting+=title+" " + + greeting+= personal + " " + family +"." + return greeting
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/session04/greetings/doc/_modules/index.html b/session04/greetings/doc/_modules/index.html new file mode 100644 index 00000000..d8b58c9e --- /dev/null +++ b/session04/greetings/doc/_modules/index.html @@ -0,0 +1,85 @@ + + + + + + + + Overview: module code — Greetings 0.1 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

All modules for which code is available

+ + +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/session04/greetings/doc/_sources/index.txt b/session04/greetings/doc/_sources/index.txt new file mode 100644 index 00000000..7454d5cc --- /dev/null +++ b/session04/greetings/doc/_sources/index.txt @@ -0,0 +1,6 @@ +Welcome to Greetings's documentation! +===================================== + +Simple "Hello, James" module developed to teach research software engineering. + +.. autofunction:: greetings.greeter.greet \ No newline at end of file diff --git a/session04/greetings/doc/_static/ajax-loader.gif b/session04/greetings/doc/_static/ajax-loader.gif new file mode 100644 index 00000000..61faf8ca Binary files /dev/null and b/session04/greetings/doc/_static/ajax-loader.gif differ diff --git a/session04/greetings/doc/_static/basic.css b/session04/greetings/doc/_static/basic.css new file mode 100644 index 00000000..967e36ce --- /dev/null +++ b/session04/greetings/doc/_static/basic.css @@ -0,0 +1,537 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/session04/greetings/doc/_static/comment-bright.png b/session04/greetings/doc/_static/comment-bright.png new file mode 100644 index 00000000..551517b8 Binary files /dev/null and b/session04/greetings/doc/_static/comment-bright.png differ diff --git a/session04/greetings/doc/_static/comment-close.png b/session04/greetings/doc/_static/comment-close.png new file mode 100644 index 00000000..09b54be4 Binary files /dev/null and b/session04/greetings/doc/_static/comment-close.png differ diff --git a/session04/greetings/doc/_static/comment.png b/session04/greetings/doc/_static/comment.png new file mode 100644 index 00000000..92feb52b Binary files /dev/null and b/session04/greetings/doc/_static/comment.png differ diff --git a/session04/greetings/doc/_static/default.css b/session04/greetings/doc/_static/default.css new file mode 100644 index 00000000..5f1399ab --- /dev/null +++ b/session04/greetings/doc/_static/default.css @@ -0,0 +1,256 @@ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} \ No newline at end of file diff --git a/session04/greetings/doc/_static/doctools.js b/session04/greetings/doc/_static/doctools.js new file mode 100644 index 00000000..c5455c90 --- /dev/null +++ b/session04/greetings/doc/_static/doctools.js @@ -0,0 +1,238 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/session04/greetings/doc/_static/down-pressed.png b/session04/greetings/doc/_static/down-pressed.png new file mode 100644 index 00000000..6f7ad782 Binary files /dev/null and b/session04/greetings/doc/_static/down-pressed.png differ diff --git a/session04/greetings/doc/_static/down.png b/session04/greetings/doc/_static/down.png new file mode 100644 index 00000000..3003a887 Binary files /dev/null and b/session04/greetings/doc/_static/down.png differ diff --git a/session04/greetings/doc/_static/file.png b/session04/greetings/doc/_static/file.png new file mode 100644 index 00000000..d18082e3 Binary files /dev/null and b/session04/greetings/doc/_static/file.png differ diff --git a/session04/greetings/doc/_static/jquery.js b/session04/greetings/doc/_static/jquery.js new file mode 100644 index 00000000..83589daa --- /dev/null +++ b/session04/greetings/doc/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/session04/greetings/doc/_static/minus.png b/session04/greetings/doc/_static/minus.png new file mode 100644 index 00000000..da1c5620 Binary files /dev/null and b/session04/greetings/doc/_static/minus.png differ diff --git a/session04/greetings/doc/_static/plus.png b/session04/greetings/doc/_static/plus.png new file mode 100644 index 00000000..b3cb3742 Binary files /dev/null and b/session04/greetings/doc/_static/plus.png differ diff --git a/session04/greetings/doc/_static/pygments.css b/session04/greetings/doc/_static/pygments.css new file mode 100644 index 00000000..d79caa15 --- /dev/null +++ b/session04/greetings/doc/_static/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/session04/greetings/doc/_static/searchtools.js b/session04/greetings/doc/_static/searchtools.js new file mode 100644 index 00000000..6e1f06bd --- /dev/null +++ b/session04/greetings/doc/_static/searchtools.js @@ -0,0 +1,622 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + + +/** + * Simple result scoring code. + */ +var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + // query found in terms + term: 5 +}; + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

').appendTo(this.out); + this.output = $('
'); + } + // Prettify the comment rating. + comment.pretty_rating = comment.rating + ' point' + + (comment.rating == 1 ? '' : 's'); + // Make a class (for displaying not yet moderated comments differently) + comment.css_class = comment.displayed ? '' : ' moderate'; + // Create a div for this comment. + var context = $.extend({}, opts, comment); + var div = $(renderTemplate(commentTemplate, context)); + + // If the user has voted on this comment, highlight the correct arrow. + if (comment.vote) { + var direction = (comment.vote == 1) ? 'u' : 'd'; + div.find('#' + direction + 'v' + comment.id).hide(); + div.find('#' + direction + 'u' + comment.id).show(); + } + + if (opts.moderator || comment.text != '[deleted]') { + div.find('a.reply').show(); + if (comment.proposal_diff) + div.find('#sp' + comment.id).show(); + if (opts.moderator && !comment.displayed) + div.find('#cm' + comment.id).show(); + if (opts.moderator || (opts.username == comment.username)) + div.find('#dc' + comment.id).show(); + } + return div; + } + + /** + * A simple template renderer. Placeholders such as <%id%> are replaced + * by context['id'] with items being escaped. Placeholders such as <#id#> + * are not escaped. + */ + function renderTemplate(template, context) { + var esc = $(document.createElement('div')); + + function handle(ph, escape) { + var cur = context; + $.each(ph.split('.'), function() { + cur = cur[this]; + }); + return escape ? esc.text(cur || "").html() : cur; + } + + return template.replace(/<([%#])([\w\.]*)\1>/g, function() { + return handle(arguments[2], arguments[1] == '%' ? true : false); + }); + } + + /** Flash an error message briefly. */ + function showError(message) { + $(document.createElement('div')).attr({'class': 'popup-error'}) + .append($(document.createElement('div')) + .attr({'class': 'error-message'}).text(message)) + .appendTo('body') + .fadeIn("slow") + .delay(2000) + .fadeOut("slow"); + } + + /** Add a link the user uses to open the comments popup. */ + $.fn.comment = function() { + return this.each(function() { + var id = $(this).attr('id').substring(1); + var count = COMMENT_METADATA[id]; + var title = count + ' comment' + (count == 1 ? '' : 's'); + var image = count > 0 ? opts.commentBrightImage : opts.commentImage; + var addcls = count == 0 ? ' nocomment' : ''; + $(this) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-open' + addcls, + id: 'ao' + id + }) + .append($(document.createElement('img')).attr({ + src: image, + alt: 'comment', + title: title + })) + .click(function(event) { + event.preventDefault(); + show($(this).attr('id').substring(2)); + }) + ) + .append( + $(document.createElement('a')).attr({ + href: '#', + 'class': 'sphinx-comment-close hidden', + id: 'ah' + id + }) + .append($(document.createElement('img')).attr({ + src: opts.closeCommentImage, + alt: 'close', + title: 'close' + })) + .click(function(event) { + event.preventDefault(); + hide($(this).attr('id').substring(2)); + }) + ); + }); + }; + + var opts = { + processVoteURL: '/_process_vote', + addCommentURL: '/_add_comment', + getCommentsURL: '/_get_comments', + acceptCommentURL: '/_accept_comment', + deleteCommentURL: '/_delete_comment', + commentImage: '/static/_static/comment.png', + closeCommentImage: '/static/_static/comment-close.png', + loadingImage: '/static/_static/ajax-loader.gif', + commentBrightImage: '/static/_static/comment-bright.png', + upArrow: '/static/_static/up.png', + downArrow: '/static/_static/down.png', + upArrowPressed: '/static/_static/up-pressed.png', + downArrowPressed: '/static/_static/down-pressed.png', + voting: false, + moderator: false + }; + + if (typeof COMMENT_OPTIONS != "undefined") { + opts = jQuery.extend(opts, COMMENT_OPTIONS); + } + + var popupTemplate = '\ +
\ +

\ + Sort by:\ + best rated\ + newest\ + oldest\ +

\ +
Comments
\ +
\ + loading comments...
\ +
    \ +
    \ +

    Add a comment\ + (markup):

    \ +
    \ + reStructured text markup: *emph*, **strong**, \ + ``code``, \ + code blocks: :: and an indented block after blank line
    \ +
    \ + \ +

    \ + \ + Propose a change ▹\ + \ + \ + Propose a change ▿\ + \ +

    \ + \ + \ + \ + \ + \ +
    \ +
    '; + + var commentTemplate = '\ +
    \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ + \ + \ + \ + \ + \ + \ +
    \ +
    \ +
    \ +

    \ + <%username%>\ + <%pretty_rating%>\ + <%time.delta%>\ +

    \ +
    <#text#>
    \ +

    \ + \ + reply ▿\ + proposal ▹\ + proposal ▿\ + \ + \ +

    \ +
    \
    +<#proposal_diff#>\
    +        
    \ +
      \ +
      \ +
      \ +
      \ + '; + + var replyTemplate = '\ +
    • \ +
      \ +
      \ + \ + \ + \ + \ + \ + \ +
      \ +
    • '; + + $(document).ready(function() { + init(); + }); +})(jQuery); + +$(document).ready(function() { + // add comment anchors for all paragraphs that are commentable + $('.sphinx-has-comment').comment(); + + // highlight search words in search results + $("div.context").each(function() { + var params = $.getQueryParameters(); + var terms = (params.q) ? params.q[0].split(/\s+/) : []; + var result = $(this); + $.each(terms, function() { + result.highlightText(this.toLowerCase(), 'highlighted'); + }); + }); + + // directly open comment window if requested + var anchor = document.location.hash; + if (anchor.substring(0, 9) == '#comment-') { + $('#ao' + anchor.substring(9)).click(); + document.location.hash = '#s' + anchor.substring(9); + } +}); diff --git a/session04/greetings/doc/genindex.html b/session04/greetings/doc/genindex.html new file mode 100644 index 00000000..a476e5b1 --- /dev/null +++ b/session04/greetings/doc/genindex.html @@ -0,0 +1,93 @@ + + + + + + + + + Index — Greetings 0.1 documentation + + + + + + + + + + + + + + +
      +
      +
      +
      + + +

      Index

      + +
      + +
      + + +
      +
      +
      +
      +
      + + + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/session04/greetings/doc/index.html b/session04/greetings/doc/index.html new file mode 100644 index 00000000..b72379ff --- /dev/null +++ b/session04/greetings/doc/index.html @@ -0,0 +1,92 @@ + + + + + + + + Welcome to Greetings’s documentation! — Greetings 0.1 documentation + + + + + + + + + + + + + + +
      +
      +
      +
      + +
      +

      Welcome to Greetings’s documentation!¶

      +

      Simple “Hello, James” module developed to teach research software engineering.

      +
      + + +
      +
      +
      +
      +
      +

      This Page

      + + + +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/session04/greetings/doc/objects.inv b/session04/greetings/doc/objects.inv new file mode 100644 index 00000000..adf25ebc Binary files /dev/null and b/session04/greetings/doc/objects.inv differ diff --git a/session04/greetings/doc/search.html b/session04/greetings/doc/search.html new file mode 100644 index 00000000..7d1b876e --- /dev/null +++ b/session04/greetings/doc/search.html @@ -0,0 +1,100 @@ + + + + + + + + Search — Greetings 0.1 documentation + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      + +

      Search

      +
      + +

      + Please activate JavaScript to enable the search + functionality. +

      +
      +

      + From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

      +
      + + + + + +
      + +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + + \ No newline at end of file diff --git a/session04/greetings/doc/searchindex.js b/session04/greetings/doc/searchindex.js new file mode 100644 index 00000000..4d3044fa --- /dev/null +++ b/session04/greetings/doc/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:42,terms:{softwar:0,modul:0,famili:[],greeter:[],captain:[],string:[],given:[],engin:0,inform:[],titl:[],research:0,reverend:[],luc:[],bool:[],paramet:[],simpl:0,polit:[],riker:[],sourc:[],"return":[],option:[],fals:[],gener:[],develop:0,teach:0,"true":[],formal:[],appropri:[],name:[],jame:0,person:[],str:[],picard:[],jean:[],hello:0},objtypes:{},objnames:{},filenames:["index"],titles:["Welcome to Greetings’s documentation!"],objects:{},titleterms:{document:0,welcom:0,greet:0}}) \ No newline at end of file diff --git a/session04/python/greetings/greetings/__init__.py b/session04/greetings/greetings/__init__.py similarity index 100% rename from session04/python/greetings/greetings/__init__.py rename to session04/greetings/greetings/__init__.py diff --git a/session04/python/greetings/greetings/command.py b/session04/greetings/greetings/command.py similarity index 97% rename from session04/python/greetings/greetings/command.py rename to session04/greetings/greetings/command.py index 2716b3b0..05a2c655 100755 --- a/session04/python/greetings/greetings/command.py +++ b/session04/greetings/greetings/command.py @@ -14,4 +14,4 @@ def process(): print greet(arguments.personal, arguments.family, arguments.title, arguments.polite) if __name__ == "__main__": - process() + process() \ No newline at end of file diff --git a/session04/python/greetings/scripts/greet b/session04/greetings/greetings/greet old mode 100755 new mode 100644 similarity index 85% rename from session04/python/greetings/scripts/greet rename to session04/greetings/greetings/greet index 8e386267..2ad1cb02 --- a/session04/python/greetings/scripts/greet +++ b/session04/greetings/greetings/greet @@ -1,4 +1,4 @@ + #!/usr/bin/env python from greetings.command import process -process() - +process() \ No newline at end of file diff --git a/session04/python/greetings/greetings/greeter.py b/session04/greetings/greetings/greeter.py similarity index 92% rename from session04/python/greetings/greetings/greeter.py rename to session04/greetings/greetings/greeter.py index 53654340..f2b74201 100644 --- a/session04/python/greetings/greetings/greeter.py +++ b/session04/greetings/greetings/greeter.py @@ -1,6 +1,7 @@ -### "Def" + + def greet(personal, family, title="", polite=False): -### "Doc" + """ Generate a greeting string for a person. Parameters @@ -20,10 +21,9 @@ def greet(personal, family, title="", polite=False): An appropriate greeting """ -### "Content" greeting= "How do you do, " if polite else "Hey, " if title: greeting+=title+" " greeting+= personal + " " + family +"." - return greeting + return greeting \ No newline at end of file diff --git a/session04/python/greetings/greetings/test/__init__.py b/session04/greetings/greetings/test/__init__.py similarity index 100% rename from session04/python/greetings/greetings/test/__init__.py rename to session04/greetings/greetings/test/__init__.py diff --git a/session04/python/greetings/greetings/test/fixtures/samples.yaml b/session04/greetings/greetings/test/fixtures/samples.yaml similarity index 85% rename from session04/python/greetings/greetings/test/fixtures/samples.yaml rename to session04/greetings/greetings/test/fixtures/samples.yaml index be59b4b0..bc5e2d5f 100644 --- a/session04/python/greetings/greetings/test/fixtures/samples.yaml +++ b/session04/greetings/greetings/test/fixtures/samples.yaml @@ -8,4 +8,4 @@ - personal: James family: Hetherington title: Dr - answer: "Hey, Dr James Hetherington." + answer: "Hey, Dr James Hetherington." \ No newline at end of file diff --git a/session04/python/greetings/greetings/test/test_greeter.py b/session04/greetings/greetings/test/test_greeter.py similarity index 86% rename from session04/python/greetings/greetings/test/test_greeter.py rename to session04/greetings/greetings/test/test_greeter.py index 1d24c233..d1d401cc 100644 --- a/session04/python/greetings/greetings/test/test_greeter.py +++ b/session04/greetings/greetings/test/test_greeter.py @@ -7,4 +7,4 @@ def test_greeter(): fixtures=yaml.load(fixtures_file) for fixture in fixtures: answer=fixture.pop('answer') - assert_equal(greet(**fixture), answer) + assert_equal(greet(**fixture), answer) \ No newline at end of file diff --git a/session04/python/greetings/index.rst b/session04/greetings/index.rst similarity index 78% rename from session04/python/greetings/index.rst rename to session04/greetings/index.rst index 35d7338c..7454d5cc 100644 --- a/session04/python/greetings/index.rst +++ b/session04/greetings/index.rst @@ -3,5 +3,4 @@ Welcome to Greetings's documentation! Simple "Hello, James" module developed to teach research software engineering. -.. autofunction:: greetings.greeter.greet - +.. autofunction:: greetings.greeter.greet \ No newline at end of file diff --git a/session04/python/greetings/setup.py b/session04/greetings/setup.py similarity index 93% rename from session04/python/greetings/setup.py rename to session04/greetings/setup.py index dc5a7046..d2d313b5 100644 --- a/session04/python/greetings/setup.py +++ b/session04/greetings/setup.py @@ -1,4 +1,4 @@ -### "Whole" + from setuptools import setup, find_packages setup( @@ -7,4 +7,4 @@ packages = find_packages(exclude=['*test']), scripts = ['scripts/greet'], install_requires = ['argparse'] -) +) \ No newline at end of file diff --git a/session04/index.md b/session04/index.md new file mode 100644 index 00000000..d65dfd86 --- /dev/null +++ b/session04/index.md @@ -0,0 +1,6 @@ +--- +title: Session 4 +--- + +Some content here + diff --git a/session04/python/green.png b/session04/python/green.png deleted file mode 100644 index 51545da8..00000000 Binary files a/session04/python/green.png and /dev/null differ diff --git a/session04/python/greengraph.png b/session04/python/greengraph.png deleted file mode 100644 index 05c31b62..00000000 Binary files a/session04/python/greengraph.png and /dev/null differ diff --git a/session04/python/greengraph.py b/session04/python/greengraph.py deleted file mode 100644 index b9bc160b..00000000 --- a/session04/python/greengraph.py +++ /dev/null @@ -1,107 +0,0 @@ -### "geolocation" -import geopy -geocoder=geopy.geocoders.GoogleV3(domain="maps.google.co.uk") -def geolocate(place): - return geocoder.geocode(place,exactly_one=False)[0][1] - -london_location=geolocate("London") -print london_location - -### "URL" -import requests -def map_at(lat,long, satellite=False, zoom=12, - size=(400,400), sensor=False): - base="http://maps.googleapis.com/maps/api/staticmap?" - params=dict( - sensor= str(sensor).lower(), - zoom= zoom, - size= "x".join(map(str,size)), - center= ",".join(map(str,(lat,long))), - style="feature:all|element:labels|visibility:off" - ) - if satellite: - params["maptype"]="satellite" - return requests.get(base,params=params) - - -map_response=map_at(51.5072, -0.1275, zoom=10) -url=map_response.url -print url - -### "png" - -def is_green(r,g,b): - threshold=1.1 - return g>r*threshold and g>b*threshold - -import png -from itertools import izip - -def count_green_in_png(data): - image=png.Reader(file=StringIO(data.content)).asRGB() - count = 0 - for row in image[2]: - pixels=izip(*[iter(row)]*3) - count+=sum(1 for pixel in pixels if is_green(*pixel)) - return count - -from StringIO import StringIO -print count_green_in_png(map_at(*london_location)) - -### "visualise" - -def show_green_in_png(data): - image=png.Reader(file=StringIO(data.content)).asRGB() - count = 0 - out=[] - for row in image[2]: - outrow=[] - pixels=izip(*[iter(row)]*3) - for pixel in pixels: - outrow.append(0) - if is_green(*pixel): - outrow.append(255) - else: - outrow.append(0) - outrow.append(0) - out.append(outrow) - buffer=StringIO() - result = png.from_array(out,mode='RGB') - result.save(buffer) - return buffer.getvalue() - - - -### "points" - -from numpy import linspace -def location_sequence(start,end,steps): - # Would actually prefer this if steps - # were deduced from zoomlevel - # But need projection code for that - lats=linspace(start[0],end[0],steps) - longs=linspace(start[1],end[1],steps) - return zip(lats,longs) - -[count_green_in_png(map_at(*location,zoom=10,satellite=True)) - for location in location_sequence( - geolocate("London"), - geolocate("Birmingham"), - 10)] - - -### "save" -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -with open('green.png','w') as green: - green.write(show_green_in_png(map_at(*london_location, - zoom=10,satellite=True))) - -plt.plot([ - count_green_in_png( - map_at(*location,zoom=10,satellite=True)) - for location in location_sequence( - geolocate("London"), - geolocate("Birmingham"),10)]) -plt.savefig('greengraph.png') diff --git a/session04/python/greeter.py b/session04/python/greeter.py deleted file mode 100755 index 43781e60..00000000 --- a/session04/python/greeter.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -from argparse import ArgumentParser -if __name__ == "__main__": - parser = ArgumentParser(description = "Generate appropriate greetings") - parser.add_argument('--title', '-t') - parser.add_argument('--polite','-p', action="store_true") - parser.add_argument('personal') - parser.add_argument('family') - arguments= parser.parse_args() - greeting= "How do you do, " if arguments.polite else "Hey, " - if arguments.title: - greeting+=arguments.title+" " - greeting+= arguments.personal + " " + arguments.family +"." - print greeting diff --git a/session04/python/greetings.sh b/session04/python/greetings.sh deleted file mode 100755 index 4151da4f..00000000 --- a/session04/python/greetings.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -chmod u+x greeter.py -./greeter.py --help -./greeter.py James Hetherington -./greeter.py --polite James Hetherington -./greeter.py James Hetherington --title Dr diff --git a/session04/python/greetings/Greetings.egg-info/PKG-INFO b/session04/python/greetings/Greetings.egg-info/PKG-INFO deleted file mode 100644 index 02d0fa7f..00000000 --- a/session04/python/greetings/Greetings.egg-info/PKG-INFO +++ /dev/null @@ -1,10 +0,0 @@ -Metadata-Version: 1.0 -Name: Greetings -Version: 0.1 -Summary: UNKNOWN -Home-page: UNKNOWN -Author: UNKNOWN -Author-email: UNKNOWN -License: UNKNOWN -Description: UNKNOWN -Platform: UNKNOWN diff --git a/session04/python/greetings/Greetings.egg-info/SOURCES.txt b/session04/python/greetings/Greetings.egg-info/SOURCES.txt deleted file mode 100644 index ce706af2..00000000 --- a/session04/python/greetings/Greetings.egg-info/SOURCES.txt +++ /dev/null @@ -1,10 +0,0 @@ -setup.py -Greetings.egg-info/PKG-INFO -Greetings.egg-info/SOURCES.txt -Greetings.egg-info/dependency_links.txt -Greetings.egg-info/requires.txt -Greetings.egg-info/top_level.txt -greetings/__init__.py -greetings/command.py -greetings/greeter.py -scripts/greet \ No newline at end of file diff --git a/session04/python/greetings/Greetings.egg-info/dependency_links.txt b/session04/python/greetings/Greetings.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/session04/python/greetings/Greetings.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/session04/python/greetings/Greetings.egg-info/requires.txt b/session04/python/greetings/Greetings.egg-info/requires.txt deleted file mode 100644 index 1352d5e6..00000000 --- a/session04/python/greetings/Greetings.egg-info/requires.txt +++ /dev/null @@ -1 +0,0 @@ -argparse diff --git a/session04/python/greetings/Greetings.egg-info/top_level.txt b/session04/python/greetings/Greetings.egg-info/top_level.txt deleted file mode 100644 index 070e4c8f..00000000 --- a/session04/python/greetings/Greetings.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -greetings diff --git a/session04/python/greetings/conf.py b/session04/python/greetings/conf.py deleted file mode 100644 index 9dda5564..00000000 --- a/session04/python/greetings/conf.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Greetings documentation build configuration file, created by -# sphinx-quickstart on Fri Oct 24 16:06:07 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' -### "Extensions" -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', # Support automatic documentation - 'sphinx.ext.coverage', # Automatically check if functions are documented - 'sphinx.ext.mathjax', # Allow support for algebra - 'sphinx.ext.viewcode', # Include the source code in documentation - 'numpydoc' # Support NumPy style docstrings -] -### "Extensions" -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Greetings' -copyright = u'2014, James Hetherington' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.1' -# The full version, including alpha/beta/rc tags. -release = '0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Greetingsdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'Greetings.tex', u'Greetings Documentation', - u'James Hetherington', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'greetings', u'Greetings Documentation', - [u'James Hetherington'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Greetings', u'Greetings Documentation', - u'James Hetherington', 'Greetings', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/session04/python/greetings_installed.sh b/session04/python/greetings_installed.sh deleted file mode 100755 index 027eab42..00000000 --- a/session04/python/greetings_installed.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -greet --help -greet James Hetherington -greet --polite James Hetherington -greet James Hetherington --title Dr - diff --git a/session04/python/map.png b/session04/python/map.png deleted file mode 100644 index 81ceafda..00000000 Binary files a/session04/python/map.png and /dev/null differ diff --git a/session04/python/setup_greetings_module.sh b/session04/python/setup_greetings_module.sh deleted file mode 100644 index d46bbead..00000000 --- a/session04/python/setup_greetings_module.sh +++ /dev/null @@ -1,4 +0,0 @@ -touch greetings/greetings/test/__init__.py -touch greetings/greetings/__init__.py -chmod u+x greetings/greetings/command.py -# chmod u+x greetings/scripts/greet diff --git a/session04/python/system.py b/session04/python/system.py deleted file mode 100644 index 5322892a..00000000 --- a/session04/python/system.py +++ /dev/null @@ -1,20 +0,0 @@ -### "paths" -from os.path import join, dirname, abspath -# Load the data file from session 1 -datapath=join(dirname(dirname(dirname(abspath(__file__)))), - 'session01', 'data', 'inflammation-01.csv') -newpath=join(dirname(__file__), 'midvals.yaml') - -### "files" -source = open(datapath) -import csv -reader = csv.reader(source) -midvals = [row[len(row)/2] for row in reader] -print midvals -source.close() - -### "context" -import yaml -with open(newpath, 'w') as yamlfile: - yaml.dump(midvals, yamlfile) - diff --git a/session04/python/tree.sh b/session04/python/tree.sh deleted file mode 100644 index 5331171b..00000000 --- a/session04/python/tree.sh +++ /dev/null @@ -1 +0,0 @@ -tree --charset ascii greetings diff --git a/session04/python/web.py b/session04/python/web.py deleted file mode 100644 index ad7561ec..00000000 --- a/session04/python/web.py +++ /dev/null @@ -1,25 +0,0 @@ -### "URL" - -import requests -ucl=requests.get( - 'http://en.wikipedia.org/wiki/University_College_London' -) -map_here=requests.get('http://maps.googleapis.com/maps/api/staticmap', - params={'center':"51.5, -0.1", 'size':"400x400", 'zoom':10}) -print ucl.url -print map_here.url - -### "download" -print "Beginning of HTML file", ucl.text.split()[0:2] -with open('map.png','w') as map_image: - map_image.write(map_here.content) - -### "parse" -# pip install beautifulsoup4 -from bs4 import BeautifulSoup -import re # Python regular expression library -wikipage=BeautifulSoup(ucl.text) -# HTML looks like value -motto=wikipage.find(text=re.compile("Motto")) - -#print 'Coordinates:', latitude, longitude diff --git a/session04/session04.ipynb b/session04/session04.ipynb index de14d702..44b5f8f7 100644 --- a/session04/session04.ipynb +++ b/session04/session04.ipynb @@ -1,1543 +1,1911 @@ { - "metadata": { - "name": null - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n---\ntitle: Research Software Engineering with Python\nauthor: James Hetherington and Matt Clarkson\n---\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Libraries" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Libraries are awesome" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe strength of a language lies as much in the set of libraries available, as it does\nin the language itself.\n\nA great set of libraries allows for a very powerful programming style:\n\n* Write minimal code yourself\n* Choose the right libraries\n* Plug them together\n* Create impressive results\n\nNot only is this efficient with your programming time, it's also more efficient with computer\ntime.\n\nThe chances are any algorithm you might want to use has already been programmed better by someone else.\n\nThis lecture is available as an [IPython Notebook](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session04/../python/session04.ipynb)\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Careful use of Libraries" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Drawbacks of libraries." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Sometimes, libraries are not looked after by their creator: code that is not maintained *rots*:\n * It no longer works with later versions of *upstream* libraries.\n * It doesn't work on newer platforms or systems.\n * Features that are needed now, because the field has moved on, are not added\n\n* Sometimes, libraries are hard to get working:\n * For libraries in pure python, this is almost never a problem\n * But many libraries involve *compiled components*: these can be hard to install.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Contribute, don't duplicate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* You have a duty to the ecosystem of scholarly software:\n * If there's a tool or algorithm you need, find a project which provides it.\n * If there are features missing, or problems with it, fix them, [don't create your own](http://xkcd.com/927/) library.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "How to choose a library" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* When was the last commit?\n* How often are there commits?\n* Can you find the lead contributor on the internet?\n* Do they respond when approached:\n * emails to developer list\n * personal emails\n * tweets\n * [irc](https://freenode.net)\n * issues raised on GitHub?\n* Are there contributors other than the lead contributor?\n* Is there discussion of the library on Stack Exchange?\n* Is the code on an open version control tool like GitHub?\n* Is it on standard package repositories. (PyPI, apt/yum/brew)\n* Are there any tests?\n* Download it. Can you build it? Do the tests pass?\n* Is there an open test dashboard? (Travis/Jenkins/CDash)\n* What dependencies does the library itself have? Do they pass this list?\n* Are different versions of the library clearly labeled with version numbers?\n* Is there a changelog?\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Sensible Version Numbering" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe best approach to version numbers clearly distinguishes kinds of change:\n\nGiven a version number MAJOR.MINOR.PATCH, e.g. 2.11.14 increment the:\n\n* MAJOR version when you make incompatible API changes,\n* MINOR version when you add functionality in a backwards-compatible manner, and\n* PATCH version when you make backwards-compatible bug fixes.\n\nThis is called [Semantic Versioning](http://semver.org)\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Python Libraries" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "The Python Standard Library" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nPython comes with a powerful [standard library](https://docs.python.org/2/library/).\n\nLearning python is as much about learning this library as learning the language itself.\n\nYou've already seen a few packages in this library: `math`, `pdb`, `pytest`, `datetime`.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "The Python Package Index" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nPython's real power, however, comes with the Python Package Index: [PyPI](https://pypi.python.org/pypi).\nThis is a huge array of libraries, with all kinds of capabilities, all easily installable from the \ncommand line or through your Python distribution.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Pip" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nPackages from PyPI are installed using Pip.\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "pip list # See what you have installed\npip search # Search PyPI for a package\nsudo pip install # install a package\nsudo pip install --upgrade # upgrade\nsudo pip uninstall " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Some libraries" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Argparse" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThis is the standard library for building programs with a command-line interface.\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greeter.py", - "#!/usr/bin/env python", - "from argparse import ArgumentParser", - "if __name__ == \"__main__\":", - " parser = ArgumentParser(description = \"Generate appropriate greetings\")", - " parser.add_argument('--title', '-t')", - " parser.add_argument('--polite','-p', action=\"store_true\")", - " parser.add_argument('personal')", - " parser.add_argument('family')", - " arguments= parser.parse_args()", - " greeting= \"How do you do, \" if arguments.polite else \"Hey, \"", - " if arguments.title:", - " greeting+=arguments.title+\" \"", - " greeting+= arguments.personal + \" \" + arguments.family +\".\"", - " print greeting", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash", - "#!/usr/bin/env bash", - "chmod u+x greeter.py", - "./greeter.py --help", - "./greeter.py James Hetherington", - "./greeter.py --polite James Hetherington", - "./greeter.py James Hetherington --title Dr", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Operating system paths and files" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWhen loading and saving files, standard libraries allow you to manage file names,\nin an operating-system independent way:\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "__file__ = \"session04.ipynb\" # No __file__ in notebook!" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from os.path import join, dirname, abspath", - "# Load the data file from session 1", - "datapath=join(dirname(dirname(dirname(abspath(__file__)))),", - " 'session01', 'data', 'inflammation-01.csv')", - "newpath=join(dirname(__file__), 'midvals.yaml')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAnd you'll want to be able to read and write to files:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "source = open(datapath)", - "import csv", - "reader = csv.reader(source)", - "midvals = [row[len(row)/2] for row in reader]", - "print midvals", - "source.close()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nNote the use of the `csv` library to read csv files as well.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Context managers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThere's a better way to handle opening and closing files\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import yaml", - "with open(newpath, 'w') as yamlfile:", - " yaml.dump(midvals, yamlfile)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis syntax using `with` is called a context manager.\nIt is used when a library wants stuff to happen both before **and** after client code is called.\n\nHere, [`yaml`](http://www.yaml.org) is another standard file format for data files similar to XML or CSV.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Working with web resources" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nTo interact with resources on the web, you need a way to work with URLs: escaping characters that\ncan't appear in URLs, composing ?foo=bar web argument strings and so on.\n\nWe can use the [requests](http://docs.python-requests.org/en/latest/) library from PyPI for this.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import requests", - "ucl=requests.get(", - " 'http://en.wikipedia.org/wiki/University_College_London'", - ")", - "map_here=requests.get('http://maps.googleapis.com/maps/api/staticmap',", - " params={'center':\"51.5, -0.1\", 'size':\"400x400\", 'zoom':10})", - "print ucl.url", - "print map_here.url", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAnd you can download files from the web, accessing headers and the body of the response:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print \"Beginning of HTML file\", ucl.text.split()[0:2]", - "with open('map.png','w') as map_image:", - " map_image.write(map_here.content)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nLibraries even allow you to parse HTML content, to find the data you want within a page:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "# pip install beautifulsoup4", - "from bs4 import BeautifulSoup", - "import re # Python regular expression library", - "wikipage=BeautifulSoup(ucl.text)", - "# HTML looks like value", - "motto=wikipage.find(text=re.compile(\"Motto\"))", - "", - "#print 'Coordinates:', latitude, longitude", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import IPython", - "IPython.core.display.Image(map_here.content)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Graph of Green Spaces" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "The problem" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nLet's look at an extended example of using libraries to work with Python to analyse data.\n\nWe'd like to know how density of green space varies as we move from city centre to the countryside:\n\n* Find the location of two places by name\n* Obtain maps or satellite images of the geography at points between them\n * In this toy example: just evenly divide the range\n* Determine the proportion of the images that are parkland\n * In this toy example: bits that are green!\n* Plot a graph\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Geolocation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nGoogle provides a service to go from \"London\" to 51.51N, 0.1275W. Fortunately, there's a very nice library\non PyPI to access it: `pip install geopy`\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import geopy", - "geocoder=geopy.geocoders.GoogleV3(domain=\"maps.google.co.uk\")", - "def geolocate(place):", - " return geocoder.geocode(place,exactly_one=False)[0][1]", - "", - "london_location=geolocate(\"London\")", - "print london_location", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Addressing the google maps API" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nGoogle maps has a static API to obtain satellite images with URLs like this:\n\n[http://maps.googleapis.com/maps/api/staticmap?style=feature:all|element:labels|visibility:off&size=400x400&sensor=false&zoom=10¢er=51.5072,-0.1275](http://maps.googleapis.com/maps/api/staticmap?style=feature:all|element:labels|visibility:off&size=400x400&sensor=false&zoom=10¢er=51.5072,-0.1275)\n\nWe'll therefore need to use a library to build this URL, and fetch the result\n\n`sudo pip install pypng` will get you this library.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import requests", - "def map_at(lat,long, satellite=False, zoom=12, ", - " size=(400,400), sensor=False):", - " base=\"http://maps.googleapis.com/maps/api/staticmap?\"", - " params=dict(", - " sensor= str(sensor).lower(),", - " zoom= zoom,", - " size= \"x\".join(map(str,size)),", - " center= \",\".join(map(str,(lat,long))),", - " style=\"feature:all|element:labels|visibility:off\"", - " )", - " if satellite:", - " params[\"maptype\"]=\"satellite\"", - " return requests.get(base,params=params)", - "", - "", - "map_response=map_at(51.5072, -0.1275, zoom=10)", - "url=map_response.url", - "print url", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import IPython", - "map_png=map_at(*london_location)", - "IPython.core.display.Image(map_png.content)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Finding the green bits" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe'll need a library to parse `.png` image files and determine which bits are green:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def is_green(r,g,b):", - " threshold=1.1", - " return g>r*threshold and g>b*threshold", - "", - "import png", - "from itertools import izip", - "", - "def count_green_in_png(data):", - " image=png.Reader(file=StringIO(data.content)).asRGB()", - " count = 0", - " for row in image[2]:", - " pixels=izip(*[iter(row)]*3)", - " count+=sum(1 for pixel in pixels if is_green(*pixel))", - " return count", - "", - "from StringIO import StringIO", - "print count_green_in_png(map_at(*london_location))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Checking our code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe could write some unit tests, but for something like this, visualisation is the key to\nverification. Let's look which bits are green by building a new png.\n\nWe could write the new png to disk, but we can use `StringIO` to get a string in memory which\nbehaves like a file:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def show_green_in_png(data):", - " image=png.Reader(file=StringIO(data.content)).asRGB()", - " count = 0", - " out=[]", - " for row in image[2]:", - " outrow=[]", - " pixels=izip(*[iter(row)]*3)", - " for pixel in pixels:", - " outrow.append(0)", - " if is_green(*pixel):", - " outrow.append(255)", - " else:", - " outrow.append(0)", - " outrow.append(0)", - " out.append(outrow)", - " buffer=StringIO()", - " result = png.from_array(out,mode='RGB')", - " result.save(buffer)", - " return buffer.getvalue()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "IPython.core.display.Image(show_green_in_png(map_at(*london_location,satellite=True)))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Points in between" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe need all the points equally spaced between two locations.\nNumpy has a routine for just this:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from numpy import linspace", - "def location_sequence(start,end,steps):", - " # Would actually prefer this if steps", - " # were deduced from zoomlevel", - " # But need projection code for that", - " lats=linspace(start[0],end[0],steps)", - " longs=linspace(start[1],end[1],steps)", - " return zip(lats,longs)", - "", - "[count_green_in_png(map_at(*location,zoom=10,satellite=True))", - " for location in location_sequence(", - " geolocate(\"London\"),", - " geolocate(\"Birmingham\"),", - " 10)]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "The results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import matplotlib.pyplot as plt", - "%matplotlib inline", - "plt.plot([count_green_in_png(get_map(*location,zoom=10,satellite=True))", - " for location in location_sequence(geolocate(\"London\"),geolocate(\"Birmingham\"),10)])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Packaging" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Packaging" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nOnce we've made a working program, we'd like to be able to share it with others.\n\nA good cross-platform build tool is the most important thing: you can always\nhave collaborators build from source.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Distribution tools" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nDistribution tools allow one to obtain a working copy of someone else's package.\n\nLanguage-specific tools: PyPI, Ruby Gems, CPAN, CRAN\nPlatform specific packagers e.g. brew, apt/yum\n\nWindows doesn't have anything like `brew install` or `apt-get`\nYou have to build an 'installer'.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Laying out a project" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWhen planning to package a project for distribution, defining a suitable\nproject layout is essential.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash", - "tree --charset ascii greetings", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Using setuptools" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nTo make python code into a package, we have to write a `setupfile`:\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/setup.py", - "", - "", - "from setuptools import setup, find_packages", - "", - "setup(", - " name = \"Greetings\",", - " version = \"0.1\",", - " packages = find_packages(exclude=['*test']),", - " scripts = ['scripts/greet'],", - " install_requires = ['argparse']", - ")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can now install this code with\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "sudo python setup.py install" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAnd the package will be then available to use everywhere on the system:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import greetings", - "from greetings.greeter import greet", - "print greetings.greeter.greet(\"James\",\"Hetherington\")" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAnd the scripts are now available as command line commands:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash", - "#!/usr/bin/env bash", - "greet --help", - "greet James Hetherington", - "greet --polite James Hetherington", - "greet James Hetherington --title Dr", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Installing from GitHub" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe could now submit \"greeter\" to PyPI for approval, so everyone could `pip install` it.\n\nHowever, when using git, we don't even need to do that: we can install directly from any git URL:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "sudo pip install git+git://github.com/jamespjh/greeter\ngreet Humphry Appleby --title Sir" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nTry it!\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Convert the script to a module" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nOf course, there's more to do when taking code from a quick script and turning it into a proper module:\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/greetings/greeter.py", - "", - "", - "def greet(personal, family, title=\"\", polite=False):", - "", - " \"\"\" Generate a greeting string for a person.", - "", - " Parameters", - " ----------", - " personal: str", - " A given name, such as Will or Jean-Luc", - " family: str", - " A family name, such as Riker or Picard", - " title: str", - " An optional title, such as Captain or Reverend", - " polite: bool", - " True for a formal greeting, False for informal.", - "", - " Returns", - " -------", - " string", - " An appropriate greeting", - " \"\"\"", - "", - " greeting= \"How do you do, \" if polite else \"Hey, \"", - " if title:", - " greeting+=title+\" \"", - "", - " greeting+= personal + \" \" + family +\".\"", - " return greeting", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThe documentation string explains how to use the function; don't worry about this for now, we'll consider\nthis next time.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write an executable script" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/greetings/command.py", - "from argparse import ArgumentParser", - "from greeter import greet", - "", - "def process():", - " parser = ArgumentParser(description = \"Generate appropriate greetings\")", - "", - " parser.add_argument('--title', '-t')", - " parser.add_argument('--polite', '-p', action=\"store_true\")", - " parser.add_argument('personal')", - " parser.add_argument('family')", - "", - " arguments= parser.parse_args()", - "", - " print greet(arguments.personal, arguments.family, arguments.title, arguments.polite)", - "", - "if __name__ == \"__main__\":", - " process()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write an entry point script stub" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/greetings/greet", - "", - "#!/usr/bin/env python", - "from greetings.command import process", - "process()", - "", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write a readme file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "e.g.:" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "Greetings!\n==========\n\nThis is a very simple example package used as part of the UCL\n[Research Software Engineering with Python](development.rc.ucl.ac.uk/training/engineering) course.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write a license file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "e.g.:" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "(C) University College London 2014\n\nThis \"greetings\" example package is granted into the public domain.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write a citation file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "e.g.:" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "If you wish to refer to this course, please cite the URL\nhttp://development.rc.ucl.ac.uk/training/engineering\n\nPortions of the material are taken from Software Carpentry\nhttp://swcarpentry.org\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Define packages and executables" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash", - "touch greetings/greetings/test/__init__.py", - "touch greetings/greetings/__init__.py", - "chmod u+x greetings/greetings/command.py", - "# chmod u+x greetings/scripts/greet", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write some unit tests" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSeparating the script from the logical module made this possible:\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/greetings/test/test_greeter.py", - "import yaml", - "import os", - "from ..greeter import greet", - "from nose.tools import assert_equal", - "def test_greeter():", - " with open(os.path.join(os.path.dirname(__file__),'fixtures','samples.yaml')) as fixtures_file:", - " fixtures=yaml.load(fixtures_file)", - " for fixture in fixtures:", - " answer=fixture.pop('answer')", - " assert_equal(greet(**fixture), answer)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAdd a fixtures file:\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/greetings/test/fixtures/samples.yaml", - "- personal: James", - " family: Hetherington", - " answer: \"Hey, James Hetherington.\"", - "- personal: James", - " family: Hetherington", - " polite: True", - " answer: \"How do you do, James Hetherington.\"", - "- personal: James", - " family: Hetherington", - " title: Dr", - " answer: \"Hey, Dr James Hetherington.\"", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Developer Install" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIf you modify your source files, you would now find it appeared as if the program doesn't change.\n\nThat's because pip install **copies** the file.\n\n(On my system to /Library/Python/2.7/site-packages/: this is operating\nsystem dependent.)\n\nIf you want to install a package, but keep working on it, you can do\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "sudo python setup.py develop" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Distributing compiled code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIf you're working in C++ or Fortran, there is no language specific repository.\nYou'll need to write platform installers for as many platforms as you want to\nsupport.\n\nTypically:\n\n* `dpkg` for `apt-get` on Ubuntu and Debian\n* `rpm` for `yum` on Redhat and Fedora\n* `homebrew` on OSX (Possibly `macports` as well)\n* An executable `msi` installer for Windows.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Homebrew" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nHomebrew: A ruby DSL, you host off your own webpage\n\nSee my [installer for the cppcourse example](http://github.com/jamespjh/homebrew-reactor)\n\nIf you're on OSX, do:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "brew tap jamespjh/homebrew-reactor\nbrew install reactor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Exercises" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe previously looked at Greengraph.py, a script that enables us to explore how green space varies as we move from the city to the countryside:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "### \"geolocation\"", - "import geopy", - "geocoder=geopy.geocoders.GoogleV3(domain=\"maps.google.co.uk\")", - "", - "def geolocate(place):", - " return geocoder.geocode(place,exactly_one=False)[0][1]", - "", - "london_location=geolocate(\"London\")", - "print london_location", - "", - "..." - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe Greengraph example is only available as a single large [script](https://github.com/UCL/rsd-engineeringcourse/blob/master/../python/greengraph.py).\n\nYour task is to transform this into a python package that can be pip installed directly from GitHub. Remember to include:\n\n- an \\__init\\__.py file\n- a setup.py file\n- tests\n- license and documentation" - ] - } - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Libraries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Libraries are awesome" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The strength of a language lies as much in the set of libraries available, as it does\n", + "in the language itself.\n", + "\n", + "A great set of libraries allows for a very powerful programming style:\n", + "\n", + "* Write minimal code yourself\n", + "* Choose the right libraries\n", + "* Plug them together\n", + "* Create impressive results\n", + "\n", + "Not only is this efficient with your programming time, it's also more efficient with computer\n", + "time.\n", + "\n", + "The chances are any algorithm you might want to use has already been programmed better by someone else.\n", + "\n", + "This lecture is available as an [IPython Notebook](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session04/../python/session04.ipynb)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Careful use of Libraries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Drawbacks of libraries." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Sometimes, libraries are not looked after by their creator: code that is not maintained *rots*:\n", + " * It no longer works with later versions of *upstream* libraries.\n", + " * It doesn't work on newer platforms or systems.\n", + " * Features that are needed now, because the field has moved on, are not added\n", + "\n", + "* Sometimes, libraries are hard to get working:\n", + " * For libraries in pure python, this is almost never a problem\n", + " * But many libraries involve *compiled components*: these can be hard to install.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contribute, don't duplicate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* You have a duty to the ecosystem of scholarly software:\n", + " * If there's a tool or algorithm you need, find a project which provides it.\n", + " * If there are features missing, or problems with it, fix them, [don't create your own](http://xkcd.com/927/) library.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How to choose a library" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* When was the last commit?\n", + "* How often are there commits?\n", + "* Can you find the lead contributor on the internet?\n", + "* Do they respond when approached:\n", + " * emails to developer list\n", + " * personal emails\n", + " * tweets\n", + " * [irc](https://freenode.net)\n", + " * issues raised on GitHub?\n", + "* Are there contributors other than the lead contributor?\n", + "* Is there discussion of the library on Stack Exchange?\n", + "* Is the code on an open version control tool like GitHub?\n", + "* Is it on standard package repositories. (PyPI, apt/yum/brew)\n", + "* Are there any tests?\n", + "* Download it. Can you build it? Do the tests pass?\n", + "* Is there an open test dashboard? (Travis/Jenkins/CDash)\n", + "* What dependencies does the library itself have? Do they pass this list?\n", + "* Are different versions of the library clearly labeled with version numbers?\n", + "* Is there a changelog?\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sensible Version Numbering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The best approach to version numbers clearly distinguishes kinds of change:\n", + "\n", + "Given a version number MAJOR.MINOR.PATCH, e.g. 2.11.14 increment the:\n", + "\n", + "* MAJOR version when you make incompatible API changes,\n", + "* MINOR version when you add functionality in a backwards-compatible manner, and\n", + "* PATCH version when you make backwards-compatible bug fixes.\n", + "\n", + "This is called [Semantic Versioning](http://semver.org)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Python Libraries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Python Standard Library" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Python comes with a powerful [standard library](https://docs.python.org/2/library/).\n", + "\n", + "Learning python is as much about learning this library as learning the language itself.\n", + "\n", + "You've already seen a few packages in this library: `math`, `pdb`, `pytest`, `datetime`.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Python Package Index" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Python's real power, however, comes with the Python Package Index: [PyPI](https://pypi.python.org/pypi).\n", + "This is a huge array of libraries, with all kinds of capabilities, all easily installable from the \n", + "command line or through your Python distribution.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pip" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Packages from PyPI are installed using Pip.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "pip list # See what you have installed\n", + "pip search geopy # Search PyPI for a package\n", + "sudo pip install # install a package\n", + "sudo pip install --upgrade # upgrade\n", + "sudo pip uninstall \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Some libraries" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Argparse" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "This is the standard library for building programs with a command-line interface.\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greeter.py\n" + ] + } + ], + "source": [ + "%%writefile greeter.py\n", + "#!/usr/bin/env python\n", + "from argparse import ArgumentParser\n", + "if __name__ == \"__main__\":\n", + " parser = ArgumentParser(description = \"Generate appropriate greetings\")\n", + " parser.add_argument('--title', '-t')\n", + " parser.add_argument('--polite','-p', action=\"store_true\")\n", + " parser.add_argument('personal')\n", + " parser.add_argument('family')\n", + " arguments= parser.parse_args()\n", + " greeting= \"How do you do, \" if arguments.polite else \"Hey, \"\n", + " if arguments.title:\n", + " greeting+=arguments.title+\" \"\n", + " greeting+= arguments.personal + \" \" + arguments.family +\".\"\n", + " print greeting\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "usage: greeter.py [-h] [--title TITLE] [--polite] personal family\n", + "\n", + "Generate appropriate greetings\n", + "\n", + "positional arguments:\n", + " personal\n", + " family\n", + "\n", + "optional arguments:\n", + " -h, --help show this help message and exit\n", + " --title TITLE, -t TITLE\n", + " --polite, -p\n", + "Hey, James Hetherington.\n", + "How do you do, James Hetherington.\n", + "Hey, Dr James Hetherington.\n" + ] + } + ], + "source": [ + "%%bash\n", + "#!/usr/bin/env bash\n", + "chmod u+x greeter.py\n", + "./greeter.py --help\n", + "./greeter.py James Hetherington\n", + "./greeter.py --polite James Hetherington\n", + "./greeter.py James Hetherington --title Dr\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Operating system paths and files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "When loading and saving files, standard libraries allow you to manage file names,\n", + "in an operating-system independent way:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "__file__ = \"session04.ipynb\" # No __file__ in notebook!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from os.path import join, dirname, abspath\n", + "# Load the data file from session 1\n", + "datapath=join(dirname(dirname(abspath(__file__))),\n", + " 'session01', 'data', 'inflammation-01.csv')\n", + "newpath=join(dirname(__file__), 'midvals.yaml')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "And you'll want to be able to read and write to files:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['6', '18', '19', '17', '9', '12', '9', '20', '6', '18', '8', '9', '13', '15', '12', '9', '13', '13', '7', '9', '18', '12', '16', '14', '17', '7', '9', '15', '20', '14', '13', '19', '11', '10', '14', '15', '16', '15', '19', '15', '18', '19', '12', '18', '6', '14', '19', '10', '12', '16', '5', '20', '11', '15', '16', '16', '14', '9', '8', '6']\n" + ] + } + ], + "source": [ + "source = open(datapath)\n", + "import csv\n", + "reader = csv.reader(source)\n", + "midvals = [row[len(row)/2] for row in reader]\n", + "print midvals\n", + "source.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Note the use of the `csv` library to read csv files as well.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Context managers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There's a better way to handle opening and closing files\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import yaml\n", + "with open(newpath, 'w') as yamlfile:\n", + " yaml.dump(midvals, yamlfile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This syntax using `with` is called a context manager.\n", + "It is used when a library wants stuff to happen both before **and** after client code is called.\n", + "\n", + "Here, [`yaml`](http://www.yaml.org) is another standard file format for data files similar to XML or CSV.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working with web resources" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "To interact with resources on the web, you need a way to work with URLs: escaping characters that\n", + "can't appear in URLs, composing ?foo=bar web argument strings and so on.\n", + "\n", + "We can use the [requests](http://docs.python-requests.org/en/latest/) library from PyPI for this.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://en.wikipedia.org/wiki/University_College_London\n", + "http://maps.googleapis.com/maps/api/staticmap?size=400x400&zoom=10¢er=51.5%2C+-0.1\n" + ] + } + ], + "source": [ + "\n", + "import requests\n", + "ucl=requests.get(\n", + " 'http://en.wikipedia.org/wiki/University_College_London'\n", + ")\n", + "map_here=requests.get('http://maps.googleapis.com/maps/api/staticmap',\n", + " params={'center':\"51.5, -0.1\", 'size':\"400x400\", 'zoom':10})\n", + "print ucl.url\n", + "print map_here.url\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "And you can download files from the web, accessing headers and the body of the response:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Beginning of HTML file [u'']\n" + ] + } + ], + "source": [ + "\n", + "print \"Beginning of HTML file\", ucl.text.split()[0:2]\n", + "with open('map.png','w') as map_image:\n", + " map_image.write(map_here.content)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Libraries even allow you to parse HTML content, to find the data you want within a page:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# pip install beautifulsoup4\n", + "from bs4 import BeautifulSoup\n", + "import re # Python regular expression library\n", + "wikipage=BeautifulSoup(ucl.text)\n", + "# HTML looks like value\n", + "motto=wikipage.find(text=re.compile(\"Motto\"))\n", + "\n", + "#print 'Coordinates:', latitude, longitude" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAADAFBMVEUBAQAKCgodGA4SEhIYFxcb\nHBowHwEgIB82IgcyKhEnKCYsMCozLiExNC89NSI8PTpDOSRCQT5CSD9KQzFWTj1VfTVzSBFFREJG\nSkNIR0VKS0ZNTEpQTkpTVE5WWFJkXEtgXVhlYl1ial1rZFVya1pqaWZ0cW18dWd6eHRvb8hahDhj\nkj12oz9/pztrnkJ2gHCBfnqaoR2IqzaRrzOktyutuia2viKGgHOEiX+eiWSDoEGWpX6okEzGqwDk\nkwfqlg/umBTzmxzkniL6niXmqD3ApUrJq1blvU3nvHLIxhrdwwTSyhbbzRHk0Q3t1Qn22QX/3ADc\nwlrYznznxljrxVPpxVruyVbvy1jxzlr1017lxGjoxWHox2rmx3HmyXn412L722T113n12HX/4WiJ\niISNl4eRjICTj4iUkImTn4KSmomZlYuZlpCZpImZppScvoKgn5WgpJajop6hqpuppJmjsouotpqx\nvpqlpKCqqaSvrqqtt6WwrKe0sKWzsa22vKC4v6S+uK+3trG9ubGBgc+Tk9aMtf2Ruf2Zvv20xKy8\nwqi7zqy1xbK8wri8zLOcwf2xwdekxv6nyP6szP64yumwz/6y0P7CtIXCvrfIxpnSwIfRxZfcyZnV\n04jT15fFwbfDwbzBzbnMxrvNzbbOyL3J3qrD07rH2L7M07LM07vN3LLJ2bzZyqbRyr/Y0LLM4K7O\n4LHR4bXT4rznzIPnz4zn0ZXo1J3o1qfo2bDo27n/54X/6Y704ZPy4qz86qfz5rn/8LTKy8TM1sTO\n08zL28HI3MjTzcLSz8nV0cXW0sjR3cXT3MvY0sfa1Mrd3cXc2M3W1dHU3NDY1tPe2dHf3dnI1enT\n2uXO4MTR4sbS5Mja4sPd5Mrf68rV4NDb5tPb4tjd6NTZ4u7p3sLh3NPg3tng5M3p4Mrk4tTl4drl\n7dTj6trq49Lo5Nvo7Nro8dz06cn/9MT/+Nrk5eLl7OLq5+Ls6uTs7Ont8enw7un08+fz8e719PL4\n9/T5+Pf9/f3///9w8TnPAAAAAWJLR0T/pQfyxQAAIABJREFUeNqEvctrG1n6P+y/oAkk+yxiCAMz\nWcwtzCZ2EoMzfLtnmmAnttM2agzZeJttVj2ThNkk/Rszs+h0CLqAFyl08b2bbnqjQ9nooEXhgKSx\n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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import IPython\n", + "IPython.core.display.Image(map_here.content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Graph of Green Spaces" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The problem" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Let's look at an extended example of using libraries to work with Python to analyse data.\n", + "\n", + "We'd like to know how density of green space varies as we move from city centre to the countryside:\n", + "\n", + "* Find the location of two places by name\n", + "* Obtain maps or satellite images of the geography at points between them\n", + " * In this toy example: just evenly divide the range\n", + "* Determine the proportion of the images that are parkland\n", + " * In this toy example: bits that are green!\n", + "* Plot a graph\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geolocation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Google provides a service to go from \"London\" to 51.51N, 0.1275W. Fortunately, there's a very nice library\n", + "on PyPI to access it: `pip install geopy`\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:geopy:BeautifulSoup was not found. The SemanticMediaWiki geocoder will not work.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(51.5073509, -0.1277583)\n" + ] + } + ], + "source": [ + "import geopy\n", + "geocoder=geopy.geocoders.GoogleV3(domain=\"maps.google.co.uk\")\n", + "def geolocate(place):\n", + " return geocoder.geocode(place,exactly_one=False)[0][1]\n", + "\n", + "london_location=geolocate(\"London\")\n", + "print london_location" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Addressing the google maps API" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Google maps has a static API to obtain satellite images with URLs like this:\n", + "\n", + "[http://maps.googleapis.com/maps/api/staticmap?size=400x400&sensor=false&zoom=10¢er=51.5072,-0.1275](http://maps.googleapis.com/maps/api/staticmap?size=400x400&sensor=false&zoom=10¢er=51.5072,-0.1275)\n", + "\n", + "We'll therefore need to use a library to build this URL, and fetch the result\n", + "\n", + "`sudo pip install pypng` will get you this library.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "http://maps.googleapis.com/maps/api/staticmap?style=feature%3Aall%7Celement%3Alabels%7Cvisibility%3Aoff¢er=51.5072%2C-0.1275&sensor=false&zoom=10&size=400x400\n" + ] + } + ], + "source": [ + "import requests\n", + "def map_at(lat,long, satellite=False, zoom=12, \n", + " size=(400,400), sensor=False):\n", + " base=\"http://maps.googleapis.com/maps/api/staticmap?\"\n", + " params=dict(\n", + " sensor= str(sensor).lower(),\n", + " zoom= zoom,\n", + " size= \"x\".join(map(str,size)),\n", + " center= \",\".join(map(str,(lat,long))),\n", + " style=\"feature:all|element:labels|visibility:off\"\n", + " )\n", + " if satellite:\n", + " params[\"maptype\"]=\"satellite\"\n", + " return requests.get(base,params=params)\n", + "\n", + "\n", + "map_response=map_at(51.5072, -0.1275, zoom=10)\n", + "url=map_response.url\n", + "print url\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAADAFBMVEUAAAAKCgkPEQ0REg4TExMb\nHhccHBogHx4hIB8lJCMoKCYtLCoxMC83PC40MzE6ODY/Pjw7QjJDQj9DSjlEQ0JIR0RJSEVOTkxR\nUE5XVVJYVlNaWVVdZk5gZ1RkblRlZGBta2dubGh4dnF8enRqiLlykcV4mM98ntd+oNqAfnmEgXyM\nm3aXp3+mkVO0nFmkkmCnnH2/pUq/pFa0pWewoHjTslfSuVbdvlXZv1nDrG7BrnzXvnrhu07lvU3m\nwl/pwlHsxlTowljxzVr00l321F/mxWjmx3Hjx3jnynr52GL722T53Gb+32f/4WiIhoGSj4iYlY6c\nmpSbpoignZekoZqppZ2tqJ+puo6pupOqp6CsqqOgqrmusqSwraewrqm0sau3vKy2tbS7t7C7urKA\nnc6Do9mbq8aDp+OEqOSJreuKsO+Ms/OLtf2PtviNtv2Utu+Rt/eRufqRufyUu/2VvP2Zvv2mtMqj\nt9ijuNy1x5m1yJm/06GbwP2dwf2oyNmzw9+9x9e+yt2kxv6nyP6qyv24yee6ze28z+/EuJvBvbXB\nv7vC2p7GwbfEwbvJxb3C1qPF2qbJ3qnJ36/O1bXQyLbS1rzR3L7O4LHS4bfW4r3W5L/jyofnzIPn\nz4zq1YDn0ZXo1J7o1qfo2rjo3b3/6Y755Zfx5rn/8LTAwMDEw8LGxcTKycbNycHOy8bOzs7P1cbQ\nzMXUz8bQzsrQz8zW0MbT0MjT0c3V0cnU0s7W1M/Y08na1cva1czc08rc1s3b2M/d2M7U09HZ19Ha\n2NLe2dDe29Tf3trK1unE1/fJ2vfN3vnZ48He48rd6srV4fTc6Prr0s/p3sLg29Hh3NLi3tXk39bg\n3tzp4Mvi5NDk4Nbm4tnk4t7m7trp49Tt49Do5Nvo5dzo8N3168fz6s/379Pl5OHo5uLr6OLq6efs\n6eLt6uXu7Obt7Ong5/Xk6PDv8PPy7+bx7+rx8O/z9+308u/29O3x8fH29fL19PT2+fL5+Pf6+vn8\n+/v9/Pv+/v7///8SB4OFAAAAAWJLR0T/pQfyxQAAIABJREFUeNqcvc1r21jbP55/bJqXNqEtzX8y\nzGK+M7tnspgfZDMr25g4tEPbZKDG2hgjgpFUccAyWdiLqXRMrAOptPPCK4PBYLD4XZ/rnCPJaeee\n+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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import IPython\n", + "map_png=map_at(*london_location)\n", + "IPython.core.display.Image(map_png.content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Finding the green bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We'll need a library to parse `.png` image files and determine which bits are green:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12271\n" + ] + } + ], + "source": [ + "def is_green(r,g,b):\n", + " threshold=1.1\n", + " return g>r*threshold and g>b*threshold\n", + "\n", + "import png\n", + "from itertools import izip\n", + "\n", + "def count_green_in_png(data):\n", + " image=png.Reader(file=StringIO(data.content)).asRGB()\n", + " count = 0\n", + " for row in image[2]:\n", + " pixels=izip(*[iter(row)]*3)\n", + " count+=sum(1 for pixel in pixels if is_green(*pixel))\n", + " return count\n", + "\n", + "from StringIO import StringIO\n", + "print count_green_in_png(map_at(*london_location))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Checking our code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We could write some unit tests, but for something like this, visualisation is the key to\n", + "verification. Let's look which bits are green by building a new png.\n", + "\n", + "We could write the new png to disk, but we can use `StringIO` to get a string in memory which\n", + "behaves like a file:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def show_green_in_png(data):\n", + " image=png.Reader(file=StringIO(data.content)).asRGB()\n", + " count = 0\n", + " out=[]\n", + " for row in image[2]:\n", + " outrow=[]\n", + " pixels=izip(*[iter(row)]*3)\n", + " for pixel in pixels:\n", + " outrow.append(0)\n", + " if is_green(*pixel):\n", + " outrow.append(255)\n", + " else:\n", + " outrow.append(0)\n", + " outrow.append(0)\n", + " out.append(outrow)\n", + " buffer=StringIO()\n", + " result = png.from_array(out,mode='RGB')\n", + " result.save(buffer)\n", + " return buffer.getvalue()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n8VJZ+XfePs+phemOr2PZDBc1vxt413WyMeLUbeH4MMKHFCu5REi63acuVh8m92BCTS2k22yc/Z1A\nXEvlpIFP6YpHdO2oap9hUqkorZjl17FRSSNyU4kR8SL1lNIRdRVnnW6dVtI4cFugiVNmlm55ad8B\naZc1ZNMy3SmbpYZlSfJ757SaILWcw046WPk1Oau01mz1pYZXOmjZXL7OxSGFf0Bmei6se6dRQqOi\nuxz2HQ2cuIxdye5FyNPLKYcmYgqB5kpdKZTk3DNqWWGza5Sb8YEl+bohRW0DDSVNJt/LRC/roVge\nF0QGlk6bXiLbFWuUrS6yLvzSlSZQxdepymqZFcm0YKmNPZAUqySWzbwyg6ijafFodnJ3ZGCl6NPg\nLD6tlM5EHDXMl0nEpyBlYKnOpoaTFSmr+Hugz1Op4mySiyANrIkAe1MfWFQw1jY50hGSmGtdBgeW\nYlD3Mj19KMDRTFe63R3ZWw1FdV09Pzuwli8A68CS1oAnO3FATunTCjFLjZvc3dx1zRj4t8IRppQc\nKpX2mcxtrSuVUq8snWjSYJJ8NS40dlCWTmrHo3zofeWmorw02q4U74T+wnzK5Krr86w1NMmNU1dW\nIOxU0osI3tHZSp2oKOdCOZWgrqmkd9F08k0wTStrBvZkLLHVW3aY09tOf0NEoLxuHSVGNNBV78ux\nlTup2Ty3JOO8M/aSjWrPwGJ9fSVABbcrM3AhpX9AVV2nW2of9BiZaHpoCgG6MQ6WmAv/Bw+SsT+o\nit3NAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "IPython.core.display.Image(show_green_in_png(map_at(*london_location,satellite=True)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Points in between" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We need all the points equally spaced between two locations.\n", + "Numpy has a routine for just this:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[106806,\n", + " 127985,\n", + " 157091,\n", + " 158679,\n", + " 158033,\n", + " 158833,\n", + " 158561,\n", + " 156501,\n", + " 149482,\n", + " 140037]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from numpy import linspace\n", + "def location_sequence(start,end,steps):\n", + " # Would actually prefer this if steps\n", + " # were deduced from zoomlevel\n", + " # But need projection code for that\n", + " lats=linspace(start[0],end[0],steps)\n", + " longs=linspace(start[1],end[1],steps)\n", + " return zip(lats,longs)\n", + "\n", + "[count_green_in_png(map_at(*location,zoom=10,satellite=True))\n", + " for location in location_sequence(\n", + " geolocate(\"London\"),\n", + " geolocate(\"Birmingham\"),\n", + " 10)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "plt.plot([count_green_in_png(map_at(*location,zoom=10,satellite=True))\n", + " for location in location_sequence(geolocate(\"London\"),geolocate(\"Birmingham\"),10)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Packaging" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Packaging" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Once we've made a working program, we'd like to be able to share it with others.\n", + "\n", + "A good cross-platform build tool is the most important thing: you can always\n", + "have collaborators build from source.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Distribution tools" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Distribution tools allow one to obtain a working copy of someone else's package.\n", + "\n", + "Language-specific tools: PyPI, Ruby Gems, CPAN, CRAN\n", + "Platform specific packagers e.g. brew, apt/yum\n", + "\n", + "Windows doesn't have anything like `brew install` or `apt-get`\n", + "You have to build an 'installer'.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Laying out a project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "When planning to package a project for distribution, defining a suitable\n", + "project layout is essential.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "greetings\n", + "|-- CITATION.md\n", + "|-- LICENSE.md\n", + "|-- README.md\n", + "|-- greetings\n", + "| |-- __init__.py\n", + "| |-- command.py\n", + "| |-- greet\n", + "| |-- greeter.py\n", + "| `-- test\n", + "| |-- __init__.py\n", + "| |-- fixtures\n", + "| | `-- samples.yaml\n", + "| `-- test_greeter.py\n", + "`-- setup.py\n", + "\n", + "3 directories, 11 files\n" + ] + } + ], + "source": [ + "%%bash\n", + "tree --charset ascii greetings" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%%bash\n", + "mkdir -p greetings/greetings/test/fixtures" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using setuptools" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "To make python code into a package, we have to write a `setupfile`:\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/setup.py\n" + ] + } + ], + "source": [ + "%%writefile greetings/setup.py\n", + "\n", + "from setuptools import setup, find_packages\n", + "\n", + "setup(\n", + " name = \"Greetings\",\n", + " version = \"0.1\",\n", + " packages = find_packages(exclude=['*test']),\n", + " scripts = ['scripts/greet'],\n", + " install_requires = ['argparse']\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can now install this code with\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "sudo python setup.py install\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "And the package will be then available to use everywhere on the system:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hey, James Hetherington.\n" + ] + } + ], + "source": [ + "import greetings\n", + "from greetings.greeter import greet\n", + "print greetings.greeter.greet(\"James\",\"Hetherington\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "And the scripts are now available as command line commands:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "usage: greet [-h] [--title TITLE] [--polite] personal family\n", + "\n", + "Generate appropriate greetings\n", + "\n", + "positional arguments:\n", + " personal\n", + " family\n", + "\n", + "optional arguments:\n", + " -h, --help show this help message and exit\n", + " --title TITLE, -t TITLE\n", + " --polite, -p\n", + "Hey, James Hetherington.\n", + "How do you do, James Hetherington.\n", + "Hey, Dr James Hetherington.\n" + ] + } + ], + "source": [ + "%%bash\n", + "#!/usr/bin/env bash\n", + "greet --help\n", + "greet James Hetherington\n", + "greet --polite James Hetherington\n", + "greet James Hetherington --title Dr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installing from GitHub" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We could now submit \"greeter\" to PyPI for approval, so everyone could `pip install` it.\n", + "\n", + "However, when using git, we don't even need to do that: we can install directly from any git URL:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "sudo pip install git+git://github.com/jamespjh/greeter\n", + "greet Humphry Appleby --title Sir\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Try it!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Convert the script to a module" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Of course, there's more to do when taking code from a quick script and turning it into a proper module:\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/greetings/greeter.py\n" + ] + } + ], + "source": [ + "%%writefile greetings/greetings/greeter.py\n", + "\n", + "\n", + "def greet(personal, family, title=\"\", polite=False):\n", + "\n", + " \"\"\" Generate a greeting string for a person.\n", + "\n", + " Parameters\n", + " ----------\n", + " personal: str\n", + " A given name, such as Will or Jean-Luc\n", + " family: str\n", + " A family name, such as Riker or Picard\n", + " title: str\n", + " An optional title, such as Captain or Reverend\n", + " polite: bool\n", + " True for a formal greeting, False for informal.\n", + "\n", + " Returns\n", + " -------\n", + " string\n", + " An appropriate greeting\n", + " \"\"\"\n", + "\n", + " greeting= \"How do you do, \" if polite else \"Hey, \"\n", + " if title:\n", + " greeting+=title+\" \"\n", + "\n", + " greeting+= personal + \" \" + family +\".\"\n", + " return greeting\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "The documentation string explains how to use the function; don't worry about this for now, we'll consider\n", + "this next time.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write an executable script" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/greetings/command.py\n" + ] + } + ], + "source": [ + "%%writefile greetings/greetings/command.py\n", + "from argparse import ArgumentParser\n", + "from greeter import greet\n", + "\n", + "def process():\n", + " parser = ArgumentParser(description = \"Generate appropriate greetings\")\n", + "\n", + " parser.add_argument('--title', '-t')\n", + " parser.add_argument('--polite', '-p', action=\"store_true\")\n", + " parser.add_argument('personal')\n", + " parser.add_argument('family')\n", + "\n", + " arguments= parser.parse_args()\n", + "\n", + " print greet(arguments.personal, arguments.family, arguments.title, arguments.polite)\n", + "\n", + "if __name__ == \"__main__\":\n", + " process()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write an entry point script stub" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/greetings/greet\n" + ] + } + ], + "source": [ + "%%writefile greetings/greetings/greet\n", + "\n", + "#!/usr/bin/env python\n", + "from greetings.command import process\n", + "process()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write a readme file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "e.g.:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/README.md\n" + ] + } + ], + "source": [ + "%%writefile greetings/README.md\n", + "\n", + "Greetings!\n", + "==========\n", + "\n", + "This is a very simple example package used as part of the UCL\n", + "[Research Software Engineering with Python](development.rc.ucl.ac.uk/training/engineering) course." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write a license file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "e.g.:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/LICENSE.md\n" + ] + } + ], + "source": [ + "%%writefile greetings/LICENSE.md\n", + "\n", + "(C) University College London 2014\n", + "\n", + "This \"greetings\" example package is granted into the public domain.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write a citation file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "e.g.:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/CITATION.md\n" + ] + } + ], + "source": [ + "%%writefile greetings/CITATION.md\n", + "\n", + "If you wish to refer to this course, please cite the URL\n", + "http://development.rc.ucl.ac.uk/training/engineering\n", + "\n", + "Portions of the material are taken from Software Carpentry\n", + "http://swcarpentry.org\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define packages and executables" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%bash\n", + "touch greetings/greetings/test/__init__.py\n", + "touch greetings/greetings/__init__.py\n", + "chmod u+x greetings/greetings/command.py\n", + "# chmod u+x greetings/scripts/greet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write some unit tests" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Separating the script from the logical module made this possible:\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/greetings/test/test_greeter.py\n" + ] + } + ], + "source": [ + "%%writefile greetings/greetings/test/test_greeter.py\n", + "import yaml\n", + "import os\n", + "from ..greeter import greet\n", + "from nose.tools import assert_equal\n", + "def test_greeter():\n", + " with open(os.path.join(os.path.dirname(__file__),'fixtures','samples.yaml')) as fixtures_file:\n", + " fixtures=yaml.load(fixtures_file)\n", + " for fixture in fixtures:\n", + " answer=fixture.pop('answer')\n", + " assert_equal(greet(**fixture), answer)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Add a fixtures file:\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting greetings/greetings/test/fixtures/samples.yaml\n" + ] + } + ], + "source": [ + "%%writefile greetings/greetings/test/fixtures/samples.yaml\n", + "- personal: James\n", + " family: Hetherington\n", + " answer: \"Hey, James Hetherington.\"\n", + "- personal: James\n", + " family: Hetherington\n", + " polite: True\n", + " answer: \"How do you do, James Hetherington.\"\n", + "- personal: James\n", + " family: Hetherington\n", + " title: Dr\n", + " answer: \"Hey, Dr James Hetherington.\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Developer Install" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "If you modify your source files, you would now find it appeared as if the program doesn't change.\n", + "\n", + "That's because pip install **copies** the file.\n", + "\n", + "(On my system to /Library/Python/2.7/site-packages/: this is operating\n", + "system dependent.)\n", + "\n", + "If you want to install a package, but keep working on it, you can do\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "```\n", + "sudo python setup.py develop\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Distributing compiled code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "If you're working in C++ or Fortran, there is no language specific repository.\n", + "You'll need to write platform installers for as many platforms as you want to\n", + "support.\n", + "\n", + "Typically:\n", + "\n", + "* `dpkg` for `apt-get` on Ubuntu and Debian\n", + "* `rpm` for `yum` on Redhat and Fedora\n", + "* `homebrew` on OSX (Possibly `macports` as well)\n", + "* An executable `msi` installer for Windows.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Homebrew" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Homebrew: A ruby DSL, you host off your own webpage\n", + "\n", + "See my [installer for the cppcourse example](http://github.com/jamespjh/homebrew-reactor)\n", + "\n", + "If you're on OSX, do:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "```\n", + "brew tap jamespjh/homebrew-reactor\n", + "brew install reactor\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercises" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We previously looked at Greengraph.py, a script that enables us to explore how green space varies as we move from the city to the countryside:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(51.5073509, -0.1277583)\n" + ] + } + ], + "source": [ + "### \"geolocation\"\n", + "import geopy\n", + "geocoder=geopy.geocoders.GoogleV3(domain=\"maps.google.co.uk\")\n", + "\n", + "def geolocate(place):\n", + " return geocoder.geocode(place,exactly_one=False)[0][1]\n", + "\n", + "london_location=geolocate(\"London\")\n", + "print london_location" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The Greengraph example is only available as a single large [script](https://github.com/UCL/rsd-engineeringcourse/blob/master/../python/greengraph.py).\n", + "\n", + "Your task is to transform this into a python package that can be pip installed directly from GitHub. Remember to include:\n", + "\n", + "- an \\__init\\__.py file\n", + "- a setup.py file\n", + "- tests\n", + "- license and documentation" + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Session 4" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session05/python/conventions.py b/session05/conventions.py similarity index 100% rename from session05/python/conventions.py rename to session05/conventions.py diff --git a/session05/index.md b/session05/index.md new file mode 100644 index 00000000..65dc2774 --- /dev/null +++ b/session05/index.md @@ -0,0 +1,5 @@ +--- +title: Session 5 +--- + +Some content diff --git a/session05/python/anotherfile.py b/session05/python/anotherfile.py deleted file mode 100644 index af334709..00000000 --- a/session05/python/anotherfile.py +++ /dev/null @@ -1,2 +0,0 @@ -from mock import Mock -One=Mock diff --git a/session05/python/comments.py b/session05/python/comments.py deleted file mode 100644 index 67687d8d..00000000 --- a/session05/python/comments.py +++ /dev/null @@ -1,78 +0,0 @@ -### "setup" -from mock import Mock -array=[] -agt=[] -ws=[] -agents=[] -counter=0 -x=Mock() -agent=Mock() - - - -### "obvious" -counter=counter+1 # Increment the counter -for element in array: # Loop over elements - pass - -### "style1" -for i in range(len(agt)): #for each agent - agt[i].theta+=ws[i] # Increment the angle of each agent - #by its angular velocity - agt[i].x+=r*sin(agt[i].theta) #Move the agent by the step-size - agt[i].y+=r*cos(agt[i].theta) #r in the direction indicated - -### "style2" -for agent in agents: - agent.turn() - agent.move() - -class Agent(object): - def turn(self): - self.direction+=self.angular_velocity; - def move(self): - self.x+=Agent.step_length*sin(self.direction) - self.y+=Agent.step_length*cos(self.direction) - -### "issues" -x.clear()# Code crashes here sometimes -class Agent(object): - pass - # TODO: Implement pretty-printer method -### "issuesOK" -if x.safe_to_clear(): # Guard added as temporary workaround for #32 - x.clear() -### "selfish" -agent.turn() # Turtle Power! -agent.move() -agents[:]=[]# Shredder! -### "rude" -# Stupid supervisor made me write this code -# So I did it while very very drunk. -### "teaching" -# This is how you define a decorator in python -def double(decorated_function): - # Here, the result function forms a closure over - # the decorated function - def result_function(input): - return decorated_function(decorated_function(input)) - # The returned result is a function - return result_function - -@double -def try_me_twice(): - pass -### "docstring" -def complex(real=0.0, imag=0.0): - """Form a complex number. - Keyword arguments: - real -- the real part (default 0.0) - imag -- the imaginary part (default 0.0) - """ - - # code here - -### "other" -def __init__(self): - self.angle=0 # clockwise from +ve y-axis - nonzero_indices = [] # Use sparse model as memory constrained diff --git a/session05/python/config.yaml b/session05/python/config.yaml deleted file mode 100644 index 28a6adf0..00000000 --- a/session05/python/config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -bounds: [0,0,100,100] -counts: - hawk: 5 - starling: 500 -speed: 2.0 -turning_circle: 3.0 - diff --git a/session05/python/objects.py b/session05/python/objects.py deleted file mode 100644 index 407de013..00000000 --- a/session05/python/objects.py +++ /dev/null @@ -1,124 +0,0 @@ -### "setup" -value=0 -bird_types=["Starling", "Hawk"] -import numpy as np -from mock import Mock -average=np.mean -hawk=Mock() -starling=Mock() -Mock.__sub__=Mock() -### "intro" -class Person(object): - def __init__(self,name,age): - self.name=name - self.age=age - def grow_up(self): - self.age+=1 - -james=Person("James",37) -james.home="London" -### "declare" -class MyClass(object): - pass -### "instance" -my_object = MyClass() -### "method" -class MyClass(object): - def someMethod(self, argument): - pass - -my_object=MyClass() -my_object.someMethod(value) -### "constructor" -class MyClass(object): - def __init__(self, argument): - pass - -my_object = MyClass(value) -### "member" -class MyClass(object): - def __init__(self): - self.member = "Value" - -my_object = MyClass() -assert(my_object.member == "Value") -### "structure_before" -from random import random -birds = [{"position": random(), - "velocity": random(), - "type": kind} for kind in bird_types] - -average_position = average([bird["position"] for bird in birds]) -### "structure_after" -class Bird(object): - def __init__(self,type): - from random import random - self.type = type - self.position = random() - self.velocity = random() - -birds = [Bird(type) for type in bird_types] -average_position = average([bird.position for bird in birds]) -### "method_before" -def can_see(source,target): - return (source.facing-target.facing) The proper use of comments is to compensate for our failure to express yourself in code. \nNote that I used the word failure. I meant it. Comments are always failures.\n\n-- Robert Martin, [Clean Code](http://www.amazon.co.uk/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882).\n\nI wouldn't disagree, but still, writing \"self-documenting\" code is very hard, so do comment if you're unsure!\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Comments which belong in an issue tracker" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "x.clear()# Code crashes here sometimes", - "class Agent(object):", - " pass", - " # TODO: Implement pretty-printer method", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nBUT comments that reference issues in the tracker can be good.\n\nE.g.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "if x.safe_to_clear(): # Guard added as temporary workaround for #32", - " x.clear()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nis OK.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Comments which only make sense to the author today" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "agent.turn() # Turtle Power!", - "agent.move()", - "agents[:]=[]# Shredder!", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Comments which are unpublishable" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "# Stupid supervisor made me write this code", - "# So I did it while very very drunk.", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Good comments" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Pedagogical comments" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nCode that *is* good style, but you're not familiar with, or \nthat colleagues might not be familiar with\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "# This is how you define a decorator in python", - "def double(decorated_function):", - " # Here, the result function forms a closure over ", - " # the decorated function", - " def result_function(input):", - " return decorated_function(decorated_function(input))", - " # The returned result is a function", - " return result_function", - "", - "@double", - "def try_me_twice():", - " pass", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Other good comments" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nComments which explain coding definitions or reasons for programming choices.\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def __init__(self):", - " self.angle=0 # clockwise from +ve y-axis", - " nonzero_indices = [] # Use sparse model as memory constrained", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Documentation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Documentation is hard" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Good documentation is hard, and very expensive.\n* Bad documentation is detrimental.\n* Good documentation quickly becomes bad if not kept up-to-date with code changes.\n* Professional companies pay large teams of documentation writers.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Prefer readable code with tests and vignettes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIf you don't have the capacity to maintain great documentation,\nfocus on:\n\n* Readable code\n* Automated tests\n* Small code samples demonstrating how to use the api\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Comment-based Documentation tools" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nDocumentation tools can produce extensive documentation about your code by pulling out comments near the beginning of functions,\ntogether with the signature, into a web page.\n\nThe most popular is [Doxygen](http://www.stack.nl/~dimitri/doxygen/)\n\n[Have a look at an example of some Doxygen output](\nhttp://www.bempp.org/cppref/2.0/group__abstract__boundary__operators.html)\n\n[Sphinx](http://sphinx-doc.org/) is nice for Python, and works with C++ as well.\nHere's some [Sphinx-generated output](http://www.bempp.org/pythonref/2.0/bempp_visualization2.html)\nand the [corresponding source code](https://github.com/bempp/bempp/blob/master/python/bempp/visualization2.py)\n[Breathe](http://michaeljones.github.io/breathe/ ) can be used to make Sphinx and Doxygen work together.\n\n[Roxygen](http://www.rstudio.com/ide/docs/packages/documentation) is good for R.\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Example of using Sphinx" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Write some docstrings" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe're going to document our \"greeter\" example using docstrings with Sphinx.\n\nThere are various conventions for how to write docstrings, but the native sphinx one doesn't look nice when used with\nthe built in `help` system.\n\nSo we'll use the [numpydoc](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt) sphinx extension to\nallow us to use the docstring conventions from NumPy.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - " \"\"\" Generate a greeting string for a person.", - "", - " Parameters", - " ----------", - " personal: str", - " A given name, such as Will or Jean-Luc", - " family: str", - " A family name, such as Riker or Picard", - " title: str", - " An optional title, such as Captain or Reverend", - " polite: bool", - " True for a formal greeting, False for informal.", - "", - " Returns", - " -------", - " string", - " An appropriate greeting", - " \"\"\"", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Set up sphinx" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nInvoke the [sphinx-quickstart](http://sphinx-doc.org/tutorial.html) command to build Sphinx's\nconfiguration file automatically based on questions\nat the command line:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "Welcome to the Sphinx 1.2.3 quickstart utility.\n\nPlease enter values for the following settings (just press Enter to\naccept a default value, if one is given in brackets).\n\nEnter the root path for documentation.\n> Root path for the documentation [.]:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nand then look at and adapt the generated config, a file called\n[conf.py](Add link) in the root of the project.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "# Add any Sphinx extension module names here, as strings. They can be", - "# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom", - "# ones.", - "extensions = [", - " 'sphinx.ext.autodoc', # Support automatic documentation", - " 'sphinx.ext.coverage', # Automatically check if functions are documented", - " 'sphinx.ext.mathjax', # Allow support for algebra", - " 'sphinx.ext.viewcode', # Include the source code in documentation", - " 'numpydoc' # Support NumPy style docstrings", - "]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Define the root documentation page" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSphinx uses [RestructuredText](http://docutils.sourceforge.net/rst.html) another wiki markup format similar to Markdown.\n\nYou define an \"index.rst\" file to contain any preamble text you want. The rest is autogenerated by `sphinx-quickstart`\n\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile greetings/index.rst", - "Welcome to Greetings's documentation!", - "=====================================", - "", - "Simple \"Hello, James\" module developed to teach research software engineering.", - "", - ".. autofunction:: greetings.greeter.greet", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "\u00a0Run sphinx" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe can run Sphinx using:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "sphinx-build . doc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Sphinx output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSphinx's output is [html](../python/GreetingsDoc/). \nWe've just created a simple single function's documentation, but Sphinx will create\nmultiple nested pages of documentation automatically for many functions.\n\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Refactoring" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nTo refactor is to:\n\n* Make a change to the design of some software\n* Which improves the structure or readability\n* But which leaves the actual behaviour of the program completely unchanged.\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "A word from the Master" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n> Refactoring is a controlled technique for improving the design of an existing code base. \nIts essence is applying a series of small behavior-preserving transformations, each of which \"too small to be worth doing\". \nHowever the cumulative effect of each of these transformations is quite significant. \nBy doing them in small steps you reduce the risk of introducing errors. \nYou also avoid having the system broken while you are carrying out the restructuring - \nwhich allows you to gradually refactor a system over an extended period of time.\n\n-- Martin Fowler\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "List of known refactorings" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe next few sections will present some known refactorings\n\nWe'll show before and after code, present any new coding techniques needed to do the refactoring,\nand describe *code smells*: how you know you need to refactor.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Replace magic numbers with constants" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSmell: Raw numbers appear in your code\n\nBefore: \n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "data= [math.sin(x) for x in np.arange(0,3.141,3.141/100)]", - "result= [0]*100", - "for i in range(100):", - " for j in range(i+1, 100):", - " result[j] += data[i] * data[i-j] / 100", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nafter:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "resolution=100", - "pi=3.141", - "data= [math.sin(x) for x in np.arange(0,pi,pi/resolution)]", - "result= [0]*resolution", - "for i in range(resolution):", - " for j in range(i + 1, resolution):", - " result[j] += data[i] * data[i-j] / resolution", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Replace repeated code with a function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSmell: Fragments of repeated code appear\n\nBefore:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "if abs(hawk.facing-starling.facing) The aggregate motion of a flock of birds, a herd of land animals, or a school of fish is a beautiful and familiar\npart of the natural world... The aggregate motion of the simulated flock is created by a distributed behavioral model much\nlike that at work in a natural flock; the birds choose their own course. Each simulated bird is implemented as an independent\nactor that navigates according to its local perception of the dynamic environment, the laws of simulated physics that rule its\nmotion, and a set of behaviors programmed into it... The aggregate motion of the simulated flock is the result of the\ndense interaction of the relatively simple behaviors of the individual simulated birds. \n\n-- Craig W. Reynolds, \"Flocks, Herds, and Schools: A Distributed Behavioral Model\", *Computer Graphics* **21** _4_ 1987, pp 25-34\nSee the [original paper](http://www.cs.toronto.edu/~dt/siggraph97-course/cwr87/)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Boids" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Collision Avoidance: avoid collisions with nearby flockmates\n* Velocity Matching: attempt to match velocity with nearby flockmates\n* Flock Centering: attempt to stay close to nearby flockmates\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Bad_Boids" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nI have written some _very bad_ code implementing Boids, based on [Konrad Parker's](http://www.kfish.org/) pseudocode.\n\nHere's the [Github link](https://github.com/jamespjh/bad-boids).\n\nPlease fork it on GitHub, and clone your fork.\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "git clone git@github.com:yourname/bad-boids.git # OR\ngit clone https://github.com/yourname/bad-boids.git" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Look at the birdies!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nRun bad_boids:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "cd bad_boids\npython bad_boids.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nYou should be able to see some birds flying around, and then disappearing as they leave the window.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Your Task" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nTransform bad_boids into better code, while making sure it still works.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "A regression test" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nFirst, have a look at the regression test I made.\n\nTo create it, I saved out the before and after state\nfor one iteration of some boids, using ipython:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import yaml", - "import boids", - "from copy import deepcopy", - "before=deepcopy(boids.boids)", - "boids.update_boids(boids.boids)", - "after=boids.boids", - "fixture={\"before\":before,\"after\":after}", - "fixture_file=open(\"fixture.yml\",'w')", - "fixture_file.write(yaml.dump(fixture))", - "fixture_file.close()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "A regression test" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThen, I used the fixture file to define the test:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from boids import update_boids", - "from nose.tools import assert_equal", - "import os", - "import yaml", - "", - "def test_bad_boids_regression():", - " regression_data=yaml.load(open(os.path.join(os.path.dirname(__file__),'fixture.yml')))", - " boid_data=regression_data[\"before\"]", - " update_boids(boid_data)", - " assert_equal(regression_data[\"after\"],boid_data)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Make the regression test fail" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nCheck the tests pass:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "nosetests" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nEdit the file to make the test fail, see the fail, then reset it:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "git checkout boids.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Start Refactoring" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nLook at the code, consider the [list of refactorings](#refactoring-summary), and make changes\n\nEach time, do a git commit on your fork, and write a commit message explaining the \nrefactoring you did.\n\nTry to keep the changes as small as possible.\n\nIf your refactoring creates any units, (functions, modules, or classes)\nwrite a unit test for the unit." - ] - } - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Construction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Construction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Software *design* gets a lot of press (Object orientation, UML, design patterns)\n", + "\n", + "In this session we're going to look at advice on software *construction*\n", + "\n", + "This lecture is available as an [IPython Notebook](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session05/../python/session05.ipynb)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Construction vs Design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "For a given piece of code, there exist several different ways one could write it:\n", + "\n", + "* Choice of variable names\n", + "* Choice of comments\n", + "* Choice of layout\n", + "\n", + "The consideration of these questions is the area of Software Construction.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Low-level design decisions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We will also look at some of the lower-level software design decisions in the context of this section:\n", + "\n", + "* Division of code into subroutines\n", + "* Subroutine access signatures\n", + "* Choice of data structures for readability\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Algorithms and structures" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We will not, in this session, be looking at decisions as to how design questions impact performance:\n", + "\n", + "* Choice of algorithms\n", + "* Choice of data structures for performance\n", + "* Choice of memory layout\n", + "\n", + "We will consider these in a future session.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Architectural design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We will not, in this session, be looking at the large-scale questions of how program components interact,\n", + "the stategic choices that govern how software behaves at the large scale:\n", + "\n", + "* Where do objects get made?\n", + "* Which objects own or access other objects?\n", + "* How can I hide complexity in one part of the code from other parts of the code?\n", + "\n", + "We will consider these in a future session.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Construction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "So, we've excluded most of the exciting topics. What's left is the bricks and mortar of software:\n", + "how letters and symbols are used to build code which is readable.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Literate programming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In literature, books are enjoyable for different reasons:\n", + "\n", + "* The beauty of stories\n", + "* The beauty of plots\n", + "* The beauty of characters\n", + "\n", + "* The beauty of paragraphs\n", + "* The beauty of sentences\n", + "* The beauty of words\n", + "\n", + "Software has beauty at these levels too: stories and characters correspond to architecture and object design,\n", + "plots corresponds to algorithms, but the rhythm of sentences and the choice of words corresponds\n", + "to software construction.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Programming for humans" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Remember you're programming for humans as well as computers\n", + "* A program is the best, most rigourous way to describe an algorithm\n", + "* Code should be pleasant to read, a form of scholarly communication\n", + "\n", + "Read [CodeComplete](http://www.amazon.co.uk/Code-Complete-Practical-Handbook-Construction/dp/0735619670)\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is based on a number of fragments of code, with an implicit context.\n", + "We've made a library to set up the context so the examples work:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting context.py\n" + ] + } + ], + "source": [ + "%%writefile context.py\n", + "from mock import Mock\n", + "array=[]\n", + "agt=[]\n", + "ws=[]\n", + "agents=[]\n", + "counter=0\n", + "x=Mock()\n", + "y=None\n", + "agent=Mock()\n", + "value=0\n", + "bird_types=[\"Starling\", \"Hawk\"]\n", + "import numpy as np\n", + "from mock import Mock\n", + "average=np.mean\n", + "hawk=Mock()\n", + "starling=Mock()\n", + "Mock.__sub__=Mock()\n", + "sInput=\"2.0\"\n", + "input =\"2.0\"\n", + "iOffset=1\n", + "offset =1\n", + "anothervariable=1\n", + "flag1=True\n", + "variable=1\n", + "flag2=False\n", + "def do_something(): pass\n", + "from mock import Mock\n", + "Mock.__sub__=Mock()\n", + "Mock.__abs__=Mock()\n", + "chromosome=None\n", + "start_codon=None\n", + "subsequence=Mock()\n", + "transcribe=Mock()\n", + "ribe=Mock()\n", + "find=Mock()\n", + "hawk=Mock()\n", + "starling=Mock()\n", + "can_see=Mock()\n", + "my_name=\"\"\n", + "your_name=\"\"\n", + "flag1=False\n", + "flag2=False\n", + "start=0.0\n", + "end=1.0\n", + "step=0.1\n", + "birds=[Mock()]*2\n", + "resolution=100\n", + "pi=3.141\n", + "result= [0]*resolution\n", + "import numpy as np\n", + "import math\n", + "data= [math.sin(x) for x in np.arange(0,pi,pi/resolution)]\n", + "import yaml\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from context import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coding Conventions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### One code, many layouts:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Consider the following fragment of python:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import species\n", + "def AddToReaction(name, reaction):\n", + " reaction.append(species.Species(name))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "this could also have been written:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from species import Species\n", + "\n", + "def add_to_reaction(a_name,\n", + " a_reaction):\n", + " l_species = Species(a_name)\n", + " a_reaction.append( l_species )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### So many choices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Layout\n", + "* Naming\n", + "* Syntax choices\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Layout" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "reaction= {\n", + " \"reactants\": [\"H\",\"H\",\"O\"],\n", + " \"products\": [\"H2O\"]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "reaction2=(\n", + "{\n", + " \"reactants\":\n", + " [\n", + " \"H\",\n", + " \"H\",\n", + " \"O\"\n", + " ],\n", + " \"products\":\n", + " [\n", + " \"H2O\"\n", + " ]\n", + "}\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Layout choices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Brace style\n", + "* Line length\n", + "* Indentation\n", + "* Whitespace/Tabs\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Naming Conventions" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class ClassName(object):\n", + " def methodName(variable_name):\n", + " instance_variable=variable_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class class_name(object):\n", + " def method_name(a_variable):\n", + " m_instance_variable=a_variable" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Hungarian Notation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Prefix denotes *type*:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fNumber= float(sInput) + iOffset\n", + "number = float(input) + offset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Newlines" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Newlines make code easier to read\n", + "* Newlines make less code fit on a screen\n", + "\n", + "Use newlines to describe your code's *rhythm*\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Syntax Choices" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "anothervariable+=1\n", + "if ((variable==anothervariable) and flag1 or flag2): do_something()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "anothervariable = anothervariable + 1\n", + "variable_equality = (variable == anothervariable);\n", + "if ((variable_equality and flag1) or flag2):\n", + " do_something()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Syntax choices" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Explicit operator precedence\n", + "* Compound expressions\n", + "* Package import choices\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coding Conventions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "You should try to have an agreed policy for your team for these matters.\n", + "\n", + "If your language sponsor has a standard policy, use that.\n", + "\n", + "E.g. [Python PEP8](http://legacy.python.org/dev/peps/pep-0008/)\n", + "\n", + "E.g. [Google's guide for R](https://google-styleguide.googlecode.com/svn/trunk/Rguide.xml)\n", + "\n", + "E.g. [Google's style guide for C++](http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Lint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There are automated tools which enforce coding conventions and check for common mistakes.\n", + "\n", + "These are called *linters*\n", + "\n", + "E.g. `pip install` [pep8](https://pypi.python.org/pypi/pep8)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "species.py:2:6: E111 indentation is not a multiple of four\n" + ] + } + ], + "source": [ + "%%bash\n", + "pep8 species.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "It is a good idea to run a linter before every commit, or include it in your CI tests.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why comment?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* You're writing code for people, as well as computers.\n", + "* Comments can help you build code, by representing your design\n", + "* Comments explain subtleties in the code which are not obvious from the syntax\n", + "* Comments explain *why* you wrote the code the way you did\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bad Comments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\"I write good code, you can tell by the number of comments.\"\n", + "\n", + "This is wrong.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments which are obvious" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "counter=counter+1 # Increment the counter\n", + "for element in array: # Loop over elements\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments which could be replaced by better style" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for i in range(len(agt)): #for each agent\n", + " agt[i].theta+=ws[i] # Increment the angle of each agent\n", + " #by its angular velocity\n", + " agt[i].x+=r*sin(agt[i].theta) #Move the agent by the step-size\n", + " agt[i].y+=r*cos(agt[i].theta) #r in the direction indicated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Is good. But:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for agent in agents:\n", + " agent.turn()\n", + " agent.move()\n", + "\n", + "class Agent(object):\n", + " def turn(self):\n", + " self.direction+=self.angular_velocity;\n", + " def move(self):\n", + " self.x+=Agent.step_length*sin(self.direction)\n", + " self.y+=Agent.step_length*cos(self.direction)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "is probably better.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments vs expressive code " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> The proper use of comments is to compensate for our failure to express yourself in code. \n", + "Note that I used the word failure. I meant it. Comments are always failures.\n", + "\n", + "-- Robert Martin, [Clean Code](http://www.amazon.co.uk/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882).\n", + "\n", + "I wouldn't disagree, but still, writing \"self-documenting\" code is very hard, so do comment if you're unsure!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments which belong in an issue tracker" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x.clear() # Code crashes here sometimes\n", + "class Agent(object):\n", + " pass\n", + " # TODO: Implement pretty-printer method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "BUT comments that reference issues in the tracker can be good.\n", + "\n", + "E.g.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "if x.safe_to_clear(): # Guard added as temporary workaround for #32\n", + " x.clear()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "is OK.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments which only make sense to the author today" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "agent.turn() # Turtle Power!\n", + "agent.move()\n", + "agents[:]=[]# Shredder!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comments which are unpublishable" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Stupid supervisor made me write this code\n", + "# So I did it while very very drunk." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Good comments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pedagogical comments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Code that *is* good style, but you're not familiar with, or \n", + "that colleagues might not be familiar with\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# This is how you define a decorator in python\n", + "def double(decorated_function):\n", + " # Here, the result function forms a closure over \n", + " # the decorated function\n", + " def result_function(input):\n", + " return decorated_function(decorated_function(input))\n", + " # The returned result is a function\n", + " return result_function\n", + "\n", + "@double\n", + "def try_me_twice():\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Other good comments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Comments which explain coding definitions or reasons for programming choices.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def __init__(self):\n", + " self.angle=0 # clockwise from +ve y-axis\n", + " nonzero_indices = [] # Use sparse model as memory constrained" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Documentation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Documentation is hard" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Good documentation is hard, and very expensive.\n", + "* Bad documentation is detrimental.\n", + "* Good documentation quickly becomes bad if not kept up-to-date with code changes.\n", + "* Professional companies pay large teams of documentation writers.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prefer readable code with tests and vignettes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "If you don't have the capacity to maintain great documentation,\n", + "focus on:\n", + "\n", + "* Readable code\n", + "* Automated tests\n", + "* Small code samples demonstrating how to use the api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comment-based Documentation tools" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Documentation tools can produce extensive documentation about your code by pulling out comments near the beginning of functions,\n", + "together with the signature, into a web page.\n", + "\n", + "The most popular is [Doxygen](http://www.stack.nl/~dimitri/doxygen/)\n", + "\n", + "[Have a look at an example of some Doxygen output](\n", + "http://www.bempp.org/cppref/2.0/group__abstract__boundary__operators.html)\n", + "\n", + "[Sphinx](http://sphinx-doc.org/) is nice for Python, and works with C++ as well.\n", + "Here's some [Sphinx-generated output](http://www.bempp.org/pythonref/2.0/bempp_visualization2.html)\n", + "and the [corresponding source code](https://github.com/bempp/bempp/blob/master/python/bempp/visualization2.py)\n", + "[Breathe](http://michaeljones.github.io/breathe/ ) can be used to make Sphinx and Doxygen work together.\n", + "\n", + "[Roxygen](http://www.rstudio.com/ide/docs/packages/documentation) is good for R.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example of using Sphinx" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Write some docstrings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We're going to document our \"greeter\" example using docstrings with Sphinx.\n", + "\n", + "There are various conventions for how to write docstrings, but the native sphinx one doesn't look nice when used with\n", + "the built in `help` system.\n", + "\n", + "In writing Greeter, we used the docstring conventions from NumPy.\n", + "So we use the [numpydoc](https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt) sphinx extension to \n", + "support these." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "```\n", + "\"\"\" \n", + "Generate a greeting string for a person.\n", + "\n", + "Parameters\n", + "----------\n", + "personal: str\n", + " A given name, such as Will or Jean-Luc\n", + "\n", + "family: str\n", + " A family name, such as Riker or Picard\n", + "title: str\n", + " An optional title, such as Captain or Reverend\n", + "polite: bool\n", + " True for a formal greeting, False for informal.\n", + "\n", + "Returns\n", + "-------\n", + "string\n", + " An appropriate greeting\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up sphinx" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Invoke the [sphinx-quickstart](http://sphinx-doc.org/tutorial.html) command to build Sphinx's\n", + "configuration file automatically based on questions\n", + "at the command line:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` bash\n", + "sphinx-quickstart\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Which responds:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "Welcome to the Sphinx 1.2.3 quickstart utility.\n", + "\n", + "Please enter avalues for the following settings (just press Enter to\n", + "accept a default value, if one is given in brackets).\n", + "\n", + "Enter the root path for documentation.\n", + "> Root path for the documentation [.]:\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and then look at and adapt the generated config, a file called\n", + "conf.py in the root of the project. This contains the project's Sphinx configuration, as Python variables:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "``` python\n", + "#Add any Sphinx extension module names here, as strings. They can be\n", + "#extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n", + "# ones.\n", + "extensions = [\n", + " 'sphinx.ext.autodoc', # Support automatic documentation\n", + " 'sphinx.ext.coverage', # Automatically check if functions are documented\n", + " 'sphinx.ext.mathjax', # Allow support for algebra\n", + " 'sphinx.ext.viewcode', # Include the source code in documentation\n", + " 'numpydoc' # Support NumPy style docstrings\n", + "]\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To proceed with the example, we'll copy a finished conf.py into our folder, though normally you'll always use `sphinx-quickstart`\n" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ../session04/greetings/conf.py\n" + ] + } + ], + "source": [ + "%%writefile ../session04/greetings/conf.py\n", + "\n", + "import sys\n", + "import os\n", + "\n", + "extensions = [\n", + " 'sphinx.ext.autodoc', # Support automatic documentation\n", + " 'sphinx.ext.coverage', # Automatically check if functions are documented\n", + " 'sphinx.ext.mathjax', # Allow support for algebra\n", + " 'sphinx.ext.viewcode', # Include the source code in documentation\n", + " 'numpydoc' # Support NumPy style docstrings\n", + "]\n", + "templates_path = ['_templates']\n", + "source_suffix = '.rst'\n", + "master_doc = 'index'\n", + "project = u'Greetings'\n", + "copyright = u'2014, James Hetherington'\n", + "version = '0.1'\n", + "release = '0.1'\n", + "exclude_patterns = ['_build']\n", + "pygments_style = 'sphinx'\n", + "html_theme = 'default'\n", + "html_static_path = ['_static']\n", + "htmlhelp_basename = 'Greetingsdoc'\n", + "latex_elements = {\n", + "}\n", + "\n", + "latex_documents = [\n", + " ('index', 'Greetings.tex', u'Greetings Documentation',\n", + " u'James Hetherington', 'manual'),\n", + "]\n", + "\n", + "man_pages = [\n", + " ('index', 'greetings', u'Greetings Documentation',\n", + " [u'James Hetherington'], 1)\n", + "]\n", + "\n", + "texinfo_documents = [\n", + " ('index', 'Greetings', u'Greetings Documentation',\n", + " u'James Hetherington', 'Greetings', 'One line description of project.',\n", + " 'Miscellaneous'),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define the root documentation page" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Sphinx uses [RestructuredText](http://docutils.sourceforge.net/rst.html) another wiki markup format similar to Markdown.\n", + "\n", + "You define an \"index.rst\" file to contain any preamble text you want. The rest is autogenerated by `sphinx-quickstart`\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ../session04/greetings/index.rst\n" + ] + } + ], + "source": [ + "%%writefile ../session04/greetings/index.rst\n", + "Welcome to Greetings's documentation!\n", + "=====================================\n", + "\n", + "Simple \"Hello, James\" module developed to teach research software engineering.\n", + "\n", + ".. autofunction:: greetings.greeter.greet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "###  Run sphinx" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We can run Sphinx using:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Making output directory...\n", + "Running Sphinx v1.2.3\n", + "loading pickled environment... not yet created\n", + "No builder selected, using default: html\n", + "building [html]: targets for 1 source files that are out of date\n", + "updating environment: 1 added, 0 changed, 0 removed\n", + "reading sources... [100%] index\n", + "\n", + "looking for now-outdated files... none found\n", + "pickling environment... done\n", + "checking consistency... done\n", + "preparing documents... done\n", + "writing output... [100%] index\n", + "\n", + "writing additional files... (1 module code pages) _modules/index genindex search\n", + "copying static files... done\n", + "copying extra files... done\n", + "dumping search index... done\n", + "dumping object inventory... done\n", + "build succeeded, 1 warning.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: html_static_path entry u'/Users/jamespjh/devel/rsdt/training/rsd-engineeringcourse/session04/greetings/_static' does not exist\n" + ] + } + ], + "source": [ + "%%bash\n", + "cd ../session04/greetings/\n", + "sphinx-build . doc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sphinx output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sphinx's output is [html](../session04/greetings/doc). We just created a simple single function's documentation, but Sphinx will create\n", + "multiple nested pages of documentation automatically for many functions.\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Refactoring" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "To refactor is to:\n", + "\n", + "* Make a change to the design of some software\n", + "* Which improves the structure or readability\n", + "* But which leaves the actual behaviour of the program completely unchanged.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A word from the Master" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> Refactoring is a controlled technique for improving the design of an existing code base. \n", + "Its essence is applying a series of small behavior-preserving transformations, each of which \"too small to be worth doing\". \n", + "However the cumulative effect of each of these transformations is quite significant. \n", + "By doing them in small steps you reduce the risk of introducing errors. \n", + "You also avoid having the system broken while you are carrying out the restructuring - \n", + "which allows you to gradually refactor a system over an extended period of time.\n", + "\n", + "-- Martin Fowler\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### List of known refactorings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The next few sections will present some known refactorings\n", + "\n", + "We'll show before and after code, present any new coding techniques needed to do the refactoring,\n", + "and describe *code smells*: how you know you need to refactor.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Replace magic numbers with constants" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Smell: Raw numbers appear in your code\n", + "\n", + "Before: \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data= [math.sin(x) for x in np.arange(0,3.141,3.141/100)]\n", + "result= [0]*100\n", + "for i in range(100):\n", + " for j in range(i+1, 100):\n", + " result[j] += data[i] * data[i-j] / 100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "after:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "resolution=100\n", + "pi=3.141\n", + "data= [math.sin(x) for x in np.arange(0,pi,pi/resolution)]\n", + "result= [0]*resolution\n", + "for i in range(resolution):\n", + " for j in range(i + 1, resolution):\n", + " result[j] += data[i] * data[i-j] / resolution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Replace repeated code with a function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Smell: Fragments of repeated code appear\n", + "\n", + "Before:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "if abs(hawk.facing-starling.facing) The aggregate motion of a flock of birds, a herd of land animals, or a school of fish is a beautiful and familiar\n", + "part of the natural world... The aggregate motion of the simulated flock is created by a distributed behavioral model much\n", + "like that at work in a natural flock; the birds choose their own course. Each simulated bird is implemented as an independent\n", + "actor that navigates according to its local perception of the dynamic environment, the laws of simulated physics that rule its\n", + "motion, and a set of behaviors programmed into it... The aggregate motion of the simulated flock is the result of the\n", + "dense interaction of the relatively simple behaviors of the individual simulated birds. \n", + "\n", + "-- Craig W. Reynolds, \"Flocks, Herds, and Schools: A Distributed Behavioral Model\", *Computer Graphics* **21** _4_ 1987, pp 25-34\n", + "See the [original paper](http://www.cs.toronto.edu/~dt/siggraph97-course/cwr87/)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Boids" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Collision Avoidance: avoid collisions with nearby flockmates\n", + "* Velocity Matching: attempt to match velocity with nearby flockmates\n", + "* Flock Centering: attempt to stay close to nearby flockmates\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bad_Boids" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "I have written some _very bad_ code implementing Boids, based on [Konrad Parker's](http://www.kfish.org/) pseudocode.\n", + "\n", + "Here's the [Github link](https://github.com/jamespjh/bad-boids).\n", + "\n", + "Please fork it on GitHub, and clone your fork.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` bash\n", + "git clone git@github.com:yourname/bad-boids.git \n", + "# OR git clone https://github.com/yourname/bad-boids.git\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from JSAnimation import IPython_display" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the Exercise, you should start from the GitHub repository, but here's my terrible code:" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\"\"\"\n", + "A deliberately bad implementation of [Boids](http://dl.acm.org/citation.cfm?doid=37401.37406)\n", + "for use as an exercise on refactoring.\n", + "\"\"\"\n", + "\n", + "from matplotlib import pyplot as plt\n", + "from matplotlib import animation\n", + "\n", + "import random\n", + "\n", + "# Deliberately terrible code for teaching purposes\n", + "\n", + "boids_x=[random.uniform(-450,50.0) for x in range(50)]\n", + "boids_y=[random.uniform(300.0,600.0) for x in range(50)]\n", + "boid_x_velocities=[random.uniform(0,10.0) for x in range(50)]\n", + "boid_y_velocities=[random.uniform(-20.0,20.0) for x in range(50)]\n", + "boids=(boids_x,boids_y,boid_x_velocities,boid_y_velocities)\n", + "\n", + "def update_boids(boids):\n", + " xs,ys,xvs,yvs=boids\n", + " # Fly towards the middle\n", + " for i in range(len(xs)):\n", + " for j in range(len(xs)):\n", + " xvs[i]=xvs[i]+(xs[j]-xs[i])*0.01/len(xs)\n", + " for i in range(len(xs)):\n", + " for j in range(len(xs)):\n", + " yvs[i]=yvs[i]+(ys[j]-ys[i])*0.01/len(xs)\n", + " # Fly away from nearby boids\n", + " for i in range(len(xs)):\n", + " for j in range(len(xs)):\n", + " if (xs[j]-xs[i])**2 + (ys[j]-ys[i])**2 < 100:\n", + " xvs[i]=xvs[i]+(xs[i]-xs[j])\n", + " yvs[i]=yvs[i]+(ys[i]-ys[j])\n", + " # Try to match speed with nearby boids\n", + " for i in range(len(xs)):\n", + " for j in range(len(xs)):\n", + " if (xs[j]-xs[i])**2 + (ys[j]-ys[i])**2 < 10000:\n", + " xvs[i]=xvs[i]+(xvs[j]-xvs[i])*0.125/len(xs)\n", + " yvs[i]=yvs[i]+(yvs[j]-yvs[i])*0.125/len(xs)\n", + " # Move according to velocities\n", + " for i in range(len(xs)):\n", + " xs[i]=xs[i]+xvs[i]\n", + " ys[i]=ys[i]+yvs[i]\n", + "\n", + "\n", + "figure=plt.figure()\n", + "axes=plt.axes(xlim=(-500,1500), ylim=(-500,1500))\n", + "scatter=axes.scatter(boids[0],boids[1])\n", + "\n", + "def animate(frame):\n", + " update_boids(boids)\n", + " scatter.set_offsets(zip(boids[0],boids[1]))\n", + "\n", + "\n", + "anim = animation.FuncAnimation(figure, animate,\n", + " frames=200, interval=50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Look at the birdies!" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
      \n", + " \n", + "
      \n", + " \n", + "
      \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
      \n", + " Once \n", + " Loop \n", + " Reflect \n", + " \n", + "
      \n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "anim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Run bad_boids:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` bash\n", + "cd bad_boids\n", + "python bad_boids.py\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "You should be able to see some birds flying around, and then disappearing as they leave the window.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Your Task" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Transform bad_boids into better code, while making sure it still works.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A regression test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "First, have a look at the regression test I made.\n", + "\n", + "To create it, I saved out the before and after state\n", + "for one iteration of some boids, using ipython:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "``` python\n", + "import yaml\n", + "import boids\n", + "from copy import deepcopy\n", + "before=deepcopy(boids.boids)\n", + "boids.update_boids(boids.boids)\n", + "after=boids.boids\n", + "fixture={\"before\":before,\"after\":after}\n", + "fixture_file=open(\"fixture.yml\",'w')\n", + "fixture_file.write(yaml.dump(fixture))\n", + "fixture_file.close()\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A regression test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Then, I used the fixture file to define the test:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "``` python\n", + "from boids import update_boids\n", + "from nose.tools import assert_equal\n", + "import os\n", + "import yaml\n", + "\n", + "def test_bad_boids_regression():\n", + " regression_data=yaml.load(open(os.path.join(os.path.dirname(__file__),'fixture.yml')))\n", + " boid_data=regression_data[\"before\"]\n", + " update_boids(boid_data)\n", + " assert_equal(regression_data[\"after\"],boid_data)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Make the regression test fail" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Check the tests pass:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` bash\n", + "nosetests\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Edit the file to make the test fail, see the fail, then reset it:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "git checkout boids.py\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Start Refactoring" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Look at the code, consider the [list of refactorings](#refactoring-summary), and make changes\n", + "\n", + "Each time, do a git commit on your fork, and write a commit message explaining the \n", + "refactoring you did.\n", + "\n", + "Try to keep the changes as small as possible.\n", + "\n", + "If your refactoring creates any units, (functions, modules, or classes)\n", + "write a unit test for the unit." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Session 5" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session05/python/species.py b/session05/species.py similarity index 69% rename from session05/python/species.py rename to session05/species.py index cba9004c..4a12d4cd 100644 --- a/session05/python/species.py +++ b/session05/species.py @@ -1,2 +1,2 @@ class Species(object): - pass + pass diff --git a/session06/figures/builder_seq.wsd b/session06/builder_seq.wsd similarity index 100% rename from session06/figures/builder_seq.wsd rename to session06/builder_seq.wsd diff --git a/session06/figures/aggregation.yuml b/session06/figures/aggregation.yuml deleted file mode 100644 index 0e1faa20..00000000 --- a/session06/figures/aggregation.yuml +++ /dev/null @@ -1 +0,0 @@ -[Model]<>-*>[Boid],[Boid]position++->[Vector],[Boid]velocity++->[Vector] \ No newline at end of file diff --git a/session06/figures/basic.yuml b/session06/figures/basic.yuml deleted file mode 100644 index f9516830..00000000 --- a/session06/figures/basic.yuml +++ /dev/null @@ -1 +0,0 @@ -[Particle|position;velocity|move()] diff --git a/session06/figures/builder.yuml b/session06/figures/builder.yuml deleted file mode 100644 index a727f8d1..00000000 --- a/session06/figures/builder.yuml +++ /dev/null @@ -1 +0,0 @@ -[Director|Construct()]<>->[Builder| (a) BuildPart()], [Builder]^-[ConcreteBuilder| BuildPart();GetResult() ],[ConcreteBuilder]-.->[Product] diff --git a/session06/figures/factory.yuml b/session06/figures/factory.yuml deleted file mode 100644 index 10823446..00000000 --- a/session06/figures/factory.yuml +++ /dev/null @@ -1 +0,0 @@ -[Product]^-[ConcreteProduct], [Creator| (v) FactoryMethod()]^-[ConcreteCreator| FactoryMethod()], [ConcreteCreator]-.->[ConcreteProduct] diff --git a/session06/figures/inheritance.yuml b/session06/figures/inheritance.yuml deleted file mode 100644 index 88f83dae..00000000 --- a/session06/figures/inheritance.yuml +++ /dev/null @@ -1 +0,0 @@ -[Animal]^-[Bird],[Bird]^-[Eagle],[Bird]^-[Starling] \ No newline at end of file diff --git a/session06/figures/interface.yuml b/session06/figures/interface.yuml deleted file mode 100644 index 146785db..00000000 --- a/session06/figures/interface.yuml +++ /dev/null @@ -1 +0,0 @@ -[<>]^-.-[Dog] diff --git a/session06/index.md b/session06/index.md new file mode 100644 index 00000000..94afe6f3 --- /dev/null +++ b/session06/index.md @@ -0,0 +1,5 @@ +--- +title: Session6 +--- + +Some content diff --git a/session06/python/agents.py b/session06/python/agents.py deleted file mode 100644 index 8d2994c9..00000000 --- a/session06/python/agents.py +++ /dev/null @@ -1,27 +0,0 @@ -### "model" -class AgentModel(object): - def simulate(self): - for agent in agents: - for target in agents: - agent.interact(target) - agent.simulate() -### "construct" -class AgentModel(object): - def __init__(self, config): - self.agents=[] - for agent_config in config: - self.agents.append(self.create(**agent_config)) - -### "boids" -class BirdModel(AgentModel): - def create(self, species): - return Boid(species) - -### "web" -class WebAgentFactory(AgentModel): - def __init__(self, url): - self.url=url - connection=AmazonCompute.connect(url) - AgentModel.__init__(self) - def create(self, species): - return OnlineAnimal(species, connection) diff --git a/session06/python/builder.py b/session06/python/builder.py deleted file mode 100644 index af25f27b..00000000 --- a/session06/python/builder.py +++ /dev/null @@ -1,40 +0,0 @@ -pass - -### "setup" - -from mock import Mock - -### "nobuilder" - -class Model(object): - def __init__(self, xsize, ysize, - agent_count, wind_speed, - agent_sight_range, eagle_start_location): - pass - -### "simplemodel" - -Model=Mock() - -### "builder" - -class ModelBuilder(object): - def start_model(self): - self.model=Model() - def set_bounds(self, xlim, ylim): - self.model.xlim=xlim - self.model.ylim=ylim - def add_agent(self, xpos, ypos): - pass # Implementation here - def finish(self): - return self.model - -### "use" - -builder=ModelBuilder() -builder.start_model() -builder.set_bounds(500,500) -builder.add_agent(40,40) -builder.add_agent(400,100) -model=builder.finish() -model.simulate() diff --git a/session06/python/comparison.png b/session06/python/comparison.png deleted file mode 100644 index d9022851..00000000 Binary files a/session06/python/comparison.png and /dev/null differ diff --git a/session06/python/design.py b/session06/python/design.py deleted file mode 100644 index 19a76eaa..00000000 --- a/session06/python/design.py +++ /dev/null @@ -1,152 +0,0 @@ - -### "pyclass" -class Particle(object): - def __init__(self, position, velocity): - self.position=position - self.velocity=velocity - def move(self, delta_t): - self.position+= self.velocity*delta_t -### "hiding" -class MyClass(object): - def __private_method(self): pass - def _private_method(self): pass - def public_method(self): pass - -#MyClass().__private_method() # Generates error -MyClass()._private_method() # Works, but forbidden by convention -MyClass().public_method() # OK -### "accessors1" -class Person(object): - def __init__(self): - self.name = "James Hetherington" - -assert(Person().name == "James Hetherington") -### "accessors2" -class Person(object): - def __init__(self): - self._first = "James" - self._second = "Hetherington" - @property - def name(self): - return self._first + " " + self._second - -assert(Person().name == "James Hetherington") -### "accessors3" -class Person(object): - def __init__(self): - self._name = "James Hetherington" - def name(self): - return self._name - -assert(Person().name() == "James Hetherington") -### "classmethod" -class Counted(object): - number_created=0 - def __init__(self): - Counted.number_created+=1 - @classmethod - def howMany(cls): - return cls.number_created - -Counted.howMany() # 0 -x=Counted() -Counted.howMany() # 1 -z=[Counted() for x in range(5)] -Counted.howMany() # 6 -### "inheritance" -class Animal(object): - def beBorn(self): print "I exist" - def die(self): print "Argh!" - -class Bird(Animal): - def fly(self): print "Whee!" - -class Eagle(Bird): - def hunt(self): print "I'm gonna eatcha!" - -Eagle().beBorn() -Eagle().hunt() -### "super" -class Animal(object): - def __init__(self, age): - self.age=age - -class Person(Animal): - def __init__(self, age, name): - super(Person, self).__init__(age) - self.name=name -### "inheritance_factor1" -class Person(object): - def __init__(self, age, job): - self.age = age - self.job = job - def birthday(self): - self.age += 1 - -class Pet(object): - def __init__(self, age, owner): - self.age = age - self.owner = owner - def birthday(self): - self.age += 1 -### "inheritance_factor2" -class Animal(object): - def __init__(self, age): - self.age = age - def birthday(self): - self.age += 1 - -class Person(Animal): - def __init__(self, age, job): - self.job = job - super(Person, self).__init__(age) -### "polymorphism" -class Dog(object): - def noise(self): - return "Bark" - -class Cat(object): - def noise(self): - return "Miaow" - -class Pig(object): - def noise(self): return "Oink" - -class Cow(object): - def noise(self): return "Moo" - -animals=[Dog(), Dog(), Cat(), Pig(), Cow(), Cat()] -for animal in animals: - print animal.noise() -### "base" -class Animal(object): - def noise(self): return "I don't make a noise." - -class Dog(Animal): - def noise(self): return "Bark" - -class Worm(Animal): - pass - -class Poodle(Animal): - pass - -animals=[Dog(), Worm(), Pig(), Cow(), Poodle()] -for animal in animals: - print animal.noise() -### "undefined" -class Animal(object): pass - -class Worm(Animal): pass - -# Worm().noise() # Generates error -### "case" -class Animal(object): - def __init__(self,type): self.type=type - def noise(self): - if self.type=="Dog": - return "Bark" - elif self.type=="Cat": - return "Miaow" - elif self.type=="Cow": - return "Moo" diff --git a/session06/python/deviation.png b/session06/python/deviation.png deleted file mode 100644 index 3316cdd7..00000000 Binary files a/session06/python/deviation.png and /dev/null differ diff --git a/session06/python/fixed.png b/session06/python/fixed.png deleted file mode 100644 index 601fdf5c..00000000 Binary files a/session06/python/fixed.png and /dev/null differ diff --git a/session06/python/mvc.py b/session06/python/mvc.py deleted file mode 100644 index 388f90e3..00000000 --- a/session06/python/mvc.py +++ /dev/null @@ -1,28 +0,0 @@ -### "model" -class Model(object): - def simulation_step(): - # Maths Here - pass -### "view" -class View(object): - def __init__(self, model): - from matplotlib import pyplot as plt - self.figure=plt.figure() - axes=plt.axes() - self.model=model - self.scatter=axes.scatter(model.agent_locations()[:,1], - model.agent_locations()[:,2]) - def update(self): - self.scatter.set_offsets(model.agent_locations()) -### "controller" -def Controller(object): - def __init__(self): - self.model=Model() # Or use Builder - self.view=View(self.model) - def animate(): - self.model.simulation_step() - self.view.update() - self.animator=animate - def go(self): - from matplotlib import animation - animation.FuncAnimation(self.view.figure, self.animator) diff --git a/session06/python/old_fragments/accessors.py b/session06/python/old_fragments/accessors.py deleted file mode 100644 index 2e5e92ca..00000000 --- a/session06/python/old_fragments/accessors.py +++ /dev/null @@ -1,9 +0,0 @@ -class Person(object): - def __init__(self): - self._name = "James Hetherington" - - @property - def name(self): - return self._name - -assert(Person().name == "James Hetherington") diff --git a/session06/python/old_fragments/accessors2.py b/session06/python/old_fragments/accessors2.py deleted file mode 100644 index 31eba752..00000000 --- a/session06/python/old_fragments/accessors2.py +++ /dev/null @@ -1,10 +0,0 @@ -class Person(object): - def __init__(self): - self._first = "James" - self._second = "Hetherington" - - @property - def name(self): - return self._first+" "+self._second - -assert(Person().name == "James Hetherington") diff --git a/session06/python/old_fragments/after.py b/session06/python/old_fragments/after.py deleted file mode 100644 index d74ccd1e..00000000 --- a/session06/python/old_fragments/after.py +++ /dev/null @@ -1,17 +0,0 @@ - -class Animal(object): - def __init__(self, age): - self.age = age - - def birthday(self): - self.age += 1 - -class Person(Animal): - def __init__(self, age, job): - self.job = job - super(Person, self).__init__(age) - - -me=Person(37,"Programmer") -me.birthday() -assert(me.age == 38) diff --git a/session06/python/old_fragments/before.py b/session06/python/old_fragments/before.py deleted file mode 100644 index a5b7ea83..00000000 --- a/session06/python/old_fragments/before.py +++ /dev/null @@ -1,19 +0,0 @@ -class Person(object): - def __init__(self, age, job): - self.age = age - self.job = job - - def birthday(self): - self.age += 1 - -class Pet(object): - def __init__(self, age, owner): - self.age = age - self.owner = owner - - def birthday(self): - self.age += 1 - -me=Person(37,"Programmer") -me.birthday() -assert(me.age == 38) diff --git a/session06/python/old_fragments/class.py b/session06/python/old_fragments/class.py deleted file mode 100644 index fa011e93..00000000 --- a/session06/python/old_fragments/class.py +++ /dev/null @@ -1,4 +0,0 @@ -class MyClass(object): - pass - -my_object=MyClass() diff --git a/session06/python/old_fragments/inheritance.py b/session06/python/old_fragments/inheritance.py deleted file mode 100644 index bec1c57a..00000000 --- a/session06/python/old_fragments/inheritance.py +++ /dev/null @@ -1,12 +0,0 @@ -class Animal(object): - def beBorn(self): print "I exist" - def die(self): print "Argh!" - -class Bird(Animal): - def fly(self): print "Whee!" - -class Eagle(Bird): - def hunt(self): print "I'm gonna eatcha!" - -Eagle().beBorn() # prints "I exist" -Eagle().hunt() # prints "I'm gonna eatcha!" diff --git a/session06/python/old_fragments/member.py b/session06/python/old_fragments/member.py deleted file mode 100644 index 484d2fd5..00000000 --- a/session06/python/old_fragments/member.py +++ /dev/null @@ -1,6 +0,0 @@ -class MyClass(object): - def __init__(self): - self.member = "Value" - -my_object = MyClass() -assert(my_object.member == "Value") diff --git a/session06/python/old_fragments/method.py b/session06/python/old_fragments/method.py deleted file mode 100644 index 1663d449..00000000 --- a/session06/python/old_fragments/method.py +++ /dev/null @@ -1,7 +0,0 @@ -class MyClass(object): - def someMethod(self, argument): - pass - -my_object=MyClass() -value=5 -my_object.someMethod(value) diff --git a/session06/python/old_fragments/polymorphism.py b/session06/python/old_fragments/polymorphism.py deleted file mode 100644 index 02a17964..00000000 --- a/session06/python/old_fragments/polymorphism.py +++ /dev/null @@ -1,22 +0,0 @@ -class Animal(object): pass - -class Dog(Animal): - def noise(self): - return "Bark" - -class Cat(Animal): - def noise(self): - return "Miaow" - -class Pig(Animal): - def noise(self): - return "Oink" - -class Cow(Animal): - def noise(self): - return "Moo" - -animals=[Dog(), Dog(), Cat(), Pig(), Cow(), Cat()] -for animal in animals: - print animal.noise() - diff --git a/session06/python/old_fragments/polymorphism_before.py b/session06/python/old_fragments/polymorphism_before.py deleted file mode 100644 index 2ef4779e..00000000 --- a/session06/python/old_fragments/polymorphism_before.py +++ /dev/null @@ -1,16 +0,0 @@ -class Animal(object): - def __init__(self,type): self.type=type - def noise(self): - if self.type=="Dog": - return "Bark" - elif self.type=="Cat": - return "Miaow" - elif self.type=="Cow": - return "Moo" - elif self.type=="Pig": - return "Oink" - -animals=[Animal("Dog"), Animal("Dog"), Animal("Cat"), Animal("Pig"), Animal("Cow"), Animal("Cat")] -for animal in animals: - print animal.noise() - diff --git a/session06/python/old_fragments/test_access.py b/session06/python/old_fragments/test_access.py deleted file mode 100644 index 4058242d..00000000 --- a/session06/python/old_fragments/test_access.py +++ /dev/null @@ -1,15 +0,0 @@ -from nose.tools import raises - -class MyClass(object): - def __secret_method(self): pass - def _semi_secret_method(self): pass - def public_method(self): - self.__secret_method() # OK - -@raises(AttributeError) -def test_secret(): - MyClass().__secret_method() # Generates error - -def test_unsecret(): - MyClass()._semi_secret_method() # Works, but forbidden by convention - MyClass().public_method() # OK diff --git a/session06/python/spots.png b/session06/python/spots.png deleted file mode 100644 index 5e3cedd2..00000000 Binary files a/session06/python/spots.png and /dev/null differ diff --git a/session06/python/sunspots.py b/session06/python/sunspots.py deleted file mode 100644 index 74700ea5..00000000 --- a/session06/python/sunspots.py +++ /dev/null @@ -1,158 +0,0 @@ -### "imports" - -from numpy import linspace,exp,log,sqrt, array -import math -from scipy.interpolate import UnivariateSpline -from scipy.signal import lombscargle -from scipy.integrate import cumtrapz -from numpy.fft import rfft,fft,fftfreq -import csv -from StringIO import StringIO -from datetime import datetime -import requests -import matplotlib.pyplot as plt - -### "load_data" - -def load_sunspots(): - url_base="http://www.quandl.com/api/v1/datasets/SIDC/SUNSPOTS_A.csv" - x=requests.get(url_base,params={'trim_start':'1700-12-31', - 'trim_end':'2014-01-01', - 'sort_order':'asc'}) - data=csv.reader(StringIO(x.text)) #Convert requests result to look - #like a file buffer before - # reading with CSV - data.next() # Skip header row - return [float(row[1]) for row in data] - -### "InitialFigure" -spots=load_sunspots() -plt.plot(spots) -plt.savefig('spots.png') - -### "naive_fft" -spectrum=rfft(spots) - -### "FFT figure" -plt.figure() -plt.plot(abs(spectrum)) -plt.savefig('fixed.png') - -### "Series" - -class Series(object): - """Enhance NumPy N-d array with some helper functions for clarity""" - def __init__(self, data): - self.data=array(data) - self.count=self.data.shape[0] - self.start=self.data[0,0] - self.end=self.data[-1,0] - self.range=self.end-self.start - self.step=self.range/self.count - self.times=self.data[:,0] - self.values=self.data[:,1] - self.plot_data=[self.times,self.values] - self.inverse_plot_data=[1.0/self.times[20:], self.values[20:]] - - -### "Client" - -class AnalyseSunspotData(object): - def format_date(self, date): - date_format="%Y-%m-%d" - return datetime.strptime(date,date_format) - def load_data(self): - start_date_str='1700-12-31' - end_date_str='2014-01-01' - self.start_date=self.format_date(start_date_str) - end_date=self.format_date(end_date_str) - url_base=("http://www.quandl.com/api/v1/datasets/"+ - "SIDC/SUNSPOTS_A.csv") - x=requests.get(url_base,params={'trim_start':start_date_str, - 'trim_end':end_date_str, - 'sort_order':'asc'}) - secs_per_year=(datetime(2014,1,1)-datetime(2013,1,1) - ).total_seconds() - data=csv.reader(StringIO(x.text)) #Convert requests - #result to look - #like a file buffer before - #reading with CSV - data.next() # Skip header row - self.series=Series([[ - (self.format_date(row[0])-self.start_date - ).total_seconds()/secs_per_year - ,float(row[1])] for row in data]) - def __init__(self, frequency_strategy): - self.load_data() - self.frequency_strategy=frequency_strategy - def frequency_data(self): - return self.frequency_strategy.transform(self.series) - -### "Naive" - -class FourierNearestFrequencyStrategy(object): - def transform(self, series): - transformed=fft(series.values)[0:series.count/2] - frequencies=fftfreq(series.count, series.step)[0:series.count/2] - return Series(zip(frequencies, abs(transformed)/series.count)) - - -### "Spline" - -class FourierSplineFrequencyStrategy(object): - def next_power_of_two(self, value): - "Return the next power of 2 above value" - return 2**(1+int(log(value)/log(2))) - def transform(self, series): - spline=UnivariateSpline(series.times, series.values) - # Linspace will give us *evenly* spaced points in the series - fft_count= self.next_power_of_two(series.count) - points=linspace(series.start,series.end,fft_count) - regular_xs=[spline(point) for point in points] - transformed=fft(regular_xs)[0:fft_count/2] - frequencies=fftfreq(fft_count, - series.range/fft_count)[0:fft_count/2] - return Series(zip(frequencies, abs(transformed)/fft_count)) - - -### "Lomb" - -class LombFrequencyStrategy(object): - def transform(self,series): - frequencies=array(linspace(1.0/series.range, - 0.5/series.step,series.count)) - result= lombscargle(series.times, - series.values,2.0*math.pi*frequencies) - return Series(zip(frequencies, sqrt(result/series.count))) - - -### "Declare" - -fourier_model=AnalyseSunspotData(FourierSplineFrequencyStrategy()) -lomb_model=AnalyseSunspotData(LombFrequencyStrategy()) -nearest_model=AnalyseSunspotData(FourierNearestFrequencyStrategy()) - - -### "Analyze" - -comparison=fourier_model.frequency_data().inverse_plot_data+['r'] -comparison+=lomb_model.frequency_data().inverse_plot_data+['g'] -comparison+=nearest_model.frequency_data().inverse_plot_data+['b'] -deviation=365*(fourier_model.series.times-linspace( - fourier_model.series.start, - fourier_model.series.end, - fourier_model.series.count)) - -### "FinalPlots" - -plt.figure() -plt.plot(*comparison) -plt.xlim(0,16) -plt.savefig('comparison.png') - -plt.figure() -plt.plot(deviation) -plt.savefig('deviation.png') - - - diff --git a/session06/session06.ipynb b/session06/session06.ipynb index 24184d85..89060d3c 100644 --- a/session06/session06.ipynb +++ b/session06/session06.ipynb @@ -1,2584 +1,3355 @@ { - "metadata": { - "name": null - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n---\ntitle: Research Software Engineering with Python\nauthor: James Hetherington and Matt Clarkson\n---\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn this session, we will finally discuss the thing most people think of when they refer to \"Software Engineering\": the deliberate *design* of software.\nWe will discuss processes and methodologies for planned development of large-scale software projects: *Software Architecture*.\n\nThis lecture is available as an [IPython Notebook](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session06/../python/session06.ipynb)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Object-Oriented Design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe software engineering community has, in large part, focused on an object-oriented approach to the design and development of large scale software systems.\nThe basic concepts of object orientation are necessary to follow much of the software engineering conversation.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Design processes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn addition to object-oriented architecture, software engineers have focused on the development of processes for robust, reliable software development. \nThese codified ways of working hope to enable organisations to repeatably and reliably complete complex software projects in a way that minimises both development \nand maintainance costs, and meets user requirements.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Design and research" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSoftware engineering theory has largely been developed in the context of commercial software companies.\n\nThe extent to which the practices and processes developed for commercial software are applicable in a research context is itself an active area of research.\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "More on Objects" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Object Based Programming Recap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe saw last lesson how to \n\n* create our own classes\n* store data with member variables\n* add functionality with member functions\n* initialise objects with constructors\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Class design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe concepts we have introduced are common between different object oriented languages.\nThus, when we design our program using these concepts, we can think at an architectural level,\nindependent of language syntax.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Particle(object):", - " def __init__(self, position, velocity):", - " self.position=position", - " self.velocity=velocity", - " def move(self, delta_t):", - " self.position+= self.velocity*delta_t", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "class Particle {\n std::vector position;\n std::vector velocity;\n Particle(std::vector position, std::vector velocity);\n void move(double delta_t);\n}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "type particle\n real :: position\n real :: velocity\n contains\n procedure :: init\n procedure :: move\nend type particle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUML is a conventional diagrammatic notation used to describe \"class structures\" and other higher level\naspects of software design.\n\nComputer scientists get worked up about formal correctness of UML diagrams and learning the conventions precisely.\nWorking programmers can still benefit from using UML to describe their designs.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "UML for objects" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUML represents class members and methods like this:\n\n![Basic class UML](../figures/basic.png)\n\n(The above diagram is generated by the following:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "http://yuml.me/diagram/boring/class/[Particle|position;velocity|move()]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nusing the [YUML](http://yuml.me/) online UML drawing tool.\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Information Hiding" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSometimes, our design for a program would be broken if users start messing around with variables we don't want them to change.\n\nRobust class design requires consideration of which subroutines are intended for users to use, and which are internal.\nLanguages provide features to implement this: access control. \n\nIn python, we use leading underscores to control whether member variables and methods can be accessed from outside the class.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class MyClass(object):", - " def __private_method(self): pass", - " def _private_method(self): pass", - " def public_method(self): pass", - "", - "#MyClass().__private_method() # Generates error", - "MyClass()._private_method() # Works, but forbidden by convention", - "MyClass().public_method() # OK", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Property accessors" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nPython provides a mechanism to make functions appear to be variables. This can be used if you want to\nchange the way a class is implemented without changing the interface:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self):", - " self.name = \"James Hetherington\"", - "", - "assert(Person().name == \"James Hetherington\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nbecomes:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self):", - " self._first = \"James\"", - " self._second = \"Hetherington\"", - " @property", - " def name(self):", - " return self._first + \" \" + self._second", - "", - "assert(Person().name == \"James Hetherington\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nNote that the code behaves the same way to the outside user.\nThe implementation detail is hidden by private variables.\nIn languages without this feature, such as C++, it is best to always\nmake data private, and always\naccess data through functions:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self):", - " self._name = \"James Hetherington\"", - " def name(self):", - " return self._name", - "", - "assert(Person().name() == \"James Hetherington\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nBut in Python this is unnecessary.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Class Members" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n*Class*, or *static* members, belong to the class as a whole, and are shared between instances.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Counted(object):", - " number_created=0", - " def __init__(self):", - " Counted.number_created+=1", - " @classmethod", - " def howMany(cls):", - " return cls.number_created", - "", - "Counted.howMany() # 0", - "x=Counted()", - "Counted.howMany() # 1", - "z=[Counted() for x in range(5)]", - "Counted.howMany() # 6 ", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Object-based vs Object-Oriented" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSo far we have seen only object-based programming, not object-oriented programming.\n\nUsing Objects doesn't mean your code is object-oriented.\n\nTo understand object-oriented programming, we need to introduce **polymorphism** and **inheritance**.\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Inheritance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Inheritance allows related classes to share code\n* Inheritance allows a program to reflect the *ontology* of kinds of thing in a program.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Ontology and inheritance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* A bird is a kind of animal\n* An eagle is a kind of bird\n* A starling is also a kind of bird\n\n* All animals can be born and die\n* Only birds can fly (Ish.)\n* Only eagles hunt\n* Only starlings flock\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Inheritance in python" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object):", - " def beBorn(self): print \"I exist\"", - " def die(self): print \"Argh!\"", - "", - "class Bird(Animal):", - " def fly(self): print \"Whee!\"", - "", - "class Eagle(Bird):", - " def hunt(self): print \"I'm gonna eatcha!\"", - "", - "Eagle().beBorn()", - "Eagle().hunt()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Inheritance terminology" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* A *derived class* _derives_ from a *base class*\n* A *subclass* _inherits_ from a *superclass*\n\n(These are different terms for the same thing.)\n\n* Eagle is a subclass of the Animal superclass.\n* Animal is the base class of the Eagle derived class\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Inheritance and constructors" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object):", - " def __init__(self, age):", - " self.age=age", - "", - "class Person(Animal):", - " def __init__(self, age, name):", - " super(Person, self).__init__(age)", - " self.name=name", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Inheritance UML diagrams" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUML shows inheritance with an open triangular arrow pointing from subclass to superclass.\n\n![Bird inheritance diagram](../figures/inheritance.png)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Aggregation vs Inheritance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIf one object *has* or *owns* one or more objects, this is *not* inheritance.\n\nFor example, in my solution to the Boids task from last week, the overal Model owned several Boids,\nand each Boid owned two 2-vectors, one for position and one for velocity.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Aggregation in UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe Boids situation can be represented thus:\n\n![Boid aggregation diagram](../figures/aggregation.png)\n\nThe open diamond indicates **Aggregation**, the closed diamond **composition**.\n(A given boid might belong to multiple models, a given position vector is forever part of the corresponding Boid.)\n\nThe asterisk represents cardinality, a model may contain multiple Boids.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to inheritance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSmell: Repeated code between two classes which are both ontologically subtypes of something\n\nBefore:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self, age, job): ", - " self.age = age", - " self.job = job", - " def birthday(self): ", - " self.age += 1", - "", - "class Pet(object):", - " def __init__(self, age, owner): ", - " self.age = age", - " self.owner = owner", - " def birthday(self): ", - " self.age += 1", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAfter:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object):", - " def __init__(self, age): ", - " self.age = age", - " def birthday(self): ", - " self.age += 1", - "", - "class Person(Animal):", - " def __init__(self, age, job):", - " self.job = job", - " super(Person, self).__init__(age)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Polymorphism" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Polymorphism" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Dog(object):", - " def noise(self):", - " return \"Bark\"", - "", - "class Cat(object):", - " def noise(self):", - " return \"Miaow\"", - "", - "class Pig(object):", - " def noise(self): return \"Oink\"", - "", - "class Cow(object):", - " def noise(self): return \"Moo\"", - "", - "animals=[Dog(), Dog(), Cat(), Pig(), Cow(), Cat()]", - "for animal in animals:", - " print animal.noise()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis will print \"Bark Bark Miaow Oink Moo Miaow\"\n\nIf two classes support the same method, but it does different things for the two classes, \nthen if an object is of an unknown class, calling the method will invoke the version for\nwhatever class the instance is an instance of.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Polymorphism and Inheritance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nOften, polymorphism uses multiple derived classes with a common base class.\nHowever, duck typing in Python means that all that is required is that the \ntypes support a common **Concept** (Such as iterable, or container, or, in this case, the\nNoisy concept.)\n\nA common base class is used where there is a likely **default** that you want several\nof the derived classes to have.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object):", - " def noise(self): return \"I don't make a noise.\"", - "", - "class Dog(Animal):", - " def noise(self): return \"Bark\"", - "", - "class Worm(Animal):", - " pass", - "", - "class Poodle(Animal):", - " pass", - "", - "animals=[Dog(), Worm(), Pig(), Cow(), Poodle()]", - "for animal in animals:", - " print animal.noise()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Undefined Functions and Polymorphism" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn the above example, we put in a dummy noise for Animals that don't know what type they are.\n\nInstead, we can explicitly deliberately leave this undefined, and we get a crash if we access an undefined method.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object): pass", - "", - "class Worm(Animal): pass", - "", - "# Worm().noise() # Generates error", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to Polymorphism" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nSmell: a function uses a big set of `if` statements or a `case` statement to decide what to do:\n\nBefore:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Animal(object):", - " def __init__(self,type): self.type=type", - " def noise(self): ", - " if self.type==\"Dog\":", - " return \"Bark\"", - " elif self.type==\"Cat\":", - " return \"Miaow\"", - " elif self.type==\"Cow\":", - " return \"Moo\"", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nwhich is better replaced by the code above.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Interfaces and concepts" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn C++, it is common to define classes which declare dummy methods, called \"virtual\" methods, which specify\nthe methods which derived classes must implement. Classes which define these methods, which cannot be instantiated\ninto actual objects, are called \"abstract base\" classes or \"interfaces\".\n\nPython's Duck Typing approach means explicitly declaring these is unnesssary: any class concept which implements\nappropriately named methods will do. These as user-defined **concepts**, just as \"iterable\" or \"container\" are \nbuilt-in Python concepts. A class is said to \"implement and interface\" or \"satisfy a concept\".\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Interfaces in UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nInterfaces implementation in UML is indicated thus:\n\n![Interfaces in UML](../figures/interface.png)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Further UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUML is a much larger diagram language than the aspects we've shown here.\n\n* Message sequence charts show signals passing back and forth between objects ([Web Sequence Diagrams](https://www.websequencediagrams.com/))\n\n* Entity Relationship Diagrams can be used to show more general relationships between things in a system\n\n\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Patterns" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Class Complexity" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe've seen that using object orientation can produce quite complex class structures, with classes owning each other, instantiating each other,\nand inheriting from each other.\n\nThere are lots of different ways to design things, and decisions to make.\n\n> Should I inherit from this class, or own it as a member variable? (\"is a\" vs \"has a\")\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Design Patterns" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nProgrammers have noticed that there are certain ways of arranging classes that work better than others.\n\nThese are called \"design patterns\".\n\nThey were first collected on one of the [world's first Wikis](http://c2.com/cgi/wiki?WelcomeVisitors), \nas the [Portland Pattern Repository](http://c2.com/cgi-bin/wiki?PatternIndex)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Reading a pattern" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nA description of a pattern in a book such as the [Gang Of Four](http://www.amazon.co.uk/Design-patterns-elements-reusable-object-oriented/dp/0201633612)\nbook usually includes:\n\n* Intent\n* Motivation\n* Applicability\n* Structure\n* Participants\n* Collaborations\n* Consequences\n* Implementation\n* Sample Code\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Introducing Some Patterns" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThere are lots and lots of design patterns, and it's a great literature to get into to\nread about design questions in programming and learn from other people's experience.\n\nWe'll just show a few in this session:\n\n* Factory Method\n* Builder\n* Handle-Body\n* Strategy\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Factory Pattern" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Factory Pattern" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nHere's what the Gang of Four Book says about Factory Method:\n\nIntent: Define an interface for creating an object, but let subclasses decide which class to instantiate.\nFactory Method lets a class defer instantiation to subclasses.\n\nApplicability: Use the Factory method pattern when:\n\n* A class can't anticipate the class of objects it must create\n* A class wants its subclasses to specify the objects it creates\n\nThis is pretty hard to understand, so let's look at an example.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Factory UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n![Structure](../figures/factory.png)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Factory Example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAn \"agent based model\" is one like the Boids model from last week:\nagents act and interact under certain rules. Complex phenomena can be described by simple\nagent behaviours.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class AgentModel(object):", - " def simulate(self):", - " for agent in agents:", - " for target in agents:", - " agent.interact(target)", - " agent.simulate()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Agent model constructor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThis logic is common to many kinds of Agent based model, so we can imagine a common class\nfor agent based models: the constructor could parse a configuration specifying how many agents of each type to create,\ntheir initial conditions and so on.\n\nHowever, this common constructor doesn't know what kind of agent to create; as a common base, it could be a model of boids,\nor the agents could be remote agents on foreign servers, or they could even be physical hardware robots connected to the driving model\nover Wifi!\n\nWe need to defer the construction of the agents. We can do this with polymorphism: each derived class of the ABM can have an appropriate\nmethod to create its agents:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class AgentModel(object):", - " def __init__(self, config):", - " self.agents=[]", - " for agent_config in config:", - " self.agents.append(self.create(**agent_config))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis is the *factory method* pattern: a common design solution to the need to defer the construction of daughter objects to a derived class.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Agent derived classes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class BirdModel(AgentModel):", - " def create(self, species):", - " return Boid(species)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAgents are the base product, boids or robots are a ConcreteProduct.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class WebAgentFactory(AgentModel):", - " def __init__(self, url):", - " self.url=url", - " connection=AmazonCompute.connect(url)", - " AgentModel.__init__(self)", - " def create(self, species):", - " return OnlineAnimal(species, connection)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThere is no need to define an explicit base interface for the \"Agent\" concept in Python: anything that responds to \"simulate\" and \"interact\" \nmethods will do: this is our Agent concept.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to Patterns" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nI personally have got into a terrible tangle trying to make base classes which somehow\n\"promote\" themselves into a derived class based on some code in the base class.\n\nThis is an example of an \"Antipattern\": like a Smell, this is a recognised Wrong Way\nof doing things.\n\nWhat I should have written was a Creator with a FactoryMethod.\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Builder" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from mock import Mock", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Builder Pattern" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIntent: Separate the steps for constructing a complex object from its final representation.\n\n![UML](../figures/builder.png)\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Builder example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nLet's continue our Agent Based modelling example.\n\nThere's a lot more to defining a model than just adding agents of different kinds: we need to define boundary conditions,\nspecify wind speed or light conditions.\n\nWe could define all of this for an imagined advanced Model with a very very long constructor, with lots of optional arguments:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Model(object):", - " def __init__(self, xsize, ysize,", - " agent_count, wind_speed,", - " agent_sight_range, eagle_start_location):", - " pass", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Builder preferred to complex constructor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nHowever, long constructors easily become very complicated. Instead, it can be cleaner to define a Builder for models. A builder is like a \ndeferred factory: each step of the construction process is implemented as an individual method call, and the completed object\nis returned when the model is ready.\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "Model=Mock()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class ModelBuilder(object):", - " def start_model(self):", - " self.model=Model()", - " def set_bounds(self, xlim, ylim):", - " self.model.xlim=xlim", - " self.model.ylim=ylim", - " def add_agent(self, xpos, ypos):", - " pass # Implementation here", - " def finish(self):", - " return self.model", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nInheritance of an Abstract Builder for multiple concrete builders could be used where there might be multiple ways to build models\nwith the same set of calls to the builder: for example a version of the model builder yielding models which can be executed\nin parallel on a remote cluster.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Using a builder" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "builder=ModelBuilder()", - "builder.start_model()", - "builder.set_bounds(500,500)", - "builder.add_agent(40,40)", - "builder.add_agent(400,100)", - "model=builder.finish()", - "model.simulate()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Avoid staged construction without a builder." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe could, of course, just add all the building methods to the model itself, rather than having the model be yielded from a separate builder.\n\nThis is an antipattern that is often seen: a class whose `__init__` constructor alone is insufficient for it to be ready to use. A series of\nmethods must be called, in the right order, in order for it to be ready to use.\n\nThis results in very fragile code: its hard to keep track of whether an object instance is \"ready\" or not. Use of the builder pattern to\nkeep deferred construction in \n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Builder Message Sequence" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n![MessageSequence](../figures/builder_seq.png)\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Strategy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from numpy import linspace,exp,log,sqrt, array", - "import math", - "from scipy.interpolate import UnivariateSpline", - "from scipy.signal import lombscargle", - "from scipy.integrate import cumtrapz", - "from numpy.fft import rfft,fft,fftfreq", - "import csv", - "from StringIO import StringIO", - "from datetime import datetime", - "import requests", - "import matplotlib.pyplot as plt", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n%matplotlib inline\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Strategy Pattern" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nDefine a family of algorithms, encapsulate each one, and make them interchangeable. \nStrategy lets the algorithm vary independently from clients that use it.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy pattern example: sunspots" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nConsider the sequence of sunspot observations:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def load_sunspots():", - " url_base=\"http://www.quandl.com/api/v1/datasets/SIDC/SUNSPOTS_A.csv\"", - " x=requests.get(url_base,params={'trim_start':'1700-12-31',", - " 'trim_end':'2014-01-01',", - " 'sort_order':'asc'})", - " data=csv.reader(StringIO(x.text)) #Convert requests result to look ", - " #like a file buffer before ", - " # reading with CSV", - " data.next() # Skip header row", - " return [float(row[1]) for row in data]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "spots=load_sunspots()", - "plt.plot(spots)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Sunspot cycle has periodicity" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "spectrum=rfft(spots)", - "", - "### \"FFT figure\"", - "plt.figure()", - "plt.plot(abs(spectrum))", - "plt.savefig('fixed.png')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(spectrum)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Years are not constant length" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThere's a potential problem with this analysis however:\n\n* Years are not constant length\n* Leap years exist\n* But, the Fast Fourier Transform assumes evenly spaced intervals\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Uneven time series" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe Fast Fourier Transform cannot be applied to uneven time series.\n\nWe could:\n\n* Ignore this problem, and assume the effect is small\n* Interpolate and resample to even times\n* Use a method which is robust to unevenly sampled series, such as LSSA\n\nWe also want to find the period of the strongest periodic signal in the data, there are\nvarious different methods we could use for this also, such as integrating the fourier series\nby quadrature to find the mean frequency, or choosing the largest single value. \n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Uneven time series design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe could implement a base class for our common code between the different approaches,\nand define derived classes for each different algorithmic approach. However, this has drawbacks:\n\n* The constructors for each derived class will need arguments for all the numerical method's control parameters,\nsuch as the degree of spline for the interpolation method, the order of quadrature for integrators, and so on.\n* Where we have multiple algorithmic choices to make (interpolator, periodogram, peak finder...) the number\nof derived classes would explode: `class SunspotAnalyzerSplineFFTTrapeziumNearMode` is a bit unweildy.\n* The algorithmic choices are not then available for other projects\n* This design doesn't fit with a clean Ontology of \"kinds of things\": there's no Abstract Base for spectrogram generators...\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* We implement each algorithm for generating a spectrum as its own Strategy class.\n* They all implement a common interface\n* Arguments to strategy constructor specify parameters of algorithms, such as spline degree\n* One strategy instance for each algorithm is passed to the constructor for the overall analysis\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nFirst, we'll define a helper class for our time series.\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Series(object):", - " \"\"\"Enhance NumPy N-d array with some helper functions for clarity\"\"\"", - " def __init__(self, data):", - " self.data=array(data)", - " self.count=self.data.shape[0]", - " self.start=self.data[0,0]", - " self.end=self.data[-1,0]", - " self.range=self.end-self.start", - " self.step=self.range/self.count", - " self.times=self.data[:,0]", - " self.values=self.data[:,1]", - " self.plot_data=[self.times,self.values]", - " self.inverse_plot_data=[1.0/self.times[20:], self.values[20:]]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThen, our class which contains the analysis code, *except* the numerical methods\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class AnalyseSunspotData(object):", - " def format_date(self, date):", - " date_format=\"%Y-%m-%d\"", - " return datetime.strptime(date,date_format)", - " def load_data(self):", - " start_date_str='1700-12-31'", - " end_date_str='2014-01-01'", - " self.start_date=self.format_date(start_date_str)", - " end_date=self.format_date(end_date_str)", - " url_base=(\"http://www.quandl.com/api/v1/datasets/\"+", - " \"SIDC/SUNSPOTS_A.csv\")", - " x=requests.get(url_base,params={'trim_start':start_date_str,", - " 'trim_end':end_date_str,", - " 'sort_order':'asc'})", - " secs_per_year=(datetime(2014,1,1)-datetime(2013,1,1)", - " ).total_seconds()", - " data=csv.reader(StringIO(x.text)) #Convert requests", - " #result to look", - " #like a file buffer before", - " #reading with CSV", - " data.next() # Skip header row", - " self.series=Series([[", - " (self.format_date(row[0])-self.start_date", - " ).total_seconds()/secs_per_year", - " ,float(row[1])] for row in data])", - " def __init__(self, frequency_strategy):", - " self.load_data()", - " self.frequency_strategy=frequency_strategy", - " def frequency_data(self):", - " return self.frequency_strategy.transform(self.series)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nOur existing simple fourier strategy\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class FourierNearestFrequencyStrategy(object):", - " def transform(self, series):", - " transformed=fft(series.values)[0:series.count/2]", - " frequencies=fftfreq(series.count, series.step)[0:series.count/2]", - " return Series(zip(frequencies, abs(transformed)/series.count))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nA strategy based on interpolation to a spline\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class FourierSplineFrequencyStrategy(object):", - " def next_power_of_two(self, value):", - " \"Return the next power of 2 above value\"", - " return 2**(1+int(log(value)/log(2)))", - " def transform(self, series):", - " spline=UnivariateSpline(series.times, series.values)", - " # Linspace will give us *evenly* spaced points in the series", - " fft_count= self.next_power_of_two(series.count)", - " points=linspace(series.start,series.end,fft_count)", - " regular_xs=[spline(point) for point in points]", - " transformed=fft(regular_xs)[0:fft_count/2]", - " frequencies=fftfreq(fft_count,", - " series.range/fft_count)[0:fft_count/2]", - " return Series(zip(frequencies, abs(transformed)/fft_count))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nA strategy using the Lomb-Scargle Periodogram\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class LombFrequencyStrategy(object):", - " def transform(self,series):", - " frequencies=array(linspace(1.0/series.range,", - " 0.5/series.step,series.count))", - " result= lombscargle(series.times,", - " series.values,2.0*math.pi*frequencies)", - " return Series(zip(frequencies, sqrt(result/series.count)))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nDefine our concrete solutions with particular strategies\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "fourier_model=AnalyseSunspotData(FourierSplineFrequencyStrategy())", - "lomb_model=AnalyseSunspotData(LombFrequencyStrategy())", - "nearest_model=AnalyseSunspotData(FourierNearestFrequencyStrategy())", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Strategy Pattern for Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUse these new tools to compare solutions\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "comparison=fourier_model.frequency_data().inverse_plot_data+['r']", - "comparison+=lomb_model.frequency_data().inverse_plot_data+['g']", - "comparison+=nearest_model.frequency_data().inverse_plot_data+['b']", - "deviation=365*(fourier_model.series.times-linspace(", - " fourier_model.series.start,", - " fourier_model.series.end,", - " fourier_model.series.count))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Comparison of different algorithms for frequency spectrum of sunspots." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(*comparison)", - "plt.xlim(0,16)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Deviation of year length from average" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nplt.plot(deviation)\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Model-View-Controller" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Separate graphics from science!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWhenever we are coding a simulation or model we want to:\n\n* Implement the maths of the model\n* Visualise, plot, or print out what is going on.\n\nWe often see scientific programs where the code which is used to display what is happening is mixed up with the\nmathematics of the analysis. This is hard to understand.\n\nWe can do better by separating the `Model` from the `View`, and using a \"`Controller`\" to manage them.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Model(object):", - " def simulation_step():", - " # Maths Here", - " pass", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "View" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class View(object):", - " def __init__(self, model):", - " from matplotlib import pyplot as plt", - " self.figure=plt.figure()", - " axes=plt.axes()", - " self.model=model", - " self.scatter=axes.scatter(model.agent_locations()[:,1],", - " model.agent_locations()[:,2])", - " def update(self):", - " self.scatter.set_offsets(model.agent_locations())", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Controller" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def Controller(object):", - " def __init__(self):", - " self.model=Model() # Or use Builder", - " self.view=View(self.model)", - " def animate():", - " self.model.simulation_step()", - " self.view.update()", - " self.animator=animate", - " def go(self):", - " from matplotlib import animation", - " animation.FuncAnimation(self.view.figure, self.animator)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Engineering" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Software Engineering Stages" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Requirements\n* Functional Design\n* Architectural Design\n* Implementation\n* Integration\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Requirements Engineering" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nRequirements capture obviously means describing the things the software needs to be able to do.\n\nA common approach is to write down lots of \"user stories\", describing how the software helps the user achieve something:\n\n> As a clinician, when I finish an analysis, I want a report to be created on the test results, so that I can\n> send it to the patient.\n\nAs a *role*, when *condition or circumstance applies* I want *a goal or desire* so that *benefits occur*.\n\nThese are easy to map into the Gherkin behaviour driven design test language.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Functional and architectural design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nEngineers try to separate the functional design, how the software appears to and is used by the user, from the\narchitectural design, how the software achieves that functionality.\n\nChanges to functional design require users to adapt, and are thus often more costly than changes to architectural design.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Waterfall" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe _Waterfall_ design philosophy argues that the elements of design should occur in order: first requirements capture, then functional design,\nthen architectural design. This approach is based on the idea that if a mistake is made in the design, then programming effort is wasted,\nso significant effort is spent in trying to ensure that requirements are well understood and that the design is correct before programming starts.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Why Waterfall?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWithout a design approach, programmers resort to designing as we go, typing in code, trying what works, and making it up as we go along.\nWhen trying to collaborate to make software with others this can result in lots of wasted time, software that only the author understands,\ncomponents built by colleagues that don't work together, or code that the programmer thinks is nice but that doesn't meet the user's requirements.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Problems with Waterfall" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWaterfall results in a contractual approach to development, building an us-and-them relationship between users, business types, designers, and programmers.\n\n> I built what the design said, so I did my job.\n\nWaterfall results in a paperwork culture, where people spend a long time designing standard forms to document each stage of the design,\nwith less time actually spent *making things*.\n\nWaterfall results in excessive adherence to a plan, even when mistakes in the design are obvious to people doing the work.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Software is not made of bricks" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe waterfall approach to software engineering comes from the engineering tradition applied to building physical objects,\nwhere Architects and Engineers design buildings, and builders build them according to the design.\n\nSoftware is intrinsically different:\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Software is not made of bricks" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n> Software is not the same 'stuff' as that from which physical systems are constructed.\nSoftware systems differ in material respects from physical systems.\nMuch of this has been rehearsed by Fred Brooks in his classic\n['No Silver Bullet'](http://ieeexplore.ieee.org/xpl/login.jsp?reload=true&tp=&arnumber=1663532&url=http%3A%2F%2Fieeexplore.ieee.org%2Fxpls%2Fabs_all.jsp%3Farnumber%3D1663532) paper.\nFirst, complexity and scale are different in the case of software systems: relatively functionally simple software systems comprise more independent parts, placed\nin relation to each other, than do physical systems of equivalent functional value.\nSecond, and clearly linked to this, we do not have well developed components and composition mechanisms from which to build\nsoftware systems (though clearly we are working hard on providing these) nor do we have a straightforward mathematical account that\npermits us to reason about the effects of composition.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Software is not made of bricks" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n> Third, software systems operate in a domain determined principally by arbitrary rules about information and symbolic communication whilst the\noperation of physical systems is governed by the laws of physics.\nFinally, software is readily changeable and thus is changed, it is used in settings where our uncertainty leads us to anticipate the need to change.\n\n-- Prof. [Anthony Finkelstein](http://blog.prof.so/), UCL Dean of Engineering, and Professor of Software Systems Engineering\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "The Agile Manifesto" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn 2001, authors including Martin Folwer, Ward Cunningham and Kent Beck met in a Utah ski resort, and published the following manifesto.\n\n [Manifesto for Agile Software Development](http://agilemanifesto.org/)\n\n We are uncovering better ways of developing\n software by doing it and helping others do it.\n Through this work we have come to value:\n\n * _Individuals and interactions_ over processes and tools\n * _Working software_ over comprehensive documentation\n * _Customer collaboration_ over contract negotiation\n * _Responding to change_ over following a plan\n\n That is, while there is value in the items on\n the right, we value the items on the left more.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Agile is not absence of process" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n> The Agile movement is not anti-methodology, in fact, many of us want to restore credibility to the word methodology.\n> We want to restore a balance. We embrace modeling, but not in order to file some diagram in a dusty corporate repository.\n> We embrace documentation, but not hundreds of pages of never-maintained and rarely-used tomes. We plan, but recognize the\n> limits of planning in a turbulent environment. Those who would brand proponents of XP or SCRUM or any of the other\n> Agile Methodologies as \"hackers\" are ignorant of both the methodologies and the original definition of the term hacker\n\n-- Jim Highsmith.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Elements of an Agile Process" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Continuous delivery\n* Self-organising teams\n* Iterative development\n* Ongoing design\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Ongoing Design" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAgile development doesn't eschew design. Design documents should still be written, but treated as living documents,\nupdated as more insight is gained into the task, as work is done, and as requirements change.\n\nUse of a Wiki or version control repository to store design documents thus works much better than using Word documents!\n\nTest-driven design and refactoring are essential techniques to ensure that lack of \"Big Design Up Front\" doesn't produce\nbadly constructed spaghetti software which doesn't meet requirements. By continously scouring our code for smells, and\nstopping to refactor, we evolve towards a well-structured design with weakly interacting units. By starting with tests\nwhich describe how our code should behave, we create executable specifications, giving us confidence that the code does\nwhat it is supposed to.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Iterative Development" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAgile development maintains a backlog of features to be completed and bugs to be fixed. In each iteration, we start with a meeting where\nwe decide which backlog tasks will be attempted during the development cycle, estimating how long each will take,\nand selecting an achievable set of goals for the \"sprint\". At the end of each cycle, we review the goals completed and missed,\nand consider what went well, what went badly, and what could be improved.\n\nWe try not to add work to a cycle mid-sprint. New tasks that emerge are added to the backlog, and considered in the next planning meeting.\nThis reduces stress and distraction.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Continuous Delivery" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn agile development, we try to get as quickly as possible to code that can be *demonstrated* to clients. A regular demo of progress\nto clients at the end of each development iteration says so much more than sharing a design document. \"Release early, release often\"\nis a common slogan. Most bugs are found by people *using* code -- so exposing code to users as early as possible will help find bugs quickly.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Self-organising teams" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nCode is created by people. People work best when they feel ownership and pride in their work. Division of responsiblities into designers\nand programmers results in a [\"Code Monkey\"](http://open.spotify.com/track/1rIFZk9tTUtHP3vULR5wXe) role, where the craftspersonship and \nsense of responsibility for code quality is lost. Agile approaches encourage programmers, designers, clients, and businesspeople to see\nthemselves as one team, working together, with fluid roles. Programmers grab issues from the backlog according to interest, aptitude,\nand community spirit.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Agile in Research" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nAgile approaches, where we try to turn the instincts and practices which emerge naturally when smart programmers get together into\nwell-formulated best practices, have emerged as antidotes to both the chaotic free-form typing in of code, and the rigid\npaperwork-driven approaches of Waterfall.\n\nIf these approaches have turned out to be better even in industrial contexts, where requirements for code can be well understood,\nthey are even more appropriate in a research context, where we are working in poorly understood fields with even less well captured\nrequirements.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Conclusion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n* Don't ignore design\n* See if there's a known design pattern that will help\n* Do try to think about how your code will work before you start typing\n* Do use design tools like UML to think about your design without coding straight away\n* Do try to write down some user stories\n* Do maintain design documents.\n\nBUT\n\n* Do change your design as you work, updating the documents if you have them\n* Don't go dark -- never do more than a couple of weeks programming without showing what you've done to colleagues\n* Don't get isolated from the reasons for your code's existence, stay involved in the research, don't be a Code Monkey.\n* Do keep a list of all the things your code needs, estimate and prioritise tasks carefully.\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Exercises" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to classes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nComplete the exercise on Boids from last week, as far as creating a class for a Boid, if you haven't already.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to Inheritance and Polymorphism" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn the Eagle branch in my Boids repository you will find an extension of Boids to support multiple kinds of Bird.\nYou will see that this suffers from the use of an `if (type)` statement which would be\nbetter implemented with inheritance and polymorphism.\n\nTo access the Eagle branch:\n" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "cd \ngit checkout eagle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to Patterns: Builder" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThe way in which we construct our Boids model's bird content, specifying each of the\nmodel parameters as a constructor, and add birds using `initialise_random`\nand `initialise_from_data` is rather clunky.\n\nCreate a `ModelBuilder` class with methods to define model parameters and to\nadd a random boid and boid or boids from data, using the Builder pattern.\n\nYou could even create two subclasses of `ModelBuilder` to build either random boids\nor boids from a dataset.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Refactoring to Patterns: Model/View" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nYou can apply Model/View to create a separate class to visualise boid models using Matplotlib,\nand even perhaps write another viewer to visualise the boids in a different way: perhaps as graphs over time\nof dispersal, distance from flock to eagle and average velocity.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Using UML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nYou should also use [YUML](http://yuml.me) or another UML tool to visualise your class structure.\n\nYou don't have to do all these things to pass the assignment: any minimally object-oriented implementation of the boids\nwill pass." - ] - } - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this session, we will finally discuss the thing most people think of when they refer to \"Software Engineering\": the deliberate *design* of software.\n", + "We will discuss processes and methodologies for planned development of large-scale software projects: *Software Architecture*." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Object-Oriented Design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The software engineering community has, in large part, focused on an object-oriented approach to the design and development of large scale software systems.\n", + "The basic concepts of object orientation are necessary to follow much of the software engineering conversation.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design processes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In addition to object-oriented architecture, software engineers have focused on the development of processes for robust, reliable software development. \n", + "These codified ways of working hope to enable organisations to repeatably and reliably complete complex software projects in a way that minimises both development \n", + "and maintainance costs, and meets user requirements.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design and research" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Software engineering theory has largely been developed in the context of commercial software companies.\n", + "\n", + "The extent to which the practices and processes developed for commercial software are applicable in a research context is itself an active area of research.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More on Objects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Object Based Programming Recap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We saw last lesson how to \n", + "\n", + "* create our own classes\n", + "* store data with member variables\n", + "* add functionality with member functions\n", + "* initialise objects with constructors\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Class design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The concepts we have introduced are common between different object oriented languages.\n", + "Thus, when we design our program using these concepts, we can think at an architectural level,\n", + "independent of language syntax.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Particle(object):\n", + " def __init__(self, position, velocity):\n", + " self.position=position\n", + " self.velocity=velocity\n", + " def move(self, delta_t):\n", + " self.position+= self.velocity*delta_t" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` cpp\n", + "class Particle {\n", + " std::vector position;\n", + " std::vector velocity;\n", + " Particle(std::vector position, std::vector velocity);\n", + " void move(double delta_t);\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` fortran\n", + "type particle\n", + " real :: position\n", + " real :: velocity\n", + " contains\n", + " procedure :: init\n", + " procedure :: move\n", + "end type particle\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "UML is a conventional diagrammatic notation used to describe \"class structures\" and other higher level\n", + "aspects of software design.\n", + "\n", + "Computer scientists get worked up about formal correctness of UML diagrams and learning the conventions precisely.\n", + "Working programmers can still benefit from using UML to describe their designs.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### YUML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see a YUML model for a Particle class with `position` and `velocity` data and a `move()` method using\n", + "the [YUML](http://yuml.me/) online UML drawing tool.\n", + "\n", + " http://yuml.me/diagram/boring/class/[Particle|position;velocity|move%28%29" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's how we can use Python code to get an image back from YUML:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import requests\n", + "import IPython" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def yuml(model):\n", + " result=requests.get(\"http://yuml.me/diagram/boring/class/\"+model)\n", + " return IPython.core.display.Image(result.content)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[Particle|position;velocity|move()]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Information Hiding" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Sometimes, our design for a program would be broken if users start messing around with variables we don't want them to change.\n", + "\n", + "Robust class design requires consideration of which subroutines are intended for users to use, and which are internal.\n", + "Languages provide features to implement this: access control. \n", + "\n", + "In python, we use leading underscores to control whether member variables and methods can be accessed from outside the class.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class MyClass(object):\n", + " def __private_method(self): pass\n", + " def _private_method(self): pass\n", + " def public_method(self): pass\n", + "\n", + "MyClass()._private_method() # Works, but forbidden by convention\n", + "MyClass().public_method() # OK" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'MyClass' object has no attribute '__private_method'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mMyClass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__private_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Generates error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'MyClass' object has no attribute '__private_method'" + ] + } + ], + "source": [ + "MyClass().__private_method() # Generates error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Property accessors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Python provides a mechanism to make functions appear to be variables. This can be used if you want to\n", + "change the way a class is implemented without changing the interface:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self):\n", + " self.name = \"James Hetherington\"\n", + "\n", + "assert(Person().name == \"James Hetherington\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "becomes:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self):\n", + " self._first = \"James\"\n", + " self._second = \"Hetherington\"\n", + "\n", + " @property\n", + " def name(self):\n", + " return self._first + \" \" + self._second\n", + "\n", + "assert(Person().name == \"James Hetherington\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Note that the code behaves the same way to the outside user.\n", + "The implementation detail is hidden by private variables.\n", + "In languages without this feature, such as C++, it is best to always\n", + "make data private, and always\n", + "access data through functions:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self):\n", + " self._name = \"James Hetherington\"\n", + " def name(self):\n", + " return self._name\n", + "\n", + "assert(Person().name() == \"James Hetherington\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "But in Python this is unnecessary.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Class Members" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "*Class*, or *static* members, belong to the class as a whole, and are shared between instances.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class Counted(object):\n", + " number_created=0\n", + " def __init__(self):\n", + " Counted.number_created+=1\n", + " @classmethod\n", + " def howMany(cls):\n", + " return cls.number_created\n", + "\n", + "Counted.howMany() # 0\n", + "x=Counted()\n", + "Counted.howMany() # 1\n", + "z=[Counted() for x in range(5)]\n", + "Counted.howMany() # 6 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Object-based vs Object-Oriented" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "So far we have seen only object-based programming, not object-oriented programming.\n", + "\n", + "Using Objects doesn't mean your code is object-oriented.\n", + "\n", + "To understand object-oriented programming, we need to introduce **polymorphism** and **inheritance**.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inheritance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Inheritance allows related classes to share code\n", + "* Inheritance allows a program to reflect the *ontology* of kinds of thing in a program.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ontology and inheritance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* A bird is a kind of animal\n", + "* An eagle is a kind of bird\n", + "* A starling is also a kind of bird\n", + "\n", + "* All animals can be born and die\n", + "* Only birds can fly (Ish.)\n", + "* Only eagles hunt\n", + "* Only starlings flock\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inheritance in python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I exist\n", + "I'm gonna eatcha!\n" + ] + } + ], + "source": [ + "class Animal(object):\n", + " def beBorn(self): print \"I exist\"\n", + " def die(self): print \"Argh!\"\n", + "\n", + "class Bird(Animal):\n", + " def fly(self): print \"Whee!\"\n", + "\n", + "class Eagle(Bird):\n", + " def hunt(self): print \"I'm gonna eatcha!\"\n", + "\n", + "Eagle().beBorn()\n", + "Eagle().hunt()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inheritance terminology" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* A *derived class* _derives_ from a *base class*\n", + "* A *subclass* _inherits_ from a *superclass*\n", + "\n", + "(These are different terms for the same thing.)\n", + "\n", + "* Eagle is a subclass of the Animal superclass.\n", + "* Animal is the base class of the Eagle derived class\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inheritance and constructors" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Animal(object):\n", + " def __init__(self, age):\n", + " self.age=age\n", + "\n", + "class Person(Animal):\n", + " def __init__(self, age, name):\n", + " super(Person, self).__init__(age)\n", + " self.name=name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inheritance UML diagrams" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "UML shows inheritance with an open triangular arrow pointing from subclass to superclass." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nnL4WLwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[Animal]^-[Bird],[Bird]^-[Eagle],[Bird]^-[Starling]%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aggregation vs Inheritance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "If one object *has* or *owns* one or more objects, this is *not* inheritance.\n", + "\n", + "For example, in my solution to the Boids task from last week, the overal Model owned several Boids,\n", + "and each Boid owned two 2-vectors, one for position and one for velocity.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Aggregation in UML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Boids situation can be represented thus:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[Model]<>-*>[Boid],[Boid]position++->[Vector],[Boid]velocity++->[Vector]%\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The open diamond indicates **Aggregation**, the closed diamond **composition**.\n", + "(A given boid might belong to multiple models, a given position vector is forever part of the corresponding Boid.)\n", + "\n", + "The asterisk represents cardinality, a model may contain multiple Boids." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to inheritance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Smell: Repeated code between two classes which are both ontologically subtypes of something\n", + "\n", + "Before:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self, age, job): \n", + " self.age = age\n", + " self.job = job\n", + " def birthday(self): \n", + " self.age += 1\n", + "\n", + "class Pet(object):\n", + " def __init__(self, age, owner): \n", + " self.age = age\n", + " self.owner = owner\n", + " def birthday(self): \n", + " self.age += 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "After:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Animal(object):\n", + " def __init__(self, age): \n", + " self.age = age\n", + " def birthday(self): \n", + " self.age += 1\n", + "\n", + "class Person(Animal):\n", + " def __init__(self, age, job):\n", + " self.job = job\n", + " super(Person, self).__init__(age)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Polymorphism" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Polymorphism" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bark\n", + "Bark\n", + "Miaow\n", + "Oink\n", + "Moo\n", + "Miaow\n" + ] + } + ], + "source": [ + "class Dog(object):\n", + " def noise(self):\n", + " return \"Bark\"\n", + "\n", + "class Cat(object):\n", + " def noise(self):\n", + " return \"Miaow\"\n", + "\n", + "class Pig(object):\n", + " def noise(self): return \"Oink\"\n", + "\n", + "class Cow(object):\n", + " def noise(self): return \"Moo\"\n", + "\n", + "animals=[Dog(), Dog(), Cat(), Pig(), Cow(), Cat()]\n", + "for animal in animals:\n", + " print animal.noise()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This will print \"Bark Bark Miaow Oink Moo Miaow\"\n", + "\n", + "If two classes support the same method, but it does different things for the two classes, \n", + "then if an object is of an unknown class, calling the method will invoke the version for\n", + "whatever class the instance is an instance of.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Polymorphism and Inheritance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Often, polymorphism uses multiple derived classes with a common base class.\n", + "However, duck typing in Python means that all that is required is that the \n", + "types support a common **Concept** (Such as iterable, or container, or, in this case, the\n", + "Noisy concept.)\n", + "\n", + "A common base class is used where there is a likely **default** that you want several\n", + "of the derived classes to have.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bark\n", + "I don't make a noise.\n", + "Oink\n", + "Moo\n", + "I don't make a noise.\n" + ] + } + ], + "source": [ + "class Animal(object):\n", + " def noise(self): return \"I don't make a noise.\"\n", + "\n", + "class Dog(Animal):\n", + " def noise(self): return \"Bark\"\n", + "\n", + "class Worm(Animal):\n", + " pass\n", + "\n", + "class Poodle(Animal):\n", + " pass\n", + "\n", + "animals=[Dog(), Worm(), Pig(), Cow(), Poodle()]\n", + "for animal in animals:\n", + " print animal.noise()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Undefined Functions and Polymorphism" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In the above example, we put in a dummy noise for Animals that don't know what type they are.\n", + "\n", + "Instead, we can explicitly deliberately leave this undefined, and we get a crash if we access an undefined method.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Animal(object): pass\n", + "\n", + "class Worm(Animal): pass" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Worm' object has no attribute 'noise'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mWorm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnoise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Generates error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'Worm' object has no attribute 'noise'" + ] + } + ], + "source": [ + "Worm().noise() # Generates error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to Polymorphism" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Smell: a function uses a big set of `if` statements or a `case` statement to decide what to do:\n", + "\n", + "Before:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Animal(object):\n", + " def __init__(self,type): \n", + " self.type=type\n", + " \n", + " def noise(self): \n", + " if self.type==\"Dog\":\n", + " return \"Bark\"\n", + " elif self.type==\"Cat\":\n", + " return \"Miaow\"\n", + " elif self.type==\"Cow\":\n", + " return \"Moo\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "which is better replaced by the code above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interfaces and concepts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In C++, it is common to define classes which declare dummy methods, called \"virtual\" methods, which specify\n", + "the methods which derived classes must implement. Classes which define these methods, which cannot be instantiated\n", + "into actual objects, are called \"abstract base\" classes or \"interfaces\".\n", + "\n", + "Python's Duck Typing approach means explicitly declaring these is unnesssary: any class concept which implements\n", + "appropriately named methods will do. These as user-defined **concepts**, just as \"iterable\" or \"container\" are \n", + "built-in Python concepts. A class is said to \"implement and interface\" or \"satisfy a concept\".\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interfaces in UML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Interfaces implementation in UML is indicated thus:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[<>]^-.-[Dog]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Further UML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "UML is a much larger diagram language than the aspects we've shown here.\n", + "\n", + "* Message sequence charts show signals passing back and forth between objects ([Web Sequence Diagrams](https://www.websequencediagrams.com/))\n", + "\n", + "* Entity Relationship Diagrams can be used to show more general relationships between things in a system\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Class Complexity" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We've seen that using object orientation can produce quite complex class structures, with classes owning each other, instantiating each other,\n", + "and inheriting from each other.\n", + "\n", + "There are lots of different ways to design things, and decisions to make.\n", + "\n", + "> Should I inherit from this class, or own it as a member variable? (\"is a\" vs \"has a\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design Patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Programmers have noticed that there are certain ways of arranging classes that work better than others.\n", + "\n", + "These are called \"design patterns\".\n", + "\n", + "They were first collected on one of the [world's first Wikis](http://c2.com/cgi/wiki?WelcomeVisitors), \n", + "as the [Portland Pattern Repository](http://c2.com/cgi-bin/wiki?PatternIndex)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Reading a pattern" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "A description of a pattern in a book such as the [Gang Of Four](http://www.amazon.co.uk/Design-patterns-elements-reusable-object-oriented/dp/0201633612)\n", + "book usually includes:\n", + "\n", + "* Intent\n", + "* Motivation\n", + "* Applicability\n", + "* Structure\n", + "* Participants\n", + "* Collaborations\n", + "* Consequences\n", + "* Implementation\n", + "* Sample Code\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introducing Some Patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There are lots and lots of design patterns, and it's a great literature to get into to\n", + "read about design questions in programming and learn from other people's experience.\n", + "\n", + "We'll just show a few in this session:\n", + "\n", + "* Factory Method\n", + "* Builder\n", + "* Handle-Body\n", + "* Strategy\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Factory Pattern" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Factory Pattern" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Here's what the Gang of Four Book says about Factory Method:\n", + "\n", + "Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate.\n", + "Factory Method lets a class defer instantiation to subclasses.\n", + "\n", + "Applicability: Use the Factory method pattern when:\n", + "\n", + "* A class can't anticipate the class of objects it must create\n", + "* A class wants its subclasses to specify the objects it creates\n", + "\n", + "This is pretty hard to understand, so let's look at an example.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Factory UML" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAR0AAAGICAYAAAB8wTSLAAAABmJLR0QA/wD/AP+gvaeTAAAgAElE\nQVR4nO2deXgUVfq276S7kwgEEiIkZCVAEhQCCKJsIiAzo2zjyhYWURAZGJEgfAIOqCO4oCwug4qK\nQEIIKKL+HJlxCMuoQJAxEReEgJAQCPsSlpCl6/uj6TaddJJO0l3Vlbz3ddVFqHr7PU9Vn3r61Kmq\nc0AQBEEQBEEQBEEQBEEQBEEQKsFLawFCpYQAd2stQhBqySYgz/ofo4ZChKppC6wICQkp0FpIXUBR\nFK0l1DtOnDjhB/RFTEdfHDt2zK+ibc6eSHUlTsuyZZ+rHxcUFFRunZiOzvC0SlXTOC3Lln1WL+46\nN1xfSoASMR0d4MwXLCeSenFalu3pcRXEGgGTdbOYjk6RE6n2cVqW7elxLs5pur4ogNnbaQWC5iiK\nYltcHVdZrKfEbd26lUGDBhESEkKzZs249dZbefbZZ7l48aJb9vmWW27hyy+/1MWxqQwt68N1DIC3\ndRHT0QGeWqHVPEE++eQTHnzwQQYNGsSuXbvYv38/L774Irt27WLv3r0ety91zTxqGWc1HC/AS0xH\nh3hgpao0trb7UlxczPTp05k5cyYTJ04kMjKSgIAA+vfvzz//+U+6dOkCwE033cSrr77KwIEDadas\nGcnJyZw8eZJHHnmENm3a0KZNG2bNmkVRUZGtvKlTpxIdHU1QUBA333wzr732GgBTp04lKyuL0aNH\nExwczPjx41EUhezsbB566CEiIiJo3749ixYtwmw22/K1a9eORYsWMWTIEEJCQkhJSXHrsdFDHNfN\n5vqCmI4OqCvmUdO4vXv3kpeXR0JCgsM4Hx8fW65Vq1YxZMgQPvroI/r27cvw4cMJDAxk79697Nq1\ni507d/LOO+/YPj969Gi2b99Obm4un3/+Oe+//z7ffPMNS5YsoU2bNqxatYq8vDyWL19OSUkJQ4cO\nJS4ujn379vHRRx+RlJTEhx9+aKdp9erVDB48mPXr19O3b1+PNwV31gdHSEeyjqnqi69JrCfGnT59\nGpPJRGhoaJX5Xn31Vf7whz+gKAoZGRn8+uuvbNq0CZPJhJ+fHxMnTiQpKYnJkycDYDAYePnll9m1\naxfnz5/nzJkz/PLLL/To0aNc7v/9739kZ2czd+5cTCYTcXFxPPHEE6xcuZJx48bZ4l5++WX69+9f\nq312Z5wGZZdu6XiJ6egMTzQFd8cFBgZSVFTE2bNnCQwMrDLemjs7O5srV67QqVMn2/bi4mKbeeXm\n5vLHP/6RJ554go8//pjQ0FAeeOABSkpKHOY7duwYzZs3x2Qy2baHh4eTm5vr1P7UAfOodpwjxHR0\ngBYV2pNOkA4dOhAUFMSyZcuYPXu2XZyiKFy7dg0/v/IPbYeFheHv78/evXvx8ir/muF3331HixYt\nmDNnjl251rwGgwGz2WyLDw0N5cSJE1y7dg0fHx/AYmxhYWG12t/qxNaFOOnT0Sl6uq6vbdlGo5EX\nX3yRhQsXsmDBArKysjh16hSbN29m8ODBfP/99w7zdezYkaioKKZPn86xY8e4dOkS6enpJCUlAdCq\nVStOnjzJsWPHUBSFzZs32+UKDw8nLS2NK1eucPz4cTp16kRkZCTPPfccly9fZv/+/bz55puMGjXK\n4T6pcWz0ElcaMR2dUBfMozZxI0aMICUlhW3bttGzZ0/atm3LnDlz6NWrF+3bt3eYy2AwsG7dOq5e\nvUq/fv1o3bo1U6dOtd1tat++PdOnT6d379506dKFVatW0aJFC1vZ/+///T/S0tKIiori1VdfxWAw\nkJqaSlZWFjfddBMPPvggCQkJjBkzxmNPdq3rjSNkaAvPpg+w5dq1a5UGeXJTWss4Lcv29Di1yg4J\nCQEYCWwFrgHXpE9Hh3h6hdbrCaLHOC3Lro7G0ojp6ABPr1R16QSRfXZ/nJiOjpETSb04Lcv29Ljq\nxorp6Ag5kWofp2XZnh7nrpxlEdPRAc58wZ5eofVwgtSVOC3LdiZOTEeneHKlqk6clmV7epyWZbuz\nxSOmoxP0UKk8XaMcG8+IE9PRAXFxcVpLcEhtrusFC/XxGIrp6ADru0HVxdUVWo1ORk8q1x059bAv\nrix72rRp5dbJE8meTR9gy+XLl50K9sSmtDvjtCxb9tm5uPDwcJAnkusOnlCpXBGnZdmyz+rFWRHT\n0RFygnhenJZle3pcRchb5jpAUdz7hvC5c+eIi4vj/fffdypnSUkJ9957L4899pjTZXt6XNlYZ+Nq\nW3aXLl3YvHmzS/ItWbKExx9/3Lb93LlzdOrUiVWrVgGW72348OH89a9/rdZ+lI11Nq6iWDEdneLK\nSvDMM8/Qo0cPHnnkEacqvre3Nx988AFfffUV//nPfyott3///gQEBBAYGGi3WN+cr80JfPvtt/Ov\nf/2r0tjKcKbsu+++mxYtWnDkyBG7uBkzZtC8eXO+/PJLp/P17NmTr776SjVze/755+nWrRtjxowB\nLEN9LFu2jLS0NNLS0txadmWI6egEd7UAcnJy+Oijj3j++eerlS8wMJCZM2eycOHCKsudN28eWVlZ\nZGVlceDAAQ4cOGA3mLoz++JsnDtaR2FhYbz++uu27SdPnmTTpk0EBAR4bKssNzeXTz/9lDlz5tjF\nBgQEMG3aNJYsWeJWjdLS0TnurNCffPIJPXv2tN5lsG1ftmwZPXv2tMuRmppKt27dbHHDhg2zDVZe\nGQ0aNKBp06Y0bdqUoKAggoKCbNumT59OXFwcISEhdOjQgcWLF9s0nDp1iokTJxIXF0dMTAyjRo3i\nzJkzKIpCYmIiBw8eZNy4cURGRjJp0iTAMnzoiBEjaN26NbfccgtLliyhpKQERVHo3LkzS5cu5YEH\nHiAyMpL+/fvTp08fu+OSmprKHXfcYad//PjxbNy40TbC4LvvvktCQgI33HCDXdypU6eYNGkSHTp0\noEOHDsybN4/CwkIAZs6cyaFDhxg/fjytWrViypQpts9lZmYyZMgQWrZsyaBBgzhy5AgAOTk5jB49\nmri4OG677TbeeOMN274AnDhxgnHjxhEbG8utt97KF198YftuPv/8c7p16+ZwKNUHH3yQjIwMcnJy\nNDFMMR2d4epK8PXXX/OnP/2pXNywYcM4cOCA3UR2ycnJjB071hYXEBDA7bffzvbt2yste968ebRs\n2dK2fPTRR7a4kSNHsnnzZg4fPswnn3zChx9+yI4dOzCbzSQkJNCwYUN27tzJf//7X7p06UJ+fj4A\nr732Gq1bt2bFihVkZ2ezbNkyiouLSUhIIDY2lszMTNauXUtKSgqrV6+2aVuzZg0DBgwgOTmZN998\nkwMHDpCZmWnbnpSUxOjRo236AJo3b87999/PW2+9RX5+PuvXr+exxx6z22ez2czYsWMJCAggPT2d\nrVu3snv3blasWAHAK6+8QqtWrVi+fDkHDx7kjTfesOXfvHkz06dPZ9u2bTRq1IiFCxdSXFzMmDFj\niImJYc+ePSQlJbF27VqSk5NtZU6YMAE/Pz82b97MJ598QkxMjG3bjh077GakKP19NG7cmK5du/LN\nN9+Uqw+ubG1VhNy90gHOfKnV+eJLxx49epTo6OhyMYGBgQwaNIikpCReeukljh49Snp6erk5nlq2\nbMnRo0crLf/JJ59k+PDhtpigoCDb30ajkVdffZX09HTOnz/P2bNn2bdvHzfccAM///wzn332Gb6+\nvrY8jvbFmsv66z1nzhxMJhOxsbFMnjyZpKQk25CiCxYsoF+/frbPDxo0iOTkZDp27MjBgwfJyMhg\n5cqV5fZnypQp9O3bF6PRyL333ktAQIBd+T/++CNZWVls3LgRk8mEr68vjzzyCGvXrmXChAkVHhuA\nxMREW+vqgQce4N133yUzM5OjR4/y9NNPYzKZiImJYdKkSSQnJzNq1CgyMjLIzMwkKSkJf39/AGJj\nY9m3bx9gubyKioqq8HuJiori2LFjtn1wBlfFSUtHx7iiz+PixYs0a9bMYdyYMWNYv349hYWFpKSk\nMGjQIAIDA+1ibrzxRi5cuFBhuWAxsIiICCIjI4mMjKRhw4aAZUqXAQMG0Lx5c1JTU/nhhx/o1asX\nxcXF5OTkEBwcbDMcZ/b5+PHjNGvWDJPJZNcfk5ubW+GxSUhIYMOGDVy9epXk5GQGDRpkM5TShIeH\nc/fdd/P+++/z+OOPl9tune6me/fu3Hrrrdx66638/e9/p6IHO0vvQ+l9adSoEUVFRRw/fpwbb7wR\no9Foiw0NDbUZxbFjxwgJCbEZTuk8iqKQn59vdxlbtuygoCDbHPAV4Y5LK5CWjq5wxy9S48aNOX36\ntMPP3HHHHTRp0oQvv/ySlJQUli5dWi7u1KlTdv1BzqIoCt999x0hISE8/fTT5baHhYVx4sQJCgoK\nyhmPldJTxCiKQosWLTh58qTdFDE5OTl2/RplT4yePXvStGlTPvnkE9atW8fy5cvL7Yv1b2uLJDg4\nuNy+hIaG0qhRI9LT0x1Od6Mov09p48xxatGiBadOnaKoqMg2z9bRo0cJDQ0FIDg4mAsXLmA2mx2W\n5+/vz9mzZ8vti5XTp0+X6+9Rq8UjLR0d4M7OvrCwMFvHpaPyEhISmDt3Lt7e3vTq1atczsOHDxMe\nHl6j1larVq04deoUeXl5AKSlpZGRkYGiWGZqiI6OZvbs2Zw9e5bTp0+zbNkyDh8+bMsXFhbGli1b\nuHLlCnl5eXTs2JGIiAjmz5/P5cuXOXDgAMuWLWPEiBEV6vPy8mLkyJHMmzePJk2acPvtt1cYGx0d\nzUMPPeTwWMfHxxMZGcmsWbPIy8vj0qVLfPfdd6xdu9buWG/bto2rV69y4sSJSo9Nhw4diIiIYMGC\nBVy+fJmsrCzeeecd22Vq+/btadCgAevXr7d9D9a+NasJZmdnV7gv2dnZtkkH3Vm/HCGmo0NcWQl6\n9erFl19+WWHcyJEjyc3NtetctS7nzp0jPT293N2e0nHWvx3Rrl07pk6dSt++fbn99ttJSkqytSKM\nRiNJSUmcPXuW7t2706NHD3bu3Im/v78t34wZM9i6dSsxMTEsXrwYg8FAUlISWVlZdOzYkeHDhzN8\n+PAK56WyLsOGDePSpUsO48rGVoTBYGDlypUUFBRwzz33EB8fz8yZM+0+k5iYyNatW2nbti1Lliyp\nMt+KFSs4ePAgXbp0YdSoUQwbNoyRI0cC4OPjw/Lly1m+fDmdOnUiMTGRxo0b23J2796dr776yuF+\nnD9/nt27d5e7O+lof6vzY+Is8sKnZ9MH2HL+/HmnP1Ddpu/Ro0e5/fbb2blzJxEREdXKt3z5cjZs\n2GAzLXfoc1VcZbG//fYbffv2JSMjw/bsjRYaXRmXm5tL3759SUtLK3cZtWLFCj777DM++eQTt2u8\nfpPC7oVPaenonNq2esLDw7n//vuZN29etfJduHCBhQsXkpiYqGrTvGyss3GVxX7wwQcMGDCAJk2a\nqLov7tzn0NBQhgwZwvz58+225+fn8/rrr/PEE09oti9iOjrDHRX/ueeeY+fOnXzwwQdO5SsuLmbC\nhAn069fP7lmQ6pRbNtbZOFefIFeuXCE1NbXKSzBPNczK4ubMmcPu3btJSkqyfW+TJ0+md+/e9OnT\nx23lVoVcXnk2fYAt586dqzLQEy9b6mqclmV7elzZ2FatWoGMp1M3kBOk9nFalu3pcS7OqZRexHR0\nhKdX1DpygtSpOC3LLhVn9wExHR3gzPW/K/JoHadF2adPn+bGG2+UY+PeOGnp6I01a9bU6HPV7eBz\nZT6tyq6uxnfeeYeJEyc6fKq3uvmqU64r4zy9XMBcalGkI9mz6WY0Gj82Go3WHwevMv8KtcBsNhsL\nCwsDfHx8znt7exdrracuUlxcXFJcXPwIsBMoBIqk8uoDP8AE+GC5JDZgedxBTKgWeHt7zzObzY8A\n7wOORzETqotS6l8zUAIUA0XWf6Wy6gMfLEZjvL54I6ZTW7z9/Pz2tmrVKujQoUOnCwoKOmA5SYTa\nUdZ0ShtPCVAilVUfGEotVrMp/WCnfI/V505vb+9P9u7d6x0fH282m80PAF/ze4dnibbydEvpDiEz\n9i0eM2CWjmR9YD0JFCwGY12siOlUDy+DwfBAnz59Sm6++WbvPn36lGzbtu2BkpKS7fz+61yksUa9\nopT527pYDUjuXukEMxZjsZoOiNHUhhu8vb3vHzNmjAlgzJgxpv/+97/3lZSUzACuYjF4MZ3aU/pS\nSymzTtARXrLUernPx8en+OLFi4qiKMrFixcVHx+fYiyP6zcBGnqAxrq2CEL9xcfH56P777+/SCnF\nfffdV2Q0Gj8FmgL+VeUQBEFwlkYGg+Hahg0bSnuO8vHHHysGg+Ea0BIoP0iyIAhCDRnZsGHDwoKC\nAjvTKSgoUBo2bFgETMLS2hHchIynI9Qr/Pz8xg4bNsxQdrB3X19fhg4d6u3n5zcUOS8EQXARQd7e\n3sX/+c9/FEd89dVXyvXXIWK1FlqXkV5loT7xKPBejx49CgwGQ+nHDwAoLi5mx44dfsBU4HVHCYTa\nI6Yj1CdGAB35/enuJkACkApc4vfnSX4AlmukURCEOogP0ABoj8VkOgPNgWZYOpEbayet7iNPJAv1\nEeu7QNb3q4qwDLtQ+j0hwU2I6Qj1keLry9Xr/88HnJ9cTKgVcmtQEARVEdMRBEFVxHQEQVAVMR1B\nEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHTEQRBVcR0BEFQFTEdQRBURUxHEARVEdMR\nBEFVxHQEQVAVMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHTEQRBVcR0BEFQFTEd\nQRBURUxHEARVEdMRBEFVxHQEQVAVMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHT\nEQRBVcR0BEFQFTEdQRBURUxHEARVEdMRBEFVxHQEQVAVMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUx\nHUEQVEVMRxAEVRHTEQRBVcR0BEFQFTEdQRBURUxHEARVEdMRBEFVxHQEQVAVMR1BEFRFTEcQBFUR\n0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHTEQRBVcR0BEFQFTEdQRBURUxHEARVEdMRBEFVxHQEQVAV\nMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHTEQRBVcR0BEFQFTEdQRBURUxHEARV\nEdMRBEFVxHQEQVAVMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAEVRHTEQRBVcR0BEFQ\nFTEdQRBURUxHEARVEdMRBEFVxHQEQVAVMR1BEFRFTEcQBFUR0xEEQVXEdARBUBUxHUEQVEVMRxAE\nVRHTEQRBVcR0BEFQFTEdQRBURUxHEARVKWc63t7eFwFFFrcuT1bynQhCncZYdoXBYDA8++yz9OrV\ny7ZOURSnEzqKdfbzzn5Wq3yuyDlp0qTC3Nxcp8sThLpGOdMBaN++PX369AFqd5Kpsc7T9FS1rkGD\nBiXlAgShHuHQdEAMo7J1tfy8F+ALNABKri/m64sg1HkqNJ2yeNJJXwdMzQiYrKuvL17X/60NvYDx\ntcxRn7gBOA68DFzVWIueeA/4uqYfrtJ0xDCqt87JWKvpWA3HVa2cNn5+fqOGDRtmqCyoOn1YtcFV\n5aigd6hK5dhwRVla6N24cWNJQUHBVtxhOmIYblvnheWuoeH6v16lllrXooCAgKIVK1aUM52qKqgz\nFdiTYjxJizMxnqTFmZiKtm/ZsqWooKCgyvyV4dB0PMkI6pKpKYpiXe9davEqF+gCPKkCOxMjWmoe\no6YWV6D65ZU7curU6Lwc/O2S1k5llUdvlVz0epwWL8CPWtwIqdblVUXrPX2dJ+rB/rLK+n+3/NTo\nvJJ7dIwnaXEmxkXl1OpGiMPXIKq4NHDbusrKrs06T9KtBlaNFWktG+NMHkds2bKFu+++mxtvvJHA\nwEA6d+7M3LlzuXDhgupaqorp1KkTX375ZbW0KIrCtm3bGDJkCKGhoQQHB3P77bfz3HPPceHCBZfr\n7dq1K5s2bapQizN6XaWlopjrWE3HSA26CKp890pNw3C3OXiabrVxZQX++OOPue+++xgyZAjfffcd\nBw8e5OWXX2bXrl38+OOPbjeR6uq1/l2dcjZu3MjQoUMZOHAgO3bsYN++fcyfP5/du3fz008/uUVv\nWc3V0evqmEr0Gvj9RkjpmyFOUanpiGG4T7cauOtXsLi4mGnTpvH0008zadIkoqKiCAgIoH///mza\ntIkuXboAcOTIEe677z5CQkKIi4tj4cKFlJSU2PLExcUxdepU+vbtS3R0NHfeeSeHDh1CURROnDjB\nuHHjiIyMJDw8nKFDh3LmzBmbjrZt2/Laa68xcOBAmjVrxpo1azh58iTjxo2jdevWtG7dmqeffpqi\noiKmTp1KVlYWo0ePJjg4mPHjx9vKeOSRR4iJiSEmJobZs2dTVFQEQFFRETNmzOCpp57iscceIzIy\nkoCAAO666y4+//xzOnfuTLt27Vi0aBGDBw8mODiYlJQUTp48yfjx44mLiyMuLo7Zs2dTWFho2+fE\nxERiYmJo3rw57du3Z9GiRSiKwrRp08jKymLs2LGEhYUxceJEALKzsxk2bBgtW7akY8eOLF68GLP5\n9+6Tjh07smTJEu69917CwsJYu3atS0y6ijprvQNboxshFZqOp5mDo3V6MDUtcPev4N69e8nLy2PU\nqFEOY3x9fSkpKeH+++8nLi6OAwcOsHHjRlatWsUHH3xgF3v+/HnWr1/PoUOHiI2N5YUXXsBsNvPQ\nQw/RsGFDfvjhB3bv3k3Xrl3tLtsAVq1axZAhQ/j444/p06cPw4YNIzAwkB9//JH09HR27drFu+++\ny9KlS2nTpg2rV68mLy+P5cuXoygKw4cPJzAwkL1797Jr1y527tzJu+++i6Io/PTTT+Tl5TFy5EiH\nx8XHxweA1atXM3jwYNavX0+fPn0YOXIkgYGBZGRk8O2335Kens57771n+3xCQgJbtmwhOzubjRs3\nsmLFCnbs2MGiRYto06YNK1euJDc3l7fffpvi4mKGDx9ObGwsP/74I+vWrSM5OZlVq1bZtAAkJSUx\naNAg1q5da3t9yVXfddntiqI4atW49vLKWmht1kkrSF8tnqpiTp06hclkIjQ0tMKYPXv2kJ2dzfPP\nP0+jRo1o27YtTz75JCtXrrQrIyEhgaCgIAD69+/Pzz//TEZGBj/99BMLFy6kSZMmhISE8NRTTxEd\nHW1X1sKFC3nssce44447OHHiBL/++isvvfQSfn5+NG3alMcee8zWj1OWzMxM9u/fz4IFC/D19SUw\nMJCJEyfa+lROnz5t28fKjssrr7zC+PHj6dWrFydOnGD//v3Mnz/fpmHChAls2rTJ9nmDwcDChQvp\n06cPgwYN4sSJE+zbt6/cdwCQkZFBTk4Of/vb32jYsCGxsbFMmTKFpKQkOy0vvfQSjz76KL169SI4\nONil33UlddbRjRCnzKfSW+Zq/fKr1TJy97rqxroTZ8qsaUzTpk0pKiri7NmzNG3a1GFMbm4uzZs3\nx2Qy2bZHRERw9OjRCsvx8/OjsLCQ7OxsQkJC8PPzc1pvdnY2V65coWPHjrZ1xcXF5YzRmuPIkSNc\nuXKFTp062baVlJTYTCYwMNC2j4GBgQ71ltVk1dC5c2c7DWFhYQAcO3aMe+65hylTpvDRRx/RokUL\nHnroIYqKihzu27Fjx2jWrBlGo9G2PTw8nLKjFDjzg+aq+uAKKnw40NEvdUWxelunddnupqqmdE0/\na6VDhw4EBQXx1ltv8cwzz5T7/LVr1wgNDeXEiRNcu3bNdimSnZ1NeHh4ufiyZYaHh5OXl0dBQQG+\nvr5O6Q0NDcXf358ff/wRLy+vcjEGg4GSkt9f8A8PD7eLL6shPj6eoKAg3n77bWbNmmVXlnUfyxIW\nFoa/vz+ZmZnlNAB89913tGjRwpavLAaDAbPZbCunRYsWnDx5ksLCQtsxzMnJISwszCUmopUR1ery\nSi6baq/b3bioKW0XYzQaefnll3nllVeYP38+WVlZnD59mq+++ooBAwawZ88eOnfuTGRkJHPnzuXS\npUv8+uuvLF26lDFjxlRZVocOHWjVqhUzZszgzJkznDp1ijfeeIPffvvNpqX0v2C5Jd6yZUsSExM5\nduwY+fn57Nq1i9WrVwMWk0lLS+Pq1ascP36cjh07EhUVRWJiIrm5uVy6dIn09HSSkpIAMBqNzJ8/\nn9dee40XX3zRto9paWn8+c9/JiMjo9yx6dChA1FRUTz11FMcP36cS5cusXv3btvlUHR0NCdPniQv\nLw+AtLQ0Wx5FUew05uXl0alTJyIjI/n73//O5cuX2b9/P2+99RYJCQmqfdfuoMq7V1qagyv1aL0v\nahqOqypWZTEJCQmkpqaydetWunfvTps2bZg1axa9evUiPj4eg8HAxx9/zP79+2nTpg333nsvo0eP\nZty4cVWWYzQaWbduHWfOnOGWW26hc+fOfPvtt/j7+1eo19vbm9TUVK5evUrfvn1p3bo1Tz75pC1+\n1qxZbN68mYiICBYuXGiLLygo4K677qJNmza2eOsyfPhwkpKS2L59O71796Zdu3Y888wz9OzZk3bt\n2pXTYDAYWLt2rS1nTEwM06ZNs21v374906ZN484776Rr166sXr2akJAQ2/aZM2eyZcsWWrVqxaJF\ni/D29iY5OZkDBw7Qvn17hg0bxsiRIxk9erTDY1iT79HZGFfW4XJtQJPJdHnNmjUNBg8e7LBgT17n\naXocrevVq9e1rKyshcDbQAFwDSjk98fJa/PNPhwSErLsyJEjfmU3uKoprVaMJ2lxJqa+aOnQoUPh\nyZMnnwGSsdTdatdfp1741INhLFy4kF9++YX333/fbRrdodud1IVK7o4YT9LiTIwnaXEF1b68sq6v\n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\nTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[Product]^-[ConcreteProduct], [Creator| (v) FactoryMethod()]^-[ConcreteCreator| FactoryMethod()], [ConcreteCreator]-.->[ConcreteProduct]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Factory Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "An \"agent based model\" is one like the Boids model from last week:\n", + "agents act and interact under certain rules. Complex phenomena can be described by simple\n", + "agent behaviours.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class AgentModel(object):\n", + " def simulate(self):\n", + " for agent in agents:\n", + " for target in agents:\n", + " agent.interact(target)\n", + " agent.simulate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Agent model constructor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "This logic is common to many kinds of Agent based model, so we can imagine a common class\n", + "for agent based models: the constructor could parse a configuration specifying how many agents of each type to create,\n", + "their initial conditions and so on.\n", + "\n", + "However, this common constructor doesn't know what kind of agent to create; as a common base, it could be a model of boids,\n", + "or the agents could be remote agents on foreign servers, or they could even be physical hardware robots connected to the driving model\n", + "over Wifi!\n", + "\n", + "We need to defer the construction of the agents. We can do this with polymorphism: each derived class of the ABM can have an appropriate\n", + "method to create its agents:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class AgentModel(object):\n", + " def __init__(self, config):\n", + " self.agents=[]\n", + " for agent_config in config:\n", + " self.agents.append(self.create(**agent_config))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This is the *factory method* pattern: a common design solution to the need to defer the construction of daughter objects to a derived class.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Agent derived classes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class BirdModel(AgentModel):\n", + " def create(self, species):\n", + " return Boid(species)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Agents are the base product, boids or robots are a ConcreteProduct.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class WebAgentFactory(AgentModel):\n", + " def __init__(self, url):\n", + " self.url=url\n", + " connection=AmazonCompute.connect(url)\n", + " AgentModel.__init__(self)\n", + " def create(self, species):\n", + " return OnlineAnimal(species, connection)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "There is no need to define an explicit base interface for the \"Agent\" concept in Python: anything that responds to \"simulate\" and \"interact\" \n", + "methods will do: this is our Agent concept.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to Patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "I personally have got into a terrible tangle trying to make base classes which somehow\n", + "\"promote\" themselves into a derived class based on some code in the base class.\n", + "\n", + "This is an example of an \"Antipattern\": like a Smell, this is a recognised Wrong Way\n", + "of doing things.\n", + "\n", + "What I should have written was a Creator with a FactoryMethod.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from mock import Mock" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Builder Pattern" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Intent: Separate the steps for constructing a complex object from its final representation." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nY8aMoVOnTtx7770sWbKEgICAQumvXLnCrl276NixY7F5T5w4kZCQEBo0aMCAAQO49957+ec//wmY\n74dt2rSJZs2a8fLLL+Pv719s2V1dXfnyyy+ZN28ezZs3Z/To0Xh7e6vhxaVt2rQpI0eO5KGHHqJD\nhw4sX75cXUpZVNqxY8eydetWmjRpwuzZswFo27YtGzduLLJer169SkJCAu3atSu2XopKb29YaSnV\n8kZ/f//Pjhw5Umh5ozXlabKTk5OJiYlhx44dhbYDKyntggUL+Pbbb/nhhx/KlLezug+OCC8uLCUl\nhU6dOhEXF2eztV+4cCHr169n7dq1TvNtzZo1vP3226Va3qj5fTzrsJCQEJ5++mnefffdUtm+evUq\nM2fO5LXXXitz3o4uV3nD7U0bFBREz549+eCDDwqFX7t2jdmzZzNq1KgK9c0eNLmPV1zY5MmT2blz\nJwsXLrTLdm5uLkOHDqVLly7qUwV7fXN0ucobXta0EydOZNeuXcTGxuarl1deeYUHHnhA3d9Pa7FZ\no7vNt318fDh06JDd6U0mk9q5d7ZvWoTbk9bb25uEhIR8500mE4sXL66QeimLGDWdj6f3L1Sr8Ers\nm1LgKJIKv4+n40oT38qYtjizRQXccZtvOyptZmYm7u7ud6zYigi3fqmK41q8tLQ0k2UvuJIobye0\nvJXqbNv/+7//S+fOnalevbrufCtr+rLa3r17t+VjsW/zsaY09/GecXNz+8Rofr5kvcftnffWOCA7\nO7uWwWBIc3FxydDaFz2Qk5OTlJOT8wjme3fW9/BsLgIqq2hqYH6LixvmVtP6vQZ3ghBjgJXVqlXb\nnZ6e/jetndEIS6tmaeVybh3ZVp9t3jyGso9qc7ktsFzuMOGZTKbH6tatm5eamnoX5hckn9faJw2w\nHr1ai8/yEr1iL7dlFYkr5lcHWV4fZLkRXeVFB7i7urqemD9/vufEiRMzU1JSpgCfc/tLyNXWvQpD\nsfprLb4S39wI5WvxLJnZ/TaXKsKDBoOh2tNPP83Ro0fdZ8+e3TctLW0utys7u4T0VQWlwGfr0WyJ\nA4yyiqTgSzSqutgsGNzc3JY9+uijT65du9bl4MGDREdHA9wDHMP8g7wTBxvWt09KvJXiCAq+yaWq\nH94mkyljzZo1ioXIyMgM4J+Y+3qeOvBRy0NwEv2rV6+elZ6ergpv6tSpioeHRyJQC6hZkgFBKDXu\n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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "yuml(\"[Director|Construct()]<>->[Builder| (a) BuildPart()], [Builder]^-[ConcreteBuilder| BuildPart();GetResult() ],[ConcreteBuilder]-.->[Product]\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Builder example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Let's continue our Agent Based modelling example.\n", + "\n", + "There's a lot more to defining a model than just adding agents of different kinds: we need to define boundary conditions,\n", + "specify wind speed or light conditions.\n", + "\n", + "We could define all of this for an imagined advanced Model with a very very long constructor, with lots of optional arguments:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Model(object):\n", + " def __init__(self, xsize, ysize,\n", + " agent_count, wind_speed,\n", + " agent_sight_range, eagle_start_location):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Builder preferred to complex constructor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "However, long constructors easily become very complicated. Instead, it can be cleaner to define a Builder for models. A builder is like a \n", + "deferred factory: each step of the construction process is implemented as an individual method call, and the completed object\n", + "is returned when the model is ready.\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "Model=Mock() # Create a temporary mock so the example works!" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class ModelBuilder(object):\n", + " def start_model(self):\n", + " self.model=Model()\n", + " def set_bounds(self, xlim, ylim):\n", + " self.model.xlim=xlim\n", + " self.model.ylim=ylim\n", + " def add_agent(self, xpos, ypos):\n", + " pass # Implementation here\n", + " def finish(self):\n", + " return self.model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Inheritance of an Abstract Builder for multiple concrete builders could be used where there might be multiple ways to build models\n", + "with the same set of calls to the builder: for example a version of the model builder yielding models which can be executed\n", + "in parallel on a remote cluster.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using a builder" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "builder=ModelBuilder()\n", + "builder.start_model()\n", + "builder.set_bounds(500,500)\n", + "builder.add_agent(40,40)\n", + "builder.add_agent(400,100)\n", + "model=builder.finish()\n", + "model.simulate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Avoid staged construction without a builder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We could, of course, just add all the building methods to the model itself, rather than having the model be yielded from a separate builder.\n", + "\n", + "This is an antipattern that is often seen: a class whose `__init__` constructor alone is insufficient for it to be ready to use. A series of\n", + "methods must be called, in the right order, in order for it to be ready to use.\n", + "\n", + "This results in very fragile code: its hard to keep track of whether an object instance is \"ready\" or not. Use the builder pattern to keep deferred construction in control." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Builder Message Sequence" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note: Need to add message sequence chart here" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Strategy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from numpy import linspace,exp,log,sqrt, array\n", + "import math\n", + "from scipy.interpolate import UnivariateSpline\n", + "from scipy.signal import lombscargle\n", + "from scipy.integrate import cumtrapz\n", + "from numpy.fft import rfft,fft,fftfreq\n", + "import csv\n", + "from StringIO import StringIO\n", + "from datetime import datetime\n", + "import requests\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Strategy Pattern" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Define a family of algorithms, encapsulate each one, and make them interchangeable. \n", + "Strategy lets the algorithm vary independently from clients that use it.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy pattern example: sunspots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Consider the sequence of sunspot observations:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def load_sunspots():\n", + " url_base=\"http://www.quandl.com/api/v1/datasets/SIDC/SUNSPOTS_A.csv\"\n", + " x=requests.get(url_base,params={'trim_start':'1700-12-31',\n", + " 'trim_end':'2014-01-01',\n", + " 'sort_order':'asc'})\n", + " data=csv.reader(StringIO(x.text)) #Convert requests result to look \n", + " #like a file buffer before \n", + " # reading with CSV\n", + " data.next() # Skip header row\n", + " return [float(row[1]) for row in data]" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "spots=load_sunspots()\n", + "plt.plot(spots)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sunspot cycle has periodicity" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "spectrum=rfft(spots)\n", + "\n", + "### \"FFT figure\"\n", + "plt.figure()\n", + "plt.plot(abs(spectrum))\n", + "plt.savefig('fixed.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Years are not constant length" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There's a potential problem with this analysis however:\n", + "\n", + "* Years are not constant length\n", + "* Leap years exist\n", + "* But, the Fast Fourier Transform assumes evenly spaced intervals\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uneven time series" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The Fast Fourier Transform cannot be applied to uneven time series.\n", + "\n", + "We could:\n", + "\n", + "* Ignore this problem, and assume the effect is small\n", + "* Interpolate and resample to even times\n", + "* Use a method which is robust to unevenly sampled series, such as LSSA\n", + "\n", + "We also want to find the period of the strongest periodic signal in the data, there are\n", + "various different methods we could use for this also, such as integrating the fourier series\n", + "by quadrature to find the mean frequency, or choosing the largest single value. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uneven time series design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We could implement a base class for our common code between the different approaches,\n", + "and define derived classes for each different algorithmic approach. However, this has drawbacks:\n", + "\n", + "* The constructors for each derived class will need arguments for all the numerical method's control parameters,\n", + "such as the degree of spline for the interpolation method, the order of quadrature for integrators, and so on.\n", + "* Where we have multiple algorithmic choices to make (interpolator, periodogram, peak finder...) the number\n", + "of derived classes would explode: `class SunspotAnalyzerSplineFFTTrapeziumNearMode` is a bit unweildy.\n", + "* The algorithmic choices are not then available for other projects\n", + "* This design doesn't fit with a clean Ontology of \"kinds of things\": there's no Abstract Base for spectrogram generators...\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* We implement each algorithm for generating a spectrum as its own Strategy class.\n", + "* They all implement a common interface\n", + "* Arguments to strategy constructor specify parameters of algorithms, such as spline degree\n", + "* One strategy instance for each algorithm is passed to the constructor for the overall analysis\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "First, we'll define a helper class for our time series.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Series(object):\n", + " \"\"\"Enhance NumPy N-d array with some helper functions for clarity\"\"\"\n", + " def __init__(self, data):\n", + " self.data=array(data)\n", + " self.count=self.data.shape[0]\n", + " self.start=self.data[0,0]\n", + " self.end=self.data[-1,0]\n", + " self.range=self.end-self.start\n", + " self.step=self.range/self.count\n", + " self.times=self.data[:,0]\n", + " self.values=self.data[:,1]\n", + " self.plot_data=[self.times,self.values]\n", + " self.inverse_plot_data=[1.0/self.times[20:], self.values[20:]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Then, our class which contains the analysis code, *except* the numerical methods\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class AnalyseSunspotData(object):\n", + " def format_date(self, date):\n", + " date_format=\"%Y-%m-%d\"\n", + " return datetime.strptime(date,date_format)\n", + " \n", + " def load_data(self):\n", + " start_date_str='1700-12-31'\n", + " end_date_str='2014-01-01'\n", + " self.start_date=self.format_date(start_date_str)\n", + " end_date=self.format_date(end_date_str)\n", + " url_base=(\"http://www.quandl.com/api/v1/datasets/\"+\n", + " \"SIDC/SUNSPOTS_A.csv\")\n", + " x=requests.get(url_base,params={'trim_start':start_date_str,\n", + " 'trim_end':end_date_str,\n", + " 'sort_order':'asc'})\n", + " secs_per_year=(datetime(2014,1,1)-datetime(2013,1,1)\n", + " ).total_seconds()\n", + " data=csv.reader(StringIO(x.text)) #Convert requests\n", + " #result to look\n", + " #like a file buffer before\n", + " #reading with CSV\n", + " data.next() # Skip header row\n", + " self.series=Series([[\n", + " (self.format_date(row[0])-self.start_date\n", + " ).total_seconds()/secs_per_year\n", + " ,float(row[1])] for row in data])\n", + " \n", + " def __init__(self, frequency_strategy):\n", + " self.load_data()\n", + " self.frequency_strategy=frequency_strategy\n", + " \n", + " def frequency_data(self):\n", + " return self.frequency_strategy.transform(self.series)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Our existing simple fourier strategy\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class FourierNearestFrequencyStrategy(object):\n", + " def transform(self, series):\n", + " transformed=fft(series.values)[0:series.count/2]\n", + " frequencies=fftfreq(series.count, series.step)[0:series.count/2]\n", + " return Series(zip(frequencies, abs(transformed)/series.count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "A strategy based on interpolation to a spline\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class FourierSplineFrequencyStrategy(object):\n", + " def next_power_of_two(self, value):\n", + " \"Return the next power of 2 above value\"\n", + " return 2**(1+int(log(value)/log(2)))\n", + "\n", + " def transform(self, series):\n", + " spline=UnivariateSpline(series.times, series.values)\n", + " # Linspace will give us *evenly* spaced points in the series\n", + " fft_count= self.next_power_of_two(series.count)\n", + " points=linspace(series.start,series.end,fft_count)\n", + " regular_xs=[spline(point) for point in points]\n", + " transformed=fft(regular_xs)[0:fft_count/2]\n", + " frequencies=fftfreq(fft_count,\n", + " series.range/fft_count)[0:fft_count/2]\n", + " return Series(zip(frequencies, abs(transformed)/fft_count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "A strategy using the Lomb-Scargle Periodogram\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class LombFrequencyStrategy(object):\n", + " def transform(self,series):\n", + " frequencies=array(linspace(1.0/series.range,\n", + " 0.5/series.step,series.count))\n", + " result= lombscargle(series.times,\n", + " series.values,2.0*math.pi*frequencies)\n", + " return Series(zip(frequencies, sqrt(result/series.count)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Define our concrete solutions with particular strategies\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fourier_model=AnalyseSunspotData(FourierSplineFrequencyStrategy())\n", + "lomb_model=AnalyseSunspotData(LombFrequencyStrategy())\n", + "nearest_model=AnalyseSunspotData(FourierNearestFrequencyStrategy())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Strategy Pattern for Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Use these new tools to compare solutions\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "comparison=fourier_model.frequency_data().inverse_plot_data+['r']\n", + "comparison+=lomb_model.frequency_data().inverse_plot_data+['g']\n", + "comparison+=nearest_model.frequency_data().inverse_plot_data+['b']\n", + "\n", + "deviation=365*(fourier_model.series.times-linspace(\n", + " fourier_model.series.start,\n", + " fourier_model.series.end,\n", + " fourier_model.series.count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comparison of different algorithms for frequency spectrum of sunspots." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 16)" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(*comparison)\n", + "plt.xlim(0,16)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deviation of year length from average" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(deviation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model-View-Controller" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Separate graphics from science!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Whenever we are coding a simulation or model we want to:\n", + "\n", + "* Implement the maths of the model\n", + "* Visualise, plot, or print out what is going on.\n", + "\n", + "We often see scientific programs where the code which is used to display what is happening is mixed up with the\n", + "mathematics of the analysis. This is hard to understand.\n", + "\n", + "We can do better by separating the `Model` from the `View`, and using a \"`Controller`\" to manage them.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "class Model(object):\n", + " def __init__(self):\n", + " self.positions=np.random.rand(100,2)\n", + " self.speeds=np.random.rand(100,2)+np.array([-0.5,-0.5])[np.newaxis,:]\n", + " self.deltat=0.01\n", + " \n", + " def simulation_step(self):\n", + " self.positions += self.speeds * self.deltat\n", + " \n", + " def agent_locations(self):\n", + " return self.positions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### View" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class View(object):\n", + " def __init__(self, model):\n", + " from matplotlib import pyplot as plt\n", + " self.figure=plt.figure()\n", + " axes=plt.axes()\n", + " self.model=model\n", + " self.scatter=axes.scatter(model.agent_locations()[:,0],\n", + " model.agent_locations()[:,1])\n", + " \n", + " def update(self):\n", + " self.scatter.set_offsets(self.model.agent_locations())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Controller" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Controller(object):\n", + " def __init__(self):\n", + " self.model=Model() # Or use Builder\n", + " self.view=View(self.model)\n", + " def animate(frame_number):\n", + " self.model.simulation_step()\n", + " self.view.update()\n", + " self.animator=animate\n", + " \n", + " def go(self):\n", + " from JSAnimation import IPython_display\n", + " from matplotlib import animation\n", + " anim = animation.FuncAnimation(self.view.figure, self.animator, frames=200, interval=50)\n", + " return anim" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "contl=Controller()" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
      \n", + " \n", + "
      \n", + " \n", + "
      \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
      \n", + " Once \n", + " Loop \n", + " Reflect \n", + " \n", + "
      \n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "contl.go()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Engineering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Software Engineering Stages" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Requirements\n", + "* Functional Design\n", + "* Architectural Design\n", + "* Implementation\n", + "* Integration\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Requirements Engineering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Requirements capture obviously means describing the things the software needs to be able to do.\n", + "\n", + "A common approach is to write down lots of \"user stories\", describing how the software helps the user achieve something:\n", + "\n", + "> As a clinician, when I finish an analysis, I want a report to be created on the test results, so that I can\n", + "> send it to the patient.\n", + "\n", + "As a *role*, when *condition or circumstance applies* I want *a goal or desire* so that *benefits occur*.\n", + "\n", + "These are easy to map into the Gherkin behaviour driven design test language.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Functional and architectural design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Engineers try to separate the functional design, how the software appears to and is used by the user, from the\n", + "architectural design, how the software achieves that functionality.\n", + "\n", + "Changes to functional design require users to adapt, and are thus often more costly than changes to architectural design.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Waterfall" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The _Waterfall_ design philosophy argues that the elements of design should occur in order: first requirements capture, then functional design,\n", + "then architectural design. This approach is based on the idea that if a mistake is made in the design, then programming effort is wasted,\n", + "so significant effort is spent in trying to ensure that requirements are well understood and that the design is correct before programming starts.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why Waterfall?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Without a design approach, programmers resort to designing as we go, typing in code, trying what works, and making it up as we go along.\n", + "When trying to collaborate to make software with others this can result in lots of wasted time, software that only the author understands,\n", + "components built by colleagues that don't work together, or code that the programmer thinks is nice but that doesn't meet the user's requirements.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Problems with Waterfall" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Waterfall results in a contractual approach to development, building an us-and-them relationship between users, business types, designers, and programmers.\n", + "\n", + "> I built what the design said, so I did my job.\n", + "\n", + "Waterfall results in a paperwork culture, where people spend a long time designing standard forms to document each stage of the design,\n", + "with less time actually spent *making things*.\n", + "\n", + "Waterfall results in excessive adherence to a plan, even when mistakes in the design are obvious to people doing the work.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Software is not made of bricks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The waterfall approach to software engineering comes from the engineering tradition applied to building physical objects,\n", + "where Architects and Engineers design buildings, and builders build them according to the design.\n", + "\n", + "Software is intrinsically different:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Software is not made of bricks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> Software is not the same 'stuff' as that from which physical systems are constructed.\n", + "Software systems differ in material respects from physical systems.\n", + "Much of this has been rehearsed by Fred Brooks in his classic\n", + "['No Silver Bullet'](http://ieeexplore.ieee.org/xpl/login.jsp?reload=true&tp=&arnumber=1663532&url=http%3A%2F%2Fieeexplore.ieee.org%2Fxpls%2Fabs_all.jsp%3Farnumber%3D1663532) paper.\n", + "First, complexity and scale are different in the case of software systems: relatively functionally simple software systems comprise more independent parts, placed\n", + "in relation to each other, than do physical systems of equivalent functional value.\n", + "Second, and clearly linked to this, we do not have well developed components and composition mechanisms from which to build\n", + "software systems (though clearly we are working hard on providing these) nor do we have a straightforward mathematical account that\n", + "permits us to reason about the effects of composition.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Software is not made of bricks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> Third, software systems operate in a domain determined principally by arbitrary rules about information and symbolic communication whilst the\n", + "operation of physical systems is governed by the laws of physics.\n", + "Finally, software is readily changeable and thus is changed, it is used in settings where our uncertainty leads us to anticipate the need to change.\n", + "\n", + "-- Prof. [Anthony Finkelstein](http://blog.prof.so/), UCL Dean of Engineering, and Professor of Software Systems Engineering\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The Agile Manifesto" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In 2001, authors including Martin Folwer, Ward Cunningham and Kent Beck met in a Utah ski resort, and published the following manifesto.\n", + "\n", + " [Manifesto for Agile Software Development](http://agilemanifesto.org/)\n", + "\n", + " We are uncovering better ways of developing\n", + " software by doing it and helping others do it.\n", + " Through this work we have come to value:\n", + "\n", + " * _Individuals and interactions_ over processes and tools\n", + " * _Working software_ over comprehensive documentation\n", + " * _Customer collaboration_ over contract negotiation\n", + " * _Responding to change_ over following a plan\n", + "\n", + " That is, while there is value in the items on\n", + " the right, we value the items on the left more.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Agile is not absence of process" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> The Agile movement is not anti-methodology, in fact, many of us want to restore credibility to the word methodology.\n", + "> We want to restore a balance. We embrace modeling, but not in order to file some diagram in a dusty corporate repository.\n", + "> We embrace documentation, but not hundreds of pages of never-maintained and rarely-used tomes. We plan, but recognize the\n", + "> limits of planning in a turbulent environment. Those who would brand proponents of XP or SCRUM or any of the other\n", + "> Agile Methodologies as \"hackers\" are ignorant of both the methodologies and the original definition of the term hacker\n", + "\n", + "-- Jim Highsmith.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Elements of an Agile Process" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Continuous delivery\n", + "* Self-organising teams\n", + "* Iterative development\n", + "* Ongoing design\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ongoing Design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Agile development doesn't eschew design. Design documents should still be written, but treated as living documents,\n", + "updated as more insight is gained into the task, as work is done, and as requirements change.\n", + "\n", + "Use of a Wiki or version control repository to store design documents thus works much better than using Word documents!\n", + "\n", + "Test-driven design and refactoring are essential techniques to ensure that lack of \"Big Design Up Front\" doesn't produce\n", + "badly constructed spaghetti software which doesn't meet requirements. By continously scouring our code for smells, and\n", + "stopping to refactor, we evolve towards a well-structured design with weakly interacting units. By starting with tests\n", + "which describe how our code should behave, we create executable specifications, giving us confidence that the code does\n", + "what it is supposed to.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Iterative Development" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Agile development maintains a backlog of features to be completed and bugs to be fixed. In each iteration, we start with a meeting where\n", + "we decide which backlog tasks will be attempted during the development cycle, estimating how long each will take,\n", + "and selecting an achievable set of goals for the \"sprint\". At the end of each cycle, we review the goals completed and missed,\n", + "and consider what went well, what went badly, and what could be improved.\n", + "\n", + "We try not to add work to a cycle mid-sprint. New tasks that emerge are added to the backlog, and considered in the next planning meeting.\n", + "This reduces stress and distraction.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Continuous Delivery" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In agile development, we try to get as quickly as possible to code that can be *demonstrated* to clients. A regular demo of progress\n", + "to clients at the end of each development iteration says so much more than sharing a design document. \"Release early, release often\"\n", + "is a common slogan. Most bugs are found by people *using* code -- so exposing code to users as early as possible will help find bugs quickly.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Self-organising teams" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Code is created by people. People work best when they feel ownership and pride in their work. Division of responsiblities into designers\n", + "and programmers results in a [\"Code Monkey\"](http://open.spotify.com/track/1rIFZk9tTUtHP3vULR5wXe) role, where the craftspersonship and \n", + "sense of responsibility for code quality is lost. Agile approaches encourage programmers, designers, clients, and businesspeople to see\n", + "themselves as one team, working together, with fluid roles. Programmers grab issues from the backlog according to interest, aptitude,\n", + "and community spirit.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Agile in Research" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Agile approaches, where we try to turn the instincts and practices which emerge naturally when smart programmers get together into\n", + "well-formulated best practices, have emerged as antidotes to both the chaotic free-form typing in of code, and the rigid\n", + "paperwork-driven approaches of Waterfall.\n", + "\n", + "If these approaches have turned out to be better even in industrial contexts, where requirements for code can be well understood,\n", + "they are even more appropriate in a research context, where we are working in poorly understood fields with even less well captured\n", + "requirements.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Conclusion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "* Don't ignore design\n", + "* See if there's a known design pattern that will help\n", + "* Do try to think about how your code will work before you start typing\n", + "* Do use design tools like UML to think about your design without coding straight away\n", + "* Do try to write down some user stories\n", + "* Do maintain design documents.\n", + "\n", + "BUT\n", + "\n", + "* Do change your design as you work, updating the documents if you have them\n", + "* Don't go dark -- never do more than a couple of weeks programming without showing what you've done to colleagues\n", + "* Don't get isolated from the reasons for your code's existence, stay involved in the research, don't be a Code Monkey.\n", + "* Do keep a list of all the things your code needs, estimate and prioritise tasks carefully.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to classes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Complete the exercise on Boids from last week, as far as creating a class for a Boid, if you haven't already.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to Inheritance and Polymorphism" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In the Eagle branch in my Boids repository you will find an extension of Boids to support multiple kinds of Bird.\n", + "You will see that this suffers from the use of an `if (type)` statement which would be\n", + "better implemented with inheritance and polymorphism.\n", + "\n", + "To access the Eagle branch:\n" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "cd \n", + "git checkout eagle" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to Patterns: Builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The way in which we construct our Boids model's bird content, specifying each of the\n", + "model parameters as a constructor, and add birds using `initialise_random`\n", + "and `initialise_from_data` is rather clunky.\n", + "\n", + "Create a `ModelBuilder` class with methods to define model parameters and to\n", + "add a random boid and boid or boids from data, using the Builder pattern.\n", + "\n", + "You could even create two subclasses of `ModelBuilder` to build either random boids\n", + "or boids from a dataset.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Refactoring to Patterns: Model/View" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "You can apply Model/View to create a separate class to visualise boid models using Matplotlib,\n", + "and even perhaps write another viewer to visualise the boids in a different way: perhaps as graphs over time\n", + "of dispersal, distance from flock to eagle and average velocity.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using UML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "You should also use [YUML](http://yuml.me) or another UML tool to visualise your class structure.\n", + "\n", + "You don't have to do all these things to pass the assignment: any minimally object-oriented implementation of the boids\n", + "will pass." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Session 6" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session07/Draft.ipynb b/session07/Draft.ipynb deleted file mode 100644 index b8e81697..00000000 --- a/session07/Draft.ipynb +++ /dev/null @@ -1,3695 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:fe7d42fe869e40d01b947a84a9844ea19f170b5d4db5ec26ee291c09e1764cbf" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Introduction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Code can often be annoyingly full of \"boiler-plate\" code: characters you don't really want to have to type.\n", - "\n", - "Not only is this tedious, it's also time-consuming and dangerous: unnecessary code is an unnecessary potential place for mistakes.\n", - "\n", - "There are two important phrases in software design that we've spoken of before in this context:\n", - "\n", - "> Once And Only Once\n", - "\n", - "> Don't Repeat Yourself (DRY)\n", - "\n", - "All concepts, ideas, or instructions should be in the program in just one place.\n", - "Every line in the program should say something useful and important.\n", - "\n", - "We refer to code that respects this principle as DRY code." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this lecture, we'll look at some techniques that can enable us to refactor away repetitive code.\n", - "\n", - "Since in many of these places, the techniques will involve working with functions as if they were variables, we'll learn some **functional** programming. We'll also learn more about the innards of how Python implements classes.\n", - "\n", - "We'll also think about how to write programs that *generate* the more verbose, repetitive program we could otherwise write.\n", - "We call this **metaprogramming**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Before we start on the details, we'll introduce a few examples." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Consider a bunch of variables, each of which need initialising and incrementing:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "bananas=0\n", - "apples=0\n", - "oranges=0\n", - "bananas+=1\n", - "apples+=1\n", - "oranges+=1" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The right hand side of these assignments doesn't respect the DRY principle. We could of course define a variable for our initial value:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "initial_fruit_count=0\n", - "bananas=initial_fruit_count\n", - "apples=initial_fruit_count\n", - "oranges=initial_fruit_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, this is still not as DRY as it could be: what if we wanted to replace the assignment with, say, a class constructor and a buy operation:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Basket:\n", - " def __init__(self):\n", - " self.count=0\n", - " def buy(self):\n", - " self.count+=1" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "bananas=Basket()\n", - "apples=Basket()\n", - "oranges=Basket()\n", - "bananas.buy()\n", - "apples.buy()\n", - "oranges.buy()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We had to make the change in three places. Whenever you see a situation where a refactoring or change of design might require you to change the code in multiple places, you have an opportunity to make the code DRYer." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this case, metaprogramming for incrementing these variables would involve just a loop over all the variables we want to initialise:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets=[bananas, apples, oranges]\n", - "for basket in baskets: basket.buy()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, this trick **doesn't** work for initialising a new variable:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets=[bananas, apples, oranges, kiwis]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So can we declare a new variable programmatically? Given a list of the **names** of fruit baskets we want, initialise a variable with that name?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "basket_names=['bananas', 'apples', 'oranges', 'kiwis']" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "globals()['apples']" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Wow, we can! Every module or class in Python, is, under the hood, a special dictionary, storing the values in its **namespace**. So we can create new variables by assigning to this dictionary. globals() gives a reference to the attribute dictionary for the current module" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for name in basket_names:\n", - " globals()[name]=Basket()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print kiwis.count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is **metaprogramming**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "I would NOT recommend using it for an example as trivial as the one above. \n", - "A better, more Pythonic choice here would be to use a data structure to manage your set of fruit baskets:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets={}\n", - "for name in basket_names:\n", - " baskets[name]=Basket()\n", - "print baskets['kiwis'].count\n" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or even, using a dictionary comprehension:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets={name:Basket() for name in baskets}\n", - "print baskets['kiwis'].count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Which is the nicest way to do this, I think. Code which feels like metaprogramming is needed to make it less repetitive can often instead be DRYed up using a refactored data structure, in a way which is cleaner and more easy to understand. Nevertheless, metaprogramming is worth knowing. " - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Functional Programming" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Understanding to think in a *functional programming* style is almost as important as object orientation for building DRY, clear scientific software, and is just as conceptually difficult." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Programs are composed of functions: they take data in (which we call *parameters* or *arguments*) and send data out (through `return` statements.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A conceptual trick which is often used by computer scientists to teach the core idea of functional programming is this: to write a program,\n", - " in theory, you only ever need functions with **one** argument, even when you think you need two or more. Why?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's define a program to add two numbers:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def add(a,b):\n", - " return a+b" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add(5,6)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "How can we do this, in a version which only defined functions of one argument? In order to understand this, we'll have to understand \n", - "several of the concepts of functional programming. Let's start with a program which just adds five to something:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def add_five(a):\n", - " return a+5\n", - "add_five(6)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OK, we could define lots of these, one for each number we want to add. But that would be infinitely repetitive. So, let's try to metaprogram that: we want a function which returns these add_N() functions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's start with the easy case: a function which returns a function which adds 5 to something:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def generate_five_adder():\n", - " def _add_five(a):\n", - " return a+5\n", - " return _add_five" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "coolfunction = generate_five_adder()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "coolfunction(7)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OK, so what happened there? Well, we defined a function **inside** the other function. We can always do that:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def thirty_function():\n", - " def times_three(a):\n", - " return a*3\n", - " def add_seven(a):\n", - " return a+7\n", - " return times_three(add_seven(3))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "thirty_function()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we do this, the functions enclosed inside the outer function are **local** functions, and can't be seen outside:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add_seven" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There's not really much of a difference between functions and other variables in python. A function is just a variable which can have () put after it to call the code!" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print thirty_function" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=[thirty_function, add_five, add]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for fun in x:\n", - " print fun" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And we know that one of the things we can do with a variable is `return` it. So we can return a function, and then call it outside:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def deferred_greeting():\n", - " def greet():\n", - " print \"Hello\"\n", - " return greet\n", - "friendlyfunction=deferred_greeting()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# Do something else\n", - "print \"Just passing the time...\"" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# OK, Go!\n", - "friendlyfunction()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So now, to finish this, we just need to return a function to add an arbitrary amount:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def define_adder(increment):\n", - " def adder(a):\n", - " return a+increment\n", - " return adder" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add_3=define_adder(3)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add_3(9)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can make this even prettier: let's make another variable pointing to our define_adder() function:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add = define_adder" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And now we can do the real magic:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "add(8)(5)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You may have noticed something a bit weird:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the definition of `define_adder`, `increment` is a local variable. It should have gone out of scope and died at the end of the definition. How can the amount the returned adder function is adding still be kept?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is called a **closure**. In Python, whenever a function definition references a variable in the surrounding scope, it is preserved within the function definition." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can close over global module variables as well:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "name = \"James\"\n", - "def greet():\n", - " print \"Hello, \", name" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "greet()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And note that the closure stores a reference to the variable in the surrounding scope: (\"Late Binding\")" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "name=\"Matt\"" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "greet()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We often want to apply a function to each variable in an array, to return a new array. We can do this with a list comprehension:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "numbers=range(10)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "[add_five(i) for i in numbers]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But this is sufficiently common that there's a quick built-in:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "map(add_five, numbers)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This **map** operation is really important conceptually when understanding efficient parallel programming: different computers can apply the *mapped* function to their input at the same time. We call this Single Program, Multiple Data. (SPMD) **map** is half of the **map-reduce** functional programming paradigm which is key to the efficient operation of much of today's \"data science\" explosion. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's continue our functional programming mind-stretch by looking at **reduce** operations." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We very often want to loop with some kind of accumulator, such as when finding a mean, or finding a maximum:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mean(data):\n", - " sum=0.0\n", - " for x in data:\n", - " sum+=x\n", - " return sum/len(data)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "mean(range(10))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import sys\n", - "def my_max(data):\n", - " # Start with the smallest possible number\n", - " highest=sys.float_info.min\n", - " for x in data:\n", - " if x>highest:\n", - " highest=x\n", - " return highest\n", - " " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "my_max([2,5,10,-11,-5])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These operations, where we have some variable which is building up a result, and the result is updated with some operation, can be gathered together as a functional program, taking in the operation to be used to combine results as an argument:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def accumulate(initial, operation, data):\n", - " accumulator=initial\n", - " for x in data:\n", - " accumulator=operation(accumulator, x)\n", - " return accumulator" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def my_sum(data):\n", - " def _add(a,b):\n", - " return a+b\n", - " return accumulate(0, _add, data)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "my_sum(range(5))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def bigger(a,b):\n", - " if b>a:\n", - " return b\n", - " return a\n", - " \n", - "def my_max(data):\n", - " return accumulate(sys.float_info.min, _bigger, data)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "my_max([2,5,10,-11,-5])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, because these operations, _bigger, and _add, are such that e.g. (a+b)+c = a+(b+c) , i.e. they are **associative**, we could apply our accumulation\n", - "to the left half and the right half of the array, each on a different computer, and then combine the two halves:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "1+2+3+4=(1+2)+(3+4)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Indeed, with a bigger array, we can divide-and-conquer more times:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "1+2+3+4+5+6+7+8=((1+2)+(3+4))+((5+6)+(7+8))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So with enough parallel computers, we could do this operation on eight numbers in three steps: first, we use four computers to do one each of the pairwise adds." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, we use two computers to add the four totals." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, we use one of the computers to do the final add of the two last numbers." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You might be able to do the maths to see that with an N element list, the number of such steps is proportional to the logarithm of N." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We say that with enough computers, reduction operations are O(ln N)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This course isn't an introduction to algorithms, but we'll talk more about this O() notation when we think about programming for performance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Anyway, this accumulate-under-an-operation process, is so fundamental to computing that it's usually in standard libraries for languages which allow functional programming:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from functools import reduce" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def my_max(data):\n", - " return reduce(bigger,data,sys.float_info.min)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "my_max([2,5,10,-11,-5])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When doing functional programming, we often want to be able to define a function on the fly:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def most_Cs_in_any_sequence(sequences):\n", - " def count_Cs(sequence):\n", - " return sequence.count('C')\n", - " counts=map(count_Cs, sequences)\n", - " return max(counts)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def most_Gs_in_any_sequence(sequences):\n", - " return max(map(lambda sequence: sequence.count('G'),sequences))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data=[\n", - " \"CGTA\",\n", - " \"CGGGTAAACG\",\n", - " \"GATTACA\"\n", - "]\n", - "most_Gs_in_any_sequence(data)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The syntax here is that:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "func_name=lambda a,b,c : a+b+c" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "is identical in every way to:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def func_name(a,b,c):\n", - " a+b+c" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "lambda defines an \"anonymous\" function." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def most_of_given_base_in_any_sequence(sequences, base):\n", - " return max(map(lambda sequence: sequence.count(base), sequences))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "most_of_given_base_in_any_sequence(data,'A')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above fragment defined a lambda function as a **closure** over `base`. If you understood that, you've got it! " - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def my_max(data): return reduce(lambda a,b: a if a>b else b, data, sys.float_info.min)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "my_max([2,5,10,-11,-5])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Using functional programming" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Probably the most common use in research computing for functional programming is the application of a numerical method to a function. For example:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from scipy.optimize import newton\n", - "from numpy import linspace, zeros\n", - "%matplotlib inline\n", - "from matplotlib import pyplot as plt\n", - "solve_me=lambda x: x**2-x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "newton(solve_me, 2), newton(solve_me,0.2)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "xs=linspace(-1,2,50)\n", - "plt.plot(xs,map(solve_me,xs),xs,zeros(50))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes such tools return another function:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def derivative(func, eps):\n", - " def _func_derived(x):\n", - " return (func(x+eps)-func(x))/eps\n", - " return _func_derived" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(xs,map(solve_me,xs),xs,map(derivative(solve_me,0.01),xs))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "newton(derivative(solve_me,0.01),0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Of course, coding your own numerical methods is bad:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import scipy.misc\n", - "\n", - "def derivative(func):\n", - " def _func_derived(x):\n", - " return scipy.misc.derivative(solve_me,x)\n", - " return _func_derived\n", - "\n", - "newton(derivative(solve_me),0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you've done a moderate amount of calculus, then you'll find similarities between functional programming in computer science and Functionals in the calculus of variations." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Iterators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We've seen that in Python, anything which can be iterated over is called an iterable:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for key in baskets:\n", - " print key.upper()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Surprisingly often, we want to iterate over something that takes a moderately large amount of storage to store. For example, our map images in the green-graph example." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our solution last time involved making an array of all the maps between London and Birmingham. This kept them all in memory *at the same time*: first we downloaded all the maps, then we counted the green pixels in each of them. This would NOT work if we used more points. We could do this with a for loop and an append(), but that's not as elegant as using a **generator**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Consider the basic python `range` function:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "range(10)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "total=0\n", - "for x in range(int(1e6)): total+= x\n", - "print total" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "While this was executing, the range() statement allocated a million integers. This is very inefficient. We don't actually need a million integers, just each integer in turn up to a million." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xrange(3)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a=iter(xrange(3))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "total=0\n", - "for x in xrange(int(1e6)): total+= x\n", - "print total" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets.items()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "baskets.iteritems()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can make our own iterators by defining classes that implement next() and __iter__() methods: this is the iterator protocol." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For each of the concepts we've talked about, python defines a protocol, a set of methods a class must implement, in order to be treated as a member of that concept." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the iterator protocol, the protocol that defines things that support `for x in y:`, the methods that must be supported are `next()` and `__iter__()`." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class fib_iterator(object):\n", - " def __init__(self, limit, seed1=1, seed2=1):\n", - " self.limit=limit\n", - " self.previous=seed1\n", - " self.current=seed2\n", - " def __iter__(self):\n", - " return self\n", - " def next(self):\n", - " self.previous, self.current=self.current, self.previous+self.current\n", - " self.limit -=1\n", - " if self.limit<0: raise StopIteration() # This will be explained in a few slides!\n", - " return self.current" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=fib_iterator(5)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for x in fib_iterator(5):\n", - " print x" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "sum(fib_iterator(5))" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In fact, if, to be iterated over, a class just wants to behave as if it were some other iterable, you can just implement `__iter__` and return `iter(some_other_iterable)`, without implementing `next`. For example, an image class might want to implement some metadata, but behave just as if it were just a 1-d pixel array when being iterated:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from numpy import array\n", - "class MyImage(object):\n", - " def __init__(self, pixels):\n", - " self.pixels=array(pixels,dtype='uint8')\n", - " self.channels=self.pixels.shape[2]\n", - " def __iter__(self):\n", - " # return an iterator over the pixels\n", - " # See future NumPy lecture for using reshape\n", - " return iter(self.pixels.reshape(-1,self.channels))\n", - " def show(self):\n", - " plt.imshow(self.pixels, interpolation=\"None\")" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=[[[255,255,0],[0,255,0]],[[0,0,255],[255,255,255]]]\n", - "image=MyImage(x)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "image.show()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "image.channels" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from webcolors import rgb_to_name\n", - "for pixel in image:\n", - " print rgb_to_name(pixel)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Technically, the **iterator** protocol is to implement both `__iter__` and `next`, while the **iterable** protocol is to implement `__iter__` and return an **iterator**." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There's a fair amount of \"boiler-plate\" in the above class-based definition of an iterable. Python provides another way to specify something\n", - "which meets the iterator protocol: generators." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def my_generator():\n", - " yield 5\n", - " yield 10\n", - "x=my_generator()\n", - "print x.next()\n", - "print x.next()\n", - "print x.next()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A function which has `yield` statements instead of a `return` statement returns **temporarily**. Each call of next() returns control to the function. Where it left off. Control passes back-and-forth between the generator and the caller. Our fibonnaci example therefore becomes:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def yield_fibs(limit, seed1=1,seed2=1):\n", - " current=seed1\n", - " previous=seed2\n", - " while limit>0:\n", - " limit-=1\n", - " current, previous = current+previous, current\n", - " yield current\n", - "sum(yield_fibs(5))" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(list(yield_fibs(20)))" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Exceptions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we learned about testing, we saw that Python complains when things go wrong by raising an \"Exception\" naming a type of error:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "1/0" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Exceptions are objects, forming a class hierarchy. We just raised an instance of the ZeroDivisionError class, making the program crash." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import inspect\n", - "inspect.getmro(ZeroDivisionError)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So we can see that a zero division error is a particular kind of Arithmetic Error." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=1\n", - "for y in x: print y" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "inspect.getmro(TypeError)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we were looking at testing, we saw that it is important for code to crash with a meaningful exception type when something is wrong.\n", - "We raise an Exception with `raise`. Often, we can look for an appropriate exception from the standard set to raise. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we may want to define our own exceptions. Doing this is as simple as inheriting from Exception:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class MyCustomErrorType(Exception):\n", - " pass" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "raise(MyCustomErrorType(\"Problem\"))" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can add custom data to your exception:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class MyCustomErrorType(Exception):\n", - " def __init__(self, category=None):\n", - " self.category=category\n", - " def __str__(self):\n", - " return \"Error, cateory \" + str(self. category)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "raise(MyCustomErrorType(404))" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The real power of exceptions comes, however, not in letting them crash the program, but in letting your program handle them. We say that an exception has been \"thrown\" and then \"caught\"." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import yaml\n", - "try:\n", - " config=yaml.load(open(\"datasource.yaml\"))\n", - " user=config[\"userid\"]\n", - " password=config[\"password\"]\n", - "except IOError:\n", - " user=\"anonymous\"\n", - " password=None\n", - "print user" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that we specify only the error we expect to happen and want to handle. Sometimes you see code that catches everything:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "try:\n", - " config=yaml.lod(open(\"datasource.yaml\"))\n", - " user=config[\"userid\"]\n", - " password=config[\"password\"]\n", - "except:\n", - " user=\"anonymous\"\n", - " password=None\n", - "print user" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There was a mistyped function name there, but we did not notice the error, as the generic except caught it. \n", - "Therefore, we should catch only the error we want." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile datasource2.yaml\n", - "userid: jamespjh\n", - "password: secret" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile datasource3.yaml\n", - "user: jamespjh\n", - "password: secret" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def read_credentials(source):\n", - " try:\n", - " datasource=open(source)\n", - " config=yaml.load(datasource)\n", - " user=config[\"userid\"]\n", - " password=config[\"password\"]\n", - " datasource.close()\n", - " except IOError:\n", - " user=\"anonymous\"\n", - " password=None\n", - " return user, password\n", - "print read_credentials('datasource2.yaml')" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print read_credentials('datasource.yaml')" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print read_credentials('datasource3.yaml')" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This last code has a flaw: the file was successfully opened, the missing key was noticed, but not explicitly closed. It's normally OK, as python will close the file as soon as it notices there are no longer any references to datasource in memory, after the function exits. But this is not good practice, you should keep a file handle for as short a time as possible." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def read_credentials(source):\n", - " try:\n", - " datasource=open(source)\n", - " config=yaml.load(datasource)\n", - " user=config[\"userid\"]\n", - " password=config[\"password\"]\n", - " except IOError:\n", - " user=\"anonymous\"\n", - " password=None\n", - " finally:\n", - " datasource.close()\n", - " return user, password" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `finally` clause is executed whether or not an exception occurs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The last optional clause of a `try` statement, an `else` clause is called only if an exception is NOT raised. It can be a better place than the `try` clause to put code other than that which you expect to raise the error, and which you do not want to be executed if the error is raised. It is executed in the same circumstances as code put in the end of the `try` block, the only difference is that errors raised during the `else` clause are not caught. Don't worry if this seems useless to you; most languages implementations of try/except don't support such a clause." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def read_credentials(source):\n", - " try:\n", - " datasource=open(source)\n", - " except IOError:\n", - " user=\"anonymous\"\n", - " password=None\n", - " else:\n", - " config=yaml.load(datasource)\n", - " user=config[\"userid\"]\n", - " password=config[\"password\"]\n", - " finally:\n", - " datasource.close()\n", - " return user, password" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Exceptions do not have to be caught close to the part of the program calling them. They can be caught anywhere \"above\" the calling point in\n", - "the call stack: control can jump arbitrarily far in the program: up to the `except` clause of the \"highest\" containing try statement." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def f4(x):\n", - " if x==0:\n", - " return\n", - " if x==1:\n", - " raise ArithmeticError()\n", - " if x==2:\n", - " raise SyntaxError()\n", - " if x==3:\n", - " raise TypeError()\n", - "\n", - "def f3(x):\n", - " try:\n", - " print \"F3Before\"\n", - " f4(x)\n", - " print \"F3After\"\n", - " except ArithmeticError:\n", - " print \"F3Except\"\n", - "\n", - "def f2(x):\n", - " try:\n", - " print \"F2Before\"\n", - " f3(x)\n", - " print \"F2After\"\n", - " except SyntaxError:\n", - " print \"F2Except\"\n", - " \n", - "\n", - "def f1(x):\n", - " try:\n", - " print \"F1Before\"\n", - " f2(x)\n", - " print \"F1After\"\n", - " except TypeError:\n", - " print \"F1Except\"\n", - " \n", - "\n", - " " - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "f1(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "f1(1)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "f1(2)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "f1(3)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Design with Exceptions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we know how exceptions work, we need to think about the design implications... How best to use them." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Traditional software design theory will tell you that they should only be used to describe and recover from **exceptional** conditions: things going wrong. Normal program flow shouldn't use them." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Python's designers take a different view: use of exceptions in normal flow is considered OK. For example, all iterators raise a `StopIteration` exception to\n", - "indicate the iteration is complete." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A commonly recommended python design pattern is to use exceptions to determine whether an object implments a protocol (concept/interface), rather than testing on type." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example, we might want a function which can be supplied *either* a data series *or* a path to a location on disk where data can be found. We can examine the type of the supplied content:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import yaml\n", - "def analysis(source):\n", - " if type(source)==dict:\n", - " name=source['modelname']\n", - " else:\n", - " content=open(source)\n", - " source=yaml.load(content)\n", - " name=source['modelname']\n", - " print name" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "analysis({'modelname':'Super'})" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile example.yaml\n", - "modelname: Brilliant" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "analysis('example.yaml')" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we can also use the try-it-and-handle-exceptions approach to this. " - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def analysis(source):\n", - " try:\n", - " name=source['modelname']\n", - " except TypeError:\n", - " content=open(source)\n", - " source=yaml.load(content)\n", - " name=source['modelname']\n", - " print name" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "analysis('example.yaml')" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This approach is more extensible, and **behaves properly if we give it some other data-source which responds like a dictionary or string.**" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def analysis(source):\n", - " try:\n", - " name=source['modelname']\n", - " except TypeError:\n", - " # Source was not a dictionary-like object\n", - " # Maybe it is a file path\n", - " try:\n", - " content=open(source)\n", - " source=yaml.load(content)\n", - " name=source['modelname']\n", - " except IOError:\n", - " # Maybe it was already raw YAML content\n", - " source=yaml.load(source)\n", - " name=source['modelname']\n", - " print name" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "analysis(\"modelname: Amazing\")" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes we want to catch an error, partially handle it, perhaps add some extra data to the exception, and then re-raise to be caught again further up the call stack. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The keyword \"`raise`\" with no argument in an `except:` clause will cause the caught error to be re-thrown. Doing this is the only circumstance where it is safe to do except: without catching a specfic type of error." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "try:\n", - " # Something\n", - " pass\n", - "except:\n", - " # Do this code here if anything goes wrong\n", - " raise" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It can be useful to catch and re-throw an error as you go up the chain, doing any clean-up needed for each layer of a program." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The error will finally be caught and not re-thrown only at a higher program layer that knows how to recover. This is known as the \"throw low catch high\" principle.\n" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Context managers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We saw that instead of separately `open`ing and `close`ing a file, we can have the file be automatically closed using a context manager:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with open('example.yaml') as foo:\n", - " print yaml.load(foo)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "How could we define our own one of these, if we too have clean-up code we always want to run after a calling function has done its work, or set-up code we want to do first?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can define a class that meets an appropriate protocol:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class verbose_context():\n", - " def __init__(self, name):\n", - " self.name=name\n", - " def __enter__(self):\n", - " print \"Get ready, \", self.name\n", - " def __exit__(self, exc_type, exc_value, traceback):\n", - " print \"OK, done\"" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with verbose_context(\"James\"):\n", - " print \"Doing it!\"" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, this is pretty verbose! Again, a generator with `yield` makes for an easier syntax:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from contextlib import contextmanager\n", - "\n", - "@contextmanager\n", - "def verbose_context(name):\n", - " print \"Get ready for action, \", name\n", - " yield name.upper()\n", - " print \"You did it\"" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with verbose_context(\"James\") as shouty:\n", - " print \"Doing it, \", shouty" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, we use `yield` to temporarily return from a function." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Decorators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When doing functional programming, we may often want to define mutator functions which take in one function and return a new function, such as our derivative example earlier." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def repeater(func, count):\n", - " def _repeated(x):\n", - " counter=count\n", - " while counter>0:\n", - " counter-=1\n", - " x=func(x)\n", - " return x\n", - " return _repeated" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from math import sqrt" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "fiftyroots=repeater(sqrt,50)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "fiftyroots(100)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It turns out that, quite often, we want to apply one of these to a function as we're defining a class.\n", - "For example, we may want to specify that after certain methods are called, data should always be stored:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def reset_required(func):\n", - " def _with_data_save(self, *args):\n", - " func(self,*args)\n", - " self.stored_data.append(self.data)\n", - " return _with_data_save\n", - "\n", - "class SomeClass(object):\n", - " def __init__(self):\n", - " self.data=[]\n", - " self.stored_data=[]\n", - " \n", - " def _step1(self, ins):\n", - " self.data=[x*2 for x in ins]\n", - " \n", - " step1=reset_required(_step1)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=SomeClass()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.step1(\"Hello\")\n", - "x.data" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.step1(\"World\")\n", - "x.data" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.stored_data" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Python provides some \"syntactic sugar\" to make this kind of coding prettier:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def reset_required(func):\n", - " def _with_data_save(self, *args):\n", - " func(self,*args)\n", - " self.stored_data.append(self.data)\n", - " return _with_data_save\n", - "\n", - "class SomeClass(object):\n", - " def __init__(self):\n", - " self.data=[]\n", - " self.stored_data=[]\n", - " \n", - " @reset_required\n", - " def step1(self, ins):\n", - " self.data=[x*2 for x in ins]" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=SomeClass()\n", - "x.step1(\"Hello\")\n", - "x.step1(\"World\")\n", - "x.stored_data" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Any function which accepts a function as its first argument and returns a function can be used as a **decorator** like this." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Much of Python's standard functionality is implemented as decorators: we've seen @contextmanager, @classmethod and @attribute. The @contextmanager metafunction, for example, takes in an iterator, and yields a class conforming to the context manager protocol." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Testing and functional programming." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A few weeks ago we saw a test which loaded its test cases from a YAML file and asserted each input with each output. This was nice and concise, but had one flaw: we had just one test, covering all the fixtures, so we got just one . in the test output when we ran the tests, and if any test failed, the rest were not run. We can do a nicer job with a test **generator**:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def assert_examplar(**fixture):\n", - " answer=fixture.pop('answer')\n", - " assert_equal(greet(**fixture), answer)\n", - "\n", - "def test_greeter():\n", - " with open(os.path.join(os.path.dirname(__file__),'fixtures','samples.yaml')) as fixtures_file:\n", - " fixtures=yaml.load(fixtures_file)\n", - " for fixture in fixtures:\n", - " yield assert_exemplar(**fixture)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We also remember seeing `with assert_raises()` used to do negative testing." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now imagine how this context manager might be implemented:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "@contextmanager\n", - "def my_assert_raises(exception):\n", - " try:\n", - " yield\n", - " except exception:\n", - " pass\n", - " else:\n", - " raise Exception(\"Expected,\", exception, \" to be raised, nothing was.\")" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In fact, we can now use an even easier way to define negative tests: by using `@raises` as a **decorator**:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from nose.tools import raises\n", - "\n", - "@raises(TypeError, ValueError)\n", - "def test_raises_type_error():\n", - " raise TypeError(\"This test passes\")\n", - "\n", - "@raises(Exception)\n", - "def test_that_fails_by_passing():\n", - " pass" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "test_raises_type_error()" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "test_that_fails_by_passing()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, we can imagine how nose might implement this:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def my_raises(func, exception):\n", - " def _output(*args):\n", - " with assert_raises(exception):\n", - " func(*args)\n", - " return _output" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Metaprogramming class attributes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In our introductory metaprogramming example, we were working in the root namespace of a module, using the globals() function." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We want to be able to access the attribute dictionary for other objects to do metaprogramming to, for example, programmatically create class member data or variables." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Boring(object): pass" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=Boring()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.name=\"James\"" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.name" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.__dict__" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "getattr(x,'name')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "setattr(x,'age',38)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.age" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The real power of accessing the attribute dictionary comes when we realise that there is *very little difference* between member data and member functions.\n", - "Now that we know, from our functional programming, that a function is just a variable that can be *called* with `()`, we can set an attribute to a function, and\n", - "it becomes a member function!" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "setattr(Boring, 'describe', lambda self: self.name+ \" is \"+str(self.age))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.describe()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.describe" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Boring.describe" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that we set this as an attribute of the class, so it is available to other instances of `Boring`:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y=Boring()\n", - "y.describe()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def broken_birth_year(self):\n", - " import datetime\n", - " current=datetime.datetime.now().year\n", - " return current-self.age" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Boring.birth_year=broken_birth_year" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.birth_year()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x.birth_year" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "broken_birth_year.__name__" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can access the attribute dictionary for the local namespace inside a function with locals() but this *cannot safely be written to*. Lack of safe programmatic creation of local variables is a flaw in Python." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Person(object):\n", - " def __init__(self, name, age, job, children_count):\n", - " for name,value in locals().iteritems():\n", - " if name=='self': continue\n", - " print \"Setting self.\", name, \" to \", value \n", - " setattr(self, name, value)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me=Person(\"James\", 38, \"Scientific Programmer\", 0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me.name" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Metaprogramming warning!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use this stuff **sparingly**!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above example worked, but it produced Python code which is not particularly understandable.\n", - "Remember, your objective when programming is to produce code which is **descriptive of what it does**." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above code is **definitely** less readable, less maintainable and more error prone than:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Person(object):\n", - " def __init__(self, name, age, job, children_count):\n", - " self.name=name\n", - " self.age=age\n", - " self.job=job\n", - " self.children_count=children_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes, metaprogramming will be **really** helpful in making non-repetitive code, and you should have it in your toolbox, which is why I'm teaching you it.\n", - "But doing it all the time overcomplicated matters. We've talked a lot about the DRY principle, but there is another equally important principle:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "**KISS**: *Keep it simple, Stupid!*" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Whenever you write code and you think, \"Gosh, I'm really clever\",you're probably *doing it wrong*. Code should be about clarity, not showing off." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Operator overloading" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Imagine we wanted to make a library to describe some kind of symbolic algebra system:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Term(object):\n", - " def __init__(self, symbols=[], powers=[], coefficient=1):\n", - " self.coefficient=coefficient\n", - " self.data={symbol: exponent for symbol,exponent in zip(symbols, powers)}\n", - "\n", - "class Expression(object):\n", - " def __init__(self, terms):\n", - " self.terms=terms" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So that $5x^2y+7x+2$ might be constructed as:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "first=Term(['x','y'],[2,1],5)\n", - "second=Term(['x'],[1],7)\n", - "third=Term([],[],2)\n", - "result=Expression([first, second, third])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is pretty cumbersome." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What we'd really like is to have `2x+y` give an appropriate expression." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We could define add() and multiply() operations on expressions and terms:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class Term(object):\n", - " def __init__(self, *args):\n", - " lead=args[0]\n", - " if type(lead)==type(self):\n", - " # Copy constructor\n", - " self.data=dict(lead.data)\n", - " self.coefficient=lead.coefficient\n", - " \n", - " elif type(lead)==int:\n", - " self.from_constant(lead)\n", - " \n", - " elif type(lead)==str:\n", - " self.from_symbol(*args)\n", - " \n", - " elif type(lead)==dict:\n", - " self.from_dictionary(*args)\n", - "\n", - " else:\n", - " self.from_lists(*args)\n", - " \n", - " \n", - " def from_constant(self, constant):\n", - " self.coefficient=constant\n", - " self.data={}\n", - " \n", - " def from_symbol(self, symbol, coefficient=1, power=1):\n", - " self.coefficient=coefficient\n", - " self.data={symbol:power}\n", - " \n", - " def from_dictionary(self, data, coefficient=1):\n", - " self.data=data\n", - " self.coefficient=coefficient\n", - " \n", - " def from_lists(self, symbols=[], powers=[], coefficient=1):\n", - " self.coefficient=coefficient\n", - " self.data={symbol: exponent for symbol,exponent in zip(symbols, powers)}\n", - " \n", - " def add(self, *others):\n", - " return Expression((self,)+others)\n", - " \n", - " def __add__(self, other):\n", - " return self.add(other)\n", - " \n", - " def __mul__(self, other):\n", - " return self.multiply(other)\n", - " \n", - " def __rmul__(self, other):\n", - " return self.__mul__(other)\n", - " \n", - " def __radd__(self, other):\n", - " return self.__add__(other)\n", - " \n", - " def multiply(self, *others):\n", - " result_data=dict(self.data)\n", - " result_coeff=self.coefficient\n", - " # Convert arguments to Terms first if they are constants or integers\n", - " others=map(Term,others)\n", - " for another in others:\n", - " for symbol, exponent in another.data.iteritems():\n", - " if symbol in result_data:\n", - " result_data[symbol]+=another.data[symbol]\n", - " else:\n", - " result_data[symbol]=another.data[symbol]\n", - " result_coeff*=another.coefficient\n", - " return Term(result_data,result_coeff)\n", - " \n", - " def __str__(self):\n", - " def symbol_string(symbol, power):\n", - " if power==1:\n", - " return symbol\n", - " else:\n", - " return symbol+'^'+str(power)\n", - " symbol_strings=[symbol_string(symbol, power) for symbol, power in self.data.iteritems()]\n", - " prod='*'.join(symbol_strings)\n", - " if not prod:\n", - " return str(self.coefficient)\n", - " if self.coefficient==1:\n", - " return prod\n", - " else:\n", - " return str(self.coefficient)+'*'+prod\n", - " \n", - "class Expression(object):\n", - " def __init__(self, terms=[]):\n", - " self.terms=list(terms)\n", - " def add(self, *others):\n", - " result=Expression(self.terms)\n", - " for another in others:\n", - " if type(another)==Term:\n", - " result.terms.append(another)\n", - " else:\n", - " result.terms+=another.terms\n", - " return result\n", - " def multiply(self, another):\n", - " # Distributive law left as exercise\n", - " pass\n", - " def __add__(self, other):\n", - " return self.add(other)\n", - " def __radd__(self, other):\n", - " return self.__add__(other)\n", - " def __str__(self):\n", - " return '+'.join(map(str,self.terms))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now construct the above expression as:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=Term('x')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y=Term('y')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "first=Term(5).multiply(Term('x'),Term('x'),Term('y'))\n", - "second=Term(7).multiply(Term('x'))\n", - "third=Term(2)\n", - "expr=first.add(second,third)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is better, but we still can't write the expression in a 'natural' way." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we can define what `*` and `+` do when applied to Terms!:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x_plus_y=Term('x')+'y'\n", - "print x_plus_y.terms[0].data" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "five_x_ysq=Term('x')*5*'y'*'y'\n", - "print five_x_ysq.data, five_x_ysq.coefficient" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is called operator overloading. We can define what add and multiply mean when applied to our class." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that this only works so far if we multiply on the right-hand-side!" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print 5*Term('x')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, we can define a multiplication that works backwards, which is used as a fallback if the left multiply raises an error:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "fivex=5*Term('x')\n", - "print fivex.data, fivex.coefficient" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It's not easy at the moment to see if these things are working!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can add another operator method `__str__`, which defines what happens if we try to print our class:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "first=Term(5)*'x'*'x'*'y'\n", - "second=Term(7)*'x'\n", - "third=Term(2)\n", - "expr=first+second+third\n", - "print expr" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can add lots more operators to classes. `__eq__` to determine if objects are equal. `__getitem__` to apply [1] to your object. Probably the most exciting one is `__call__`, which allows us to define other classes that *behave like functions*! We call these callables." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class MyCallable(object):\n", - " def __call__(self, name):\n", - " print \"Hello, \", name" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=MyCallable()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x(\"James\")" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We've now come full circle in the blurring of the distinction between functions and objects! The full power of functional programming is really remarkable." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you want to know more about the topics in this lecture, using a different language syntax, I recommend you watch the Abelson and Sussman \"Structure and Interpretation of Computer Programs\" lectures. These are the Computer Science equivalent of the Feynman Lectures!" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import YouTubeVideo\n", - "YouTubeVideo('2Op3QLzMgSY')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Exercise" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Numbers have units. $5\\mathrm{m}^2$ is not $5\\mathrm{J}$. $6\\mathrm{J}$ is the same as $6\\mathrm{kg}\\mathrm{m}^2\\mathrm{s}^{2}$ which is the same as $2\\mathrm{N} \\cdot 3\\mathrm{m}$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write a python library to implement handling quantities with units, and converting between units, with a github repostiory and a setup.py file, and some unit tests." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You should define operators for multiply, equality, and add for your class." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Your unit tests should include things like:" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "assert(5*meters == 0.005*kilometers)" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "assert((60.seconds).to(minutes).value==1)\n", - "assert((60.seconds).to(minutes).unit==minutes)" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "with assert_raises(IncompatibleUnitsError):\n", - " 5*meters+2*seconds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You don't have to implement every unit! You might want to load your unit definitions from a yaml config file." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session07/index.md b/session07/index.md new file mode 100644 index 00000000..396c6c3c --- /dev/null +++ b/session07/index.md @@ -0,0 +1,5 @@ +--- +title: Session 7 +--- + +Some content diff --git a/session07/python/all.py b/session07/python/all.py deleted file mode 100644 index 09911fa7..00000000 --- a/session07/python/all.py +++ /dev/null @@ -1,1101 +0,0 @@ -### "variables" - -bananas=0 -apples=0 -oranges=0 -bananas+=1 -apples+=1 -oranges+=1 - -### "constant" - -initial_fruit_count=0 -bananas=initial_fruit_count -apples=initial_fruit_count -oranges=initial_fruit_count - -### "toclass" - -class Basket: - def __init__(self): - self.count=0 - def buy(self): - self.count+=1 - -bananas=Basket() -apples=Basket() -oranges=Basket() -bananas.buy() -apples.buy() -oranges.buy() - -### "loop" - -baskets=[bananas, apples, oranges] -for basket in baskets: basket.buy() - -### "newerror" -from nose.tools import assert_raises -with assert_raises(NameError): - baskets=[bananas, apples, oranges, kiwis] - - - -### "names" -basket_names=['bananas', 'apples', 'oranges', 'kiwis'] - -globals()['apples'] - - - -### "globals" - -for name in basket_names: - globals()[name]=Basket() - - -print kiwis.count - -### "dictionary" - -baskets={} -for name in basket_names: - baskets[name]=Basket() - -print baskets['kiwis'].count - -### "comprehension" - -baskets={name:Basket() for name in baskets} -print baskets['kiwis'].count - -### "add" - -def add(a,b): - return a+b - -add(5,6) - - -### "addfive" - -def add_five(a): - return a+5 - -add_five(6) - -### "funcgen" - -def generate_five_adder(): - def _add_five(a): - return a+5 - return _add_five - -coolfunction = generate_five_adder() -coolfunction(7) - -### "thirty" - -def thirty_function(): - def times_three(a): - return a*3 - def add_seven(a): - return a+7 - return times_three(add_seven(3)) - -thirty_function() - -### "scope" -with assert_raises(NameError): - add_seven - - - -### "call" - -print thirty_function -x=[thirty_function, add_five, add] - -for fun in x: - print fun - -### "deferred" - -def deferred_greeting(): - def greet(): - print "Hello" - return greet - -friendlyfunction=deferred_greeting() - -# Do something else -print "Just passing the time..." - -# OK, Go! -friendlyfunction() - -### "curry" - -def define_adder(increment): - def adder(a): - return a+increment - return adder - -add_3=define_adder(3) - -add_3(9) - -### "rename" - -add = define_adder - -### "currying" - -add(8)(5) - -### "closure" - -name = "James" -def greet(): - print "Hello, ", name - -greet() - -### "late" - -name="Matt" - -greet() - -### "comprehension2" - -numbers=range(10) - -[add_five(i) for i in numbers] - -### "map" - -map(add_five, numbers) - -### "accumulate" - -def mean(data): - sum=0.0 - for x in data: - sum+=x - return sum/len(data) - -mean(range(10)) - -import sys -def my_max(data): - # Start with the smallest possible number - highest=sys.float_info.min - for x in data: - if x>highest: - highest=x - return highest - -my_max([2,5,10,-11,-5]) - -### "accum_generalise" - -def accumulate(initial, operation, data): - accumulator=initial - for x in data: - accumulator=operation(accumulator, x) - return accumulator - -def my_sum(data): - def _add(a,b): - return a+b - return accumulate(0, _add, data) - -print my_sum(range(5)) - -def bigger(a,b): - if b>a: - return b - return a - -def my_max(data): - return accumulate(sys.float_info.min, bigger, data) - -print my_max([2,5,10,-11,-5]) - -### "reduce" - -from functools import reduce - -def my_max(data): - return reduce(bigger,data,sys.float_info.min) - -my_max([2,5,10,-11,-5]) - -### "lambda" - -def most_Cs_in_any_sequence(sequences): - def count_Cs(sequence): - return sequence.count('C') - counts=map(count_Cs, sequences) - return max(counts) - -def most_Gs_in_any_sequence(sequences): - return max(map(lambda sequence: sequence.count('G'),sequences)) - -data=[ - "CGTA", - "CGGGTAAACG", - "GATTACA" -] - -most_Gs_in_any_sequence(data) - -### "lambda_same" - -func_name=lambda a,b,c : a+b+c - -def func_name(a,b,c): - a+b+c - -### "lambda_closure" - -def most_of_given_base_in_any_sequence(sequences, base): - return max(map(lambda sequence: sequence.count(base), sequences)) - -most_of_given_base_in_any_sequence(data,'A') - -### "pretty_max" - -def my_max(data): return reduce(lambda a,b: a if a>b else b, data, - sys.float_info.min) - -my_max([2,5,10,-11,-5]) - -### "funcalgo" - -from scipy.optimize import newton -from numpy import linspace, zeros -from matplotlib import pyplot as plt -solve_me=lambda x: x**2-x - -print newton(solve_me, 2), newton(solve_me,0.2) - -xs=linspace(-1,2,50) -solved=[xs,map(solve_me,xs),xs,zeros(50)] -plt.plot(*solved) -### "solved_savefig" -plt.savefig('solved.png') -### "derivative" - -def derivative(func, eps): - def _func_derived(x): - return (func(x+eps)-func(x))/eps - return _func_derived - -derived=(xs,map(solve_me,xs),xs,map(derivative(solve_me,0.01),xs)) -plt.plot(*derived) -print newton(derivative(solve_me,0.01),0) -### "derivative_savefig" -plt.savefig('derived.png') -plt.figure() -### "stdlib" - -import scipy.misc - -def derivative(func): - def _func_derived(x): - return scipy.misc.derivative(solve_me,x) - return _func_derived - -newton(derivative(solve_me),0) - - -### "iterable" - -for key in baskets: - print key.upper() - -### "range" - -print range(10) - -total=0 -for x in range(int(1e6)): total+= x - -print total - - - -### "xrange" - -print xrange(3) - -a=iter(xrange(3)) - -print a - -print a.next() - -print a.next() - -print a.next() - -with assert_raises(StopIteration): - print a.next() - -total=0 -for x in xrange(int(1e6)): total+= x - -print total - -### "iteritems" - -print baskets.items() - -print baskets.iteritems() - -### "iterator_protocol" - -class fib_iterator(object): - def __init__(self, limit, seed1=1, seed2=1): - self.limit=limit - self.previous=seed1 - self.current=seed2 - def __iter__(self): - return self - def next(self): - (self.previous, self.current)=( - self.current, self.previous+self.current) - self.limit -=1 - if self.limit<0: raise StopIteration() # This will be - # explained in a few slides! - return self.current - -x=fib_iterator(5) - -x.next() - -x.next() - -x.next() - -x.next() - -for x in fib_iterator(5): - print x - -sum(fib_iterator(5)) - -### "iterable_protocol" - -from numpy import array -class MyImage(object): - def __init__(self, pixels): - self.pixels=array(pixels,dtype='uint8') - self.channels=self.pixels.shape[2] - def __iter__(self): - # return an iterator over the pixels - # See future NumPy lecture for using reshape - return iter(self.pixels.reshape(-1,self.channels)) - def show(self): - plt.imshow(self.pixels, interpolation="None") - -x=[[[255,255,0],[0,255,0]],[[0,0,255],[255,255,255]]] -image=MyImage(x) - -image.show() - -print image.channels - -from webcolors import rgb_to_name -for pixel in image: - print rgb_to_name(pixel) -### iterable_savefig - -plt.savefig('colors.png') -plt.figure() - -### "generator" - -def my_generator(): - yield 5 - yield 10 - -x=my_generator() -print x.next() -print x.next() - -with assert_raises(StopIteration): - print x.next() - -### "fib_generator" - -def yield_fibs(limit, seed1=1,seed2=1): - current=seed1 - previous=seed2 - while limit>0: - limit-=1 - current, previous = current+previous, current - yield current - -print sum(yield_fibs(5)) - -plt.plot(list(yield_fibs(20))) -### "fib_plot" -plt.savefig('fibonacci.png') -plt.figure() -### "divzero" -with assert_raises(ZeroDivisionError): - 1/0 - -### "hierarchy" - -import inspect -inspect.getmro(ZeroDivisionError) - -### "typerror" -x=1 -with assert_raises(TypeError): - for y in x: print y - -inspect.getmro(TypeError) - -### "custom" - -class MyCustomErrorType(Exception): - pass - -with assert_raises(MyCustomErrorType): - raise(MyCustomErrorType("Problem")) - -### "customdata" - -class MyCustomErrorType(Exception): - def __init__(self, category=None): - self.category=category - def __str__(self): - return "Error, cateory " + str(self. category) - -with assert_raises(MyCustomErrorType): - raise(MyCustomErrorType(404)) - -### "handling" - -import yaml -try: - config=yaml.load(open("datasource.yaml")) - user=config["userid"] - password=config["password"] -except IOError: - user="anonymous" - password=None - -print user - -### "omnicatch" - -try: - config=yaml.lod(open("datasource.yaml")) - user=config["userid"] - password=config["password"] -except: - user="anonymous" - password=None - -print user - -### "threereads" -with open('datasource2.yaml','w') as outfile: - outfile.write('userid: jamespjh\n') - outfile.write('password: secret\n') - -with open('datasource3.yaml','w') as outfile: - outfile.write('user: jamespjh\n') - outfile.write('password: secret\n') - -def read_credentials(source): - try: - datasource=open(source) - config=yaml.load(datasource) - user=config["userid"] - password=config["password"] - datasource.close() - except IOError: - user="anonymous" - password=None - return user, password - -print read_credentials('datasource2.yaml') - -print read_credentials('datasource.yaml') - -with assert_raises(KeyError): - print read_credentials('datasource3.yaml') - -### "finally" - -def read_credentials(source): - try: - datasource=open(source) - config=yaml.load(datasource) - user=config["userid"] - password=config["password"] - except IOError: - user="anonymous" - password=None - finally: - datasource.close() - return user, password - - -### "tryelse" - -def read_credentials(source): - try: - datasource=open(source) - except IOError: - user="anonymous" - password=None - else: - config=yaml.load(datasource) - user=config["userid"] - password=config["password"] - finally: - datasource.close() - return user, password - - - -### "stackexample" - -def f4(x): - if x==0: - return - if x==1: - raise ArithmeticError() - if x==2: - raise SyntaxError() - if x==3: - raise TypeError() - -def f3(x): - try: - print "F3Before" - f4(x) - print "F3After" - except ArithmeticError: - print "F3Except" - -def f2(x): - try: - print "F2Before" - f3(x) - print "F2After" - except SyntaxError: - print "F2Except" - -def f1(x): - try: - print "F1Before" - f2(x) - print "F1After" - except TypeError: - print "F1Except" - -print "Example 0" -f1(0) - -print "Example 1" -f1(1) - -print "Example 2" -f1(2) - -print "Example 3" -f1(3) - -### "typecheck" - -import yaml -def analysis(source): - if type(source)==dict: - name=source['modelname'] - else: - content=open(source) - source=yaml.load(content) - name=source['modelname'] - print name - -analysis({'modelname':'Super'}) - -with open('example.yaml','w') as outfile: - outfile.write('modelname: brilliant\n') - -analysis('example.yaml') - -### "duckexception" - -def analysis(source): - try: - name=source['modelname'] - except TypeError: - content=open(source) - source=yaml.load(content) - name=source['modelname'] - print name - -analysis('example.yaml') - -### "duckextension" - -def analysis(source): - try: - name=source['modelname'] - except TypeError: - # Source was not a dictionary-like object - # Maybe it is a file path - try: - content=open(source) - source=yaml.load(content) - name=source['modelname'] - except IOError: - # Maybe it was already raw YAML content - source=yaml.load(source) - name=source['modelname'] - print name - -analysis("modelname: Amazing") - -### "rethrow" - -try: - # Something - pass -except: - # Do this code here if anything goes wrong - raise - - -### "context" - -with open('example.yaml') as foo: - print yaml.load(foo) - -### "context_protocol" - -class verbose_context(): - def __init__(self, name): - self.name=name - def __enter__(self): - print "Get ready, ", self.name - def __exit__(self, exc_type, exc_value, traceback): - print "OK, done" - -with verbose_context("James"): - print "Doing it!" - -### "context_generator" - -from contextlib import contextmanager - -@contextmanager -def verbose_context(name): - print "Get ready for action, ", name - yield name.upper() - print "You did it" - -with verbose_context("James") as shouty: - print "Doing it, ", shouty - -### "repeater" - -def repeater(func, count): - def _repeated(x): - counter=count - while counter>0: - counter-=1 - x=func(x) - return x - return _repeated - -from math import sqrt - -fiftyroots=repeater(sqrt,50) - -print fiftyroots(100) - -### "reset_required" - -def reset_required(func): - def _with_data_save(self, *args): - func(self,*args) - self.stored_data.append(self.data) - return _with_data_save - -class SomeClass(object): - def __init__(self): - self.data=[] - self.stored_data=[] - def _step1(self, ins): - self.data=[x*2 for x in ins] - step1=reset_required(_step1) - -x=SomeClass() - -x.step1("Hello") -print x.data - -x.step1("World") -print x.data - -print x.stored_data - -### "reset_decorator" - -def reset_required(func): - def _with_data_save(self, *args): - func(self,*args) - self.stored_data.append(self.data) - return _with_data_save - -class SomeClass(object): - def __init__(self): - self.data=[] - self.stored_data=[] - @reset_required - def step1(self, ins): - self.data=[x*2 for x in ins] - -x=SomeClass() -x.step1("Hello") -x.step1("World") -print x.stored_data - - -### "test_generator" - -def assert_examplar(**fixture): - answer=fixture.pop('answer') - assert_equal(greet(**fixture), answer) - -def test_greeter(): - with open(os.path.join(os.path.dirname( - __file__),'fixtures','samples.yaml') - ) as fixtures_file: - fixtures=yaml.load(fixtures_file) - for fixture in fixtures: - yield assert_exemplar(**fixture) - -### "assertraises" - -@contextmanager -def my_assert_raises(exception): - try: - yield - except exception: - pass - else: - raise Exception("Expected,", exception, - " to be raised, nothing was.") - -### "decorate_raises" - -from nose.tools import raises - -@raises(TypeError, ValueError) -def test_raises_type_error(): - raise TypeError("This test passes") - -@raises(Exception) -def test_that_fails_by_passing(): - pass - -test_raises_type_error() - -with assert_raises(AssertionError): - test_that_fails_by_passing() - -### "my_decorate_raises" - -def my_raises(func, exception): - def _output(*args): - with assert_raises(exception): - func(*args) - return _output - - - -### "getattr" - -class Boring(object): pass - -x=Boring() - -x.name="James" - -print x.name - -print x.__dict__ - -print getattr(x,'name') - -setattr(x,'age',38) - -print x.age - -### "metamember" - -setattr(Boring, 'describe', lambda self: self.name+ " is "+str(self.age)) - - -print x.describe(), x.describe, Boring.describe - -### "modclass" - -y=Boring() -with assert_raises(AttributeError): - y.describe() - -### "newclassmethod" - -def broken_birth_year(self): - import datetime - current=datetime.datetime.now().year - return current-self.age - -Boring.birth_year=broken_birth_year - -print x.birth_year() - -print x.birth_year - -print broken_birth_year.__name__ - -### "locals" - -class Person(object): - def __init__(self, name, age, job, children_count): - for name,value in locals().iteritems(): - if name=='self': continue - print "Setting self.", name, " to ", value - setattr(self, name, value) - -me=Person("James", 38, "Scientific Programmer", 0) - -print me.name - -### "dontMP" - -class Person(object): - def __init__(self, name, age, job, children_count): - self.name=name - self.age=age - self.job=job - self.children_count=children_count - -### "algebra1" - -class Term(object): - def __init__(self, symbols=[], powers=[], coefficient=1): - self.coefficient=coefficient - self.data={symbol: exponent for symbol,exponent - in zip(symbols, powers)} - -class Expression(object): - def __init__(self, terms): - self.terms=terms - -### "algebra2" - -first=Term(['x','y'],[2,1],5) -second=Term(['x'],[1],7) -third=Term([],[],2) -result=Expression([first, second, third]) - - -### "magic" - -def extend(class_to_extend): - """ Metaprogramming to allow gradual implementation - of class during notebook. Thanks to - http://www.ianbicking.org/blog/2007/08/opening-python-classes.html """ - def decorator(extending_class): - for name, value in extending_class.__dict__.iteritems(): - if name in ['__dict__','__module__', '__weakref__', '__doc__']: - continue - setattr(class_to_extend,name,value) - return class_to_extend - return decorator - -### "Polyconstruct" - -class Term(object): - def __init__(self, *args): - lead=args[0] - if type(lead)==type(self): - # Copy constructor - self.data=dict(lead.data) - self.coefficient=lead.coefficient - elif type(lead)==int: - self.from_constant(lead) - elif type(lead)==str: - self.from_symbol(*args) - elif type(lead)==dict: - self.from_dictionary(*args) - else: - self.from_lists(*args) - def from_constant(self, constant): - self.coefficient=constant - self.data={} - def from_symbol(self, symbol, coefficient=1, power=1): - self.coefficient=coefficient - self.data={symbol:power} - def from_dictionary(self, data, coefficient=1): - self.data=data - self.coefficient=coefficient - def from_lists(self, symbols=[], powers=[], coefficient=1): - self.coefficient=coefficient - self.data={symbol: exponent for symbol,exponent - in zip(symbols, powers)} -### "OperatorFunctions" - -@extend(Term) -class Term(object): - def add(self, *others): - return Expression((self,)+others) - def multiply(self, *others): - result_data=dict(self.data) - result_coeff=self.coefficient - # Convert arguments to Terms first if they are - # constants or integers - others=map(Term,others) - for another in others: - for symbol, exponent in another.data.iteritems(): - if symbol in result_data: - result_data[symbol]+=another.data[symbol] - else: - result_data[symbol]=another.data[symbol] - result_coeff*=another.coefficient - return Term(result_data,result_coeff) - -### "Overloads" -@extend(Term) -class Term(object): - def __add__(self, other): - return self.add(other) - def __mul__(self, other): - return self.multiply(other) - -### "RightOpTerm" -@extend(Term) -class Term(object): - def __rmul__(self, other): - return self.__mul__(other) - def __radd__(self, other): - return self.__add__(other) - -### "StringOverload" -@extend(Term) -class Term(object): - def __str__(self): - def symbol_string(symbol, power): - if power==1: - return symbol - else: - return symbol+'^'+str(power) - symbol_strings=[symbol_string(symbol, power) - for symbol, power in self.data.iteritems()] - prod='*'.join(symbol_strings) - if not prod: - return str(self.coefficient) - if self.coefficient==1: - return prod - else: - return str(self.coefficient)+'*'+prod - -### "ExpressionConstruct" - -class Expression(object): - def __init__(self, terms=[]): - self.terms=list(terms) - -### "ExpressionFunctions" -@extend(Expression) -class Expression(object): - def add(self, *others): - result=Expression(self.terms) - for another in others: - if type(another)==Term: - result.terms.append(another) - else: - result.terms+=another.terms - return result - -### "ExpressionOverloads" -@extend(Expression) -class Expression(object): - def multiply(self, another): - # Distributive law left as exercise - pass - def __add__(self, other): - return self.add(other) - -### "RightOp" -@extend(Expression) -class Expression(object): - def __radd__(self, other): - return self.__add__(other) - -### "ExpressionStringOverload" -@extend(Expression) -class Expression(object): - def __str__(self): - return '+'.join(map(str,self.terms)) - - - -### "withfunc" - -x=Term('x') -y=Term('y') - -first=Term(5).multiply(Term('x'),Term('x'),Term('y')) -second=Term(7).multiply(Term('x')) -third=Term(2) -expr=first.add(second,third) - -### "withop" - -x_plus_y=Term('x')+'y' -print x_plus_y.terms[0].data - -five_x_ysq=Term('x')*5*'y'*'y' -print five_x_ysq.data, five_x_ysq.coefficient - -### "RightUse" - -print 5*Term('x') - -### "HardTest" - -fivex=5*Term('x') -print fivex.data, fivex.coefficient - -### "UseString" - -first=Term(5)*'x'*'x'*'y' -second=Term(7)*'x' -third=Term(2) -expr=first+second+third -print expr - -### "Callable" - -class MyCallable(object): - def __call__(self, name): - print "Hello, ", name - -x=MyCallable() - -x("James") diff --git a/session07/python/colors.png b/session07/python/colors.png deleted file mode 100644 index 57e1c245..00000000 Binary files a/session07/python/colors.png and /dev/null differ diff --git a/session07/python/derived.png b/session07/python/derived.png deleted file mode 100644 index c7d426f7..00000000 Binary files a/session07/python/derived.png and /dev/null differ diff --git a/session07/python/fibonacci.png b/session07/python/fibonacci.png deleted file mode 100644 index eeccff1b..00000000 Binary files a/session07/python/fibonacci.png and /dev/null differ diff --git a/session07/python/solved.png b/session07/python/solved.png deleted file mode 100644 index 6cc63632..00000000 Binary files a/session07/python/solved.png and /dev/null differ diff --git a/session07/session07.ipynb b/session07/session07.ipynb index 9a65367b..c4ed123f 100644 --- a/session07/session07.ipynb +++ b/session07/session07.ipynb @@ -1,2947 +1,4578 @@ { - "metadata": { - "name": null - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Introduction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Avoid Boiler-Plate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nCode can often be annoyingly full of \"boiler-plate\" code: characters you don't really want to have to type.\n\nNot only is this tedious, it's also time-consuming and dangerous: unnecessary code is an unnecessary potential place for mistakes.\n\nThere are two important phrases in software design that we've spoken of before in this context:\n\n> Once And Only Once\n\n> Don't Repeat Yourself (DRY)\n\nAll concepts, ideas, or instructions should be in the program in just one place.\nEvery line in the program should say something useful and important.\n\nWe refer to code that respects this principle as DRY code.\n\nIn this lecture, we'll look at some techniques that can enable us to refactor away repetitive code.\n\nSince in many of these places, the techniques will involve working with\nfunctions as if they were variables, we'll learn some **functional**\nprogramming. We'll also learn more about the innards of how Python implements\nclasses.\n\nWe'll also think about how to write programs that *generate* the more verbose, repetitive program we could otherwise write.\nWe call this **metaprogramming**.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Metaprogramming Example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nConsider a bunch of variables, each of which need initialising and incrementing:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "bananas=0", - "apples=0", - "oranges=0", - "bananas+=1", - "apples+=1", - "oranges+=1", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThe right hand side of these assignments doesn't respect the DRY principle. We\ncould of course define a variable for our initial value:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "initial_fruit_count=0", - "bananas=initial_fruit_count", - "apples=initial_fruit_count", - "oranges=initial_fruit_count", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nHowever, this is still not as DRY as it could be: what if we wanted to replace\nthe assignment with, say, a class constructor and a buy operation:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Basket:", - " def __init__(self):", - " self.count=0", - " def buy(self):", - " self.count+=1", - "", - "bananas=Basket()", - "apples=Basket()", - "oranges=Basket()", - "bananas.buy()", - "apples.buy()", - "oranges.buy()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWe had to make the change in three places. Whenever you see a situation where a\nrefactoring or change of design might require you to change the code in\nmultiple places, you have an opportunity to make the code DRYer.\n\nIn this case, metaprogramming for incrementing these variables would involve\njust a loop over all the variables we want to initialise:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "baskets=[bananas, apples, oranges]", - "for basket in baskets: basket.buy()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nHowever, this trick **doesn't** work for initialising a new variable:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from nose.tools import assert_raises", - "with assert_raises(NameError):", - " baskets=[bananas, apples, oranges, kiwis]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nSo can we declare a new variable programmatically? Given a list of the\n**names** of fruit baskets we want, initialise a variable with that name?\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "basket_names=['bananas', 'apples', 'oranges', 'kiwis']", - "", - "globals()['apples']", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWow, we can! Every module or class in Python, is, under the hood, a special\ndictionary, storing the values in its **namespace**. So we can create new\nvariables by assigning to this dictionary. globals() gives a reference to the\nattribute dictionary for the current module\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "for name in basket_names:", - " globals()[name]=Basket()", - "", - "", - "print kiwis.count", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThis is **metaprogramming**.\n\nI would NOT recommend using it for an example as trivial as the one above. \nA better, more Pythonic choice here would be to use a data structure to manage your set of fruit baskets:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "baskets={}", - "for name in basket_names:", - " baskets[name]=Basket()", - "", - "print baskets['kiwis'].count", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nOr even, using a dictionary comprehension:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "baskets={name:Basket() for name in baskets}", - "print baskets['kiwis'].count", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWhich is the nicest way to do this, I think. Code which feels like\nmetaprogramming is needed to make it less repetitive can often instead be DRYed\nup using a refactored data structure, in a way which is cleaner and more easy\nto understand. Nevertheless, metaprogramming is worth knowing. \n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Notebook" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThis lecture is available as an [IPython Notebook](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session07/../python/session07.ipynb)\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Functional programming" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Functional Programming" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUnderstanding to think in a *functional programming* style is almost as\nimportant as object orientation for building DRY, clear scientific software,\nand is just as conceptually difficult.\n\nPrograms are composed of functions: they take data in (which we call\n*parameters* or *arguments*) and send data out (through `return` statements.)\n\nA conceptual trick which is often used by computer scientists to teach the core\nidea of functional programming is this: to write a program,\nin theory, you only ever need functions with **one** argument, even when you think you need two or more. Why?\n\nLet's define a program to add two numbers:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def add(a,b):", - " return a+b", - "", - "add(5,6)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nHow could we do this, in a fictional version of Python which only defined functions of one argument?\nIn order to understand this, we'll have to understand several of the concepts\nof functional programming. Let's start with a program which just adds five to\nsomething:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def add_five(a):", - " return a+5", - "", - "add_five(6)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nOK, we could define lots of these, one for each number we want to add. But that\nwould be infinitely repetitive. So, let's try to metaprogram that: we want a\nfunction which returns these add_N() functions.\n\nLet's start with the easy case: a function which returns a function which adds 5 to something:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def generate_five_adder():", - " def _add_five(a):", - " return a+5", - " return _add_five", - "", - "coolfunction = generate_five_adder()", - "coolfunction(7)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nOK, so what happened there? Well, we defined a function **inside** the other function. We can always do that:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def thirty_function():", - " def times_three(a):", - " return a*3", - " def add_seven(a):", - " return a+7", - " return times_three(add_seven(3))", - "", - "thirty_function()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWhen we do this, the functions enclosed inside the outer function are **local** functions, and can't be seen outside:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "with assert_raises(NameError):", - " add_seven", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThere's not really much of a difference between functions and other variables\nin python. A function is just a variable which can have () put after it to call\nthe code!\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print thirty_function", - "x=[thirty_function, add_five, add]", - "", - "for fun in x:", - " print fun", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nAnd we know that one of the things we can do with a variable is `return` it. So we can return a function, and then call it outside:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def deferred_greeting():", - " def greet():", - " print \"Hello\"", - " return greet", - "", - "friendlyfunction=deferred_greeting()", - "", - "# Do something else", - "print \"Just passing the time...\"", - "", - "# OK, Go!", - "friendlyfunction()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSo now, to finish this, we just need to return a function to add an arbitrary amount:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def define_adder(increment):", - " def adder(a):", - " return a+increment", - " return adder", - "", - "add_3=define_adder(3)", - "", - "add_3(9)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWe can make this even prettier: let's make another variable pointing to our define_adder() function:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "add = define_adder", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nAnd now we can do the real magic:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "add(8)(5)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Closures" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You may have noticed something a bit weird:\n\nIn the definition of `define_adder`, `increment` is a local variable. It should have gone out of scope and died at the end of the definition. How can the amount the returned adder function is adding still be kept?\n\nThis is called a **closure**. In Python, whenever a function definition references a variable in the surrounding scope, it is preserved within the function definition.\n\nYou can close over global module variables as well:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "name = \"James\"", - "def greet():", - " print \"Hello, \", name", - "", - "greet()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nAnd note that the closure stores a reference to the variable in the surrounding scope: (\"Late Binding\")\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "name=\"Matt\"", - "", - "greet()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Map and Reduce" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We often want to apply a function to each variable in an array, to return a new array. We can do this with a list comprehension:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "numbers=range(10)", - "", - "[add_five(i) for i in numbers]", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nBut this is sufficiently common that there's a quick built-in:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "map(add_five, numbers)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThis **map** operation is really important conceptually when understanding\nefficient parallel programming: different computers can apply the *mapped*\nfunction to their input at the same time. We call this Single Program, Multiple\nData. (SPMD) **map** is half of the **map-reduce** functional programming\nparadigm which is key to the efficient operation of much of today's \"data\nscience\" explosion. \n\nLet's continue our functional programming mind-stretch by looking at **reduce** operations.\n\nWe very often want to loop with some kind of accumulator, such as when finding a mean, or finding a maximum:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def mean(data):", - " sum=0.0", - " for x in data:", - " sum+=x", - " return sum/len(data)", - "", - "mean(range(10))", - "", - "import sys", - "def my_max(data):", - " # Start with the smallest possible number", - " highest=sys.float_info.min", - " for x in data:", - " if x>highest:", - " highest=x", - " return highest", - "", - "my_max([2,5,10,-11,-5])", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThese operations, where we have some variable which is building up a result,\nand the result is updated with some operation, can be gathered together as a\nfunctional program, taking in the operation to be used to combine results as an\nargument:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def accumulate(initial, operation, data):", - " accumulator=initial", - " for x in data:", - " accumulator=operation(accumulator, x)", - " return accumulator", - "", - "def my_sum(data):", - " def _add(a,b):", - " return a+b", - " return accumulate(0, _add, data)", - "", - "print my_sum(range(5))", - "", - "def bigger(a,b):", - " if b>a:", - " return b", - " return a", - "", - "def my_max(data):", - " return accumulate(sys.float_info.min, bigger, data)", - "", - "print my_max([2,5,10,-11,-5])", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nNow, because these operations, _bigger, and _add, are such that e.g. (a+b)+c = a+(b+c) , i.e. they are **associative**, we could apply our accumulation\nto the left half and the right half of the array, each on a different computer, and then combine the two halves:\n\n1+2+3+4=(1+2)+(3+4)\n\nIndeed, with a bigger array, we can divide-and-conquer more times:\n\n1+2+3+4+5+6+7+8=((1+2)+(3+4))+((5+6)+(7+8))\n\nSo with enough parallel computers, we could do this operation on eight numbers\nin three steps: first, we use four computers to do one each of the pairwise\nadds.\n\nThen, we use two computers to add the four totals.\n\nThen, we use one of the computers to do the final add of the two last numbers.\n\nYou might be able to do the maths to see that with an N element list, the\nnumber of such steps is proportional to the logarithm of N.\n\nWe say that with enough computers, reduction operations are O(ln N)\n\nThis course isn't an introduction to algorithms, but we'll talk more about this\nO() notation when we think about programming for performance.\n\nAnyway, this accumulate-under-an-operation process, is so fundamental to\ncomputing that it's usually in standard libraries for languages which allow\nfunctional programming:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from functools import reduce", - "", - "def my_max(data):", - " return reduce(bigger,data,sys.float_info.min)", - "", - "my_max([2,5,10,-11,-5])", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWhen doing functional programming, we often want to be able to define a function on the fly:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def most_Cs_in_any_sequence(sequences):", - " def count_Cs(sequence):", - " return sequence.count('C')", - " counts=map(count_Cs, sequences)", - " return max(counts)", - "", - "def most_Gs_in_any_sequence(sequences):", - " return max(map(lambda sequence: sequence.count('G'),sequences))", - "", - "data=[", - " \"CGTA\",", - " \"CGGGTAAACG\",", - " \"GATTACA\"", - "]", - "", - "most_Gs_in_any_sequence(data)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThe syntax here is that these two definitions are identical:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "func_name=lambda a,b,c : a+b+c", - "", - "def func_name(a,b,c):", - " a+b+c", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nlambda defines an \"anonymous\" function.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def most_of_given_base_in_any_sequence(sequences, base):", - " return max(map(lambda sequence: sequence.count(base), sequences))", - "", - "most_of_given_base_in_any_sequence(data,'A')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThe above fragment defined a lambda function as a **closure** over `base`. If you understood that, you've got it! \n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def my_max(data): return reduce(lambda a,b: a if a>b else b, data,", - " sys.float_info.min)", - "", - "my_max([2,5,10,-11,-5])", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Using functional programming" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nProbably the most common use in research computing for functional programming\nis the application of a numerical method to a function. For example:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "% matplotlib inline" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from scipy.optimize import newton", - "from numpy import linspace, zeros", - "from matplotlib import pyplot as plt", - "solve_me=lambda x: x**2-x", - "", - "print newton(solve_me, 2), newton(solve_me,0.2)", - "", - "xs=linspace(-1,2,50)", - "solved=[xs,map(solve_me,xs),xs,zeros(50)]", - "plt.plot(*solved)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSometimes such tools return another function:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def derivative(func, eps):", - " def _func_derived(x):", - " return (func(x+eps)-func(x))/eps", - " return _func_derived", - "", - "derived=(xs,map(solve_me,xs),xs,map(derivative(solve_me,0.01),xs))", - "plt.plot(*derived)", - "print newton(derivative(solve_me,0.01),0)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\nOf course, coding your own numerical methods is bad:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import scipy.misc", - "", - "def derivative(func):", - " def _func_derived(x):", - " return scipy.misc.derivative(solve_me,x)", - " return _func_derived", - "", - "newton(derivative(solve_me),0)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nIf you've done a moderate amount of calculus, then you'll find similarities\nbetween functional programming in computer science and Functionals in the\ncalculus of variations.\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Iterators and Generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Iterators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe've seen that in Python, anything which can be iterated over is called an iterable:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "for key in baskets:", - " print key.upper()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nSurprisingly often, we want to iterate over something that takes a moderately\nlarge amount of storage to store. For example, our map images in the\ngreen-graph example.\n\nOur solution last time involved making an array of all the maps between London\nand Birmingham. This kept them all in memory *at the same time*: first we\ndownloaded all the maps, then we counted the green pixels in each of them. This\nwould NOT work if we used more points. We could do this with a for loop and an\nappend(), but that's not as elegant as using a **generator**\n\nConsider the basic python `range` function:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print range(10)", - "", - "total=0", - "for x in range(int(1e6)): total+= x", - "", - "print total", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nWhile this was executing, the range() statement allocated a million integers.\nThis is very inefficient. We don't actually need a million integers, just each\ninteger in turn up to a million.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print xrange(3)", - "", - "a=iter(xrange(3))", - "", - "print a", - "", - "print a.next()", - "", - "print a.next()", - "", - "print a.next()", - "", - "with assert_raises(StopIteration):", - " print a.next()", - "", - "total=0", - "for x in xrange(int(1e6)): total+= x", - "", - "print total", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSimilarly:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print baskets.items()", - "", - "print baskets.iteritems()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can make our own iterators by defining classes that implement next() and __iter__() methods: this is the iterator protocol.\n\nFor each of the concepts we've talked about, python defines a protocol, a set of methods a class must implement, in order to be treated as a member of that concept.\n\nFor the iterator protocol, the protocol that defines things that support `for x in y:`, the methods that must be supported are `next()` and `__iter__()`.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class fib_iterator(object):", - " def __init__(self, limit, seed1=1, seed2=1):", - " self.limit=limit", - " self.previous=seed1", - " self.current=seed2", - " def __iter__(self):", - " return self", - " def next(self):", - " (self.previous, self.current)=(", - " self.current, self.previous+self.current)", - " self.limit -=1", - " if self.limit<0: raise StopIteration() # This will be ", - " # explained in a few slides!", - " return self.current", - "", - "x=fib_iterator(5)", - "", - "x.next()", - "", - "x.next()", - "", - "x.next()", - "", - "x.next()", - "", - "for x in fib_iterator(5):", - " print x", - "", - "sum(fib_iterator(5))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nIn fact, if, to be iterated over, a class just wants to behave as if it were some other iterable, you can just implement `__iter__` and return `iter(some_other_iterable)`, without implementing `next`. For example, an image class might want to implement some metadata, but behave just as if it were just a 1-d pixel array when being iterated:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from numpy import array", - "class MyImage(object):", - " def __init__(self, pixels):", - " self.pixels=array(pixels,dtype='uint8')", - " self.channels=self.pixels.shape[2]", - " def __iter__(self):", - " # return an iterator over the pixels", - " # See future NumPy lecture for using reshape", - " return iter(self.pixels.reshape(-1,self.channels))", - " def show(self):", - " plt.imshow(self.pixels, interpolation=\"None\")", - "", - "x=[[[255,255,0],[0,255,0]],[[0,0,255],[255,255,255]]]", - "image=MyImage(x)", - "", - "image.show()", - "", - "print image.channels", - "", - "from webcolors import rgb_to_name", - "for pixel in image:", - " print rgb_to_name(pixel)", - "### iterable_savefig", - "", - "plt.savefig('colors.png')", - "plt.figure()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nTechnically, the **iterator** protocol is to implement both `__iter__` and\n`next`, while the **iterable** protocol is to implement `__iter__` and return\nan **iterator**.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Generators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nThere's a fair amount of \"boiler-plate\" in the above class-based definition of\nan iterable. Python provides another way to specify something\nwhich meets the iterator protocol: generators.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def my_generator():", - " yield 5", - " yield 10", - "", - "x=my_generator()", - "print x.next()", - "print x.next()", - "", - "with assert_raises(StopIteration):", - " print x.next()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nA function which has `yield` statements instead of a `return` statement returns\n**temporarily**. Each call of next() returns control to the function. Where it\nleft off. Control passes back-and-forth between the generator and the caller.\nOur fibonacci example therefore becomes:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def yield_fibs(limit, seed1=1,seed2=1):", - " current=seed1", - " previous=seed2", - " while limit>0:", - " limit-=1", - " current, previous = current+previous, current", - " yield current", - "", - "print sum(yield_fibs(5))", - "", - "plt.plot(list(yield_fibs(20)))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Exceptions" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Exceptions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWhen we learned about testing, we saw that Python complains when things go wrong by raising an \"Exception\" naming a type of error:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "with assert_raises(ZeroDivisionError):", - " 1/0", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nExceptions are objects, forming a class hierarchy. We just raised an instance\nof the ZeroDivisionError class, making the program crash.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import inspect", - "inspect.getmro(ZeroDivisionError)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nSo we can see that a zero division error is a particular kind of Arithmetic Error.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "x=1", - "with assert_raises(TypeError):", - " for y in x: print y", - "", - "inspect.getmro(TypeError)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWhen we were looking at testing, we saw that it is important for code to crash with a meaningful exception type when something is wrong.\nWe raise an Exception with `raise`. Often, we can look for an appropriate exception from the standard set to raise. \n\nHowever, we may want to define our own exceptions. Doing this is as simple as inheriting from Exception:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class MyCustomErrorType(Exception):", - " pass", - "", - "with assert_raises(MyCustomErrorType):", - " raise(MyCustomErrorType(\"Problem\"))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nYou can add custom data to your exception:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class MyCustomErrorType(Exception):", - " def __init__(self, category=None):", - " self.category=category", - " def __str__(self):", - " return \"Error, cateory \" + str(self. category)", - "", - "with assert_raises(MyCustomErrorType):", - " raise(MyCustomErrorType(404))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThe real power of exceptions comes, however, not in letting them crash the program, but in letting your program handle them. We say that an exception has been \"thrown\" and then \"caught\".\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import yaml", - "try:", - " config=yaml.load(open(\"datasource.yaml\"))", - " user=config[\"userid\"]", - " password=config[\"password\"]", - "except IOError:", - " user=\"anonymous\"", - " password=None", - "", - "print user", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nNote that we specify only the error we expect to happen and want to handle. Sometimes you see code that catches everything:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "try:", - " config=yaml.lod(open(\"datasource.yaml\"))", - " user=config[\"userid\"]", - " password=config[\"password\"]", - "except:", - " user=\"anonymous\"", - " password=None", - "", - "print user", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nThere was a mistyped function name there, but we did not notice the error, as the generic except caught it. \nTherefore, we should catch only the error we want.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "with open('datasource2.yaml','w') as outfile:", - " outfile.write('userid: jamespjh\\n')", - " outfile.write('password: secret\\n')", - "", - "with open('datasource3.yaml','w') as outfile:", - " outfile.write('user: jamespjh\\n')", - " outfile.write('password: secret\\n')", - "", - "def read_credentials(source):", - " try:", - " datasource=open(source)", - " config=yaml.load(datasource)", - " user=config[\"userid\"]", - " password=config[\"password\"]", - " datasource.close()", - " except IOError:", - " user=\"anonymous\"", - " password=None", - " return user, password", - "", - "print read_credentials('datasource2.yaml')", - "", - "print read_credentials('datasource.yaml')", - "", - "with assert_raises(KeyError):", - " print read_credentials('datasource3.yaml')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis last code has a flaw: the file was successfully opened, the missing key was noticed, but not explicitly closed. It's normally OK, as python will close the file as soon as it notices there are no longer any references to datasource in memory, after the function exits. But this is not good practice, you should keep a file handle for as short a time as possible.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def read_credentials(source):", - " try:", - " datasource=open(source)", - " config=yaml.load(datasource)", - " user=config[\"userid\"]", - " password=config[\"password\"]", - " except IOError:", - " user=\"anonymous\"", - " password=None", - " finally:", - " datasource.close()", - " return user, password", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThe `finally` clause is executed whether or not an exception occurs.\n\nThe last optional clause of a `try` statement, an `else` clause is called only if an exception is NOT raised. It can be a better place than the `try` clause to put code other than that which you expect to raise the error, and which you do not want to be executed if the error is raised. It is executed in the same circumstances as code put in the end of the `try` block, the only difference is that errors raised during the `else` clause are not caught. Don't worry if this seems useless to you; most languages implementations of try/except don't support such a clause.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def read_credentials(source):", - " try:", - " datasource=open(source)", - " except IOError:", - " user=\"anonymous\"", - " password=None", - " else:", - " config=yaml.load(datasource)", - " user=config[\"userid\"]", - " password=config[\"password\"]", - " finally:", - " datasource.close()", - " return user, password", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nExceptions do not have to be caught close to the part of the program calling\nthem. They can be caught anywhere \"above\" the calling point in\nthe call stack: control can jump arbitrarily far in the program: up to the `except` clause of the \"highest\" containing try statement.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def f4(x):", - " if x==0:", - " return", - " if x==1:", - " raise ArithmeticError()", - " if x==2:", - " raise SyntaxError()", - " if x==3:", - " raise TypeError()", - "", - "def f3(x):", - " try:", - " print \"F3Before\"", - " f4(x)", - " print \"F3After\"", - " except ArithmeticError:", - " print \"F3Except\"", - "", - "def f2(x):", - " try:", - " print \"F2Before\"", - " f3(x)", - " print \"F2After\"", - " except SyntaxError:", - " print \"F2Except\"", - "", - "def f1(x):", - " try:", - " print \"F1Before\"", - " f2(x)", - " print \"F1After\"", - " except TypeError:", - " print \"F1Except\"", - "", - "print \"Example 0\"", - "f1(0)", - "", - "print \"Example 1\"", - "f1(1)", - "", - "print \"Example 2\"", - "f1(2)", - "", - "print \"Example 3\"", - "f1(3)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Design with Exceptions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nNow we know how exceptions work, we need to think about the design implications... How best to use them.\n\nTraditional software design theory will tell you that they should only be used\nto describe and recover from **exceptional** conditions: things going wrong.\nNormal program flow shouldn't use them.\n\nPython's designers take a different view: use of exceptions in normal flow is\nconsidered OK. For example, all iterators raise a `StopIteration` exception to\nindicate the iteration is complete.\n\nA commonly recommended python design pattern is to use exceptions to determine\nwhether an object implments a protocol (concept/interface), rather than testing\non type.\n\nFor example, we might want a function which can be supplied *either* a data\nseries *or* a path to a location on disk where data can be found. We can\nexamine the type of the supplied content:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "import yaml", - "def analysis(source):", - " if type(source)==dict:", - " name=source['modelname']", - " else:", - " content=open(source)", - " source=yaml.load(content)", - " name=source['modelname']", - " print name", - "", - "analysis({'modelname':'Super'})", - "", - "with open('example.yaml','w') as outfile:", - " outfile.write('modelname: brilliant\\n')", - "", - "analysis('example.yaml')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nHowever, we can also use the try-it-and-handle-exceptions approach to this. \n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def analysis(source):", - " try:", - " name=source['modelname']", - " except TypeError:", - " content=open(source)", - " source=yaml.load(content)", - " name=source['modelname']", - " print name", - "", - "analysis('example.yaml')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis approach is more extensible, and **behaves properly if we give it some\nother data-source which responds like a dictionary or string.**\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def analysis(source):", - " try:", - " name=source['modelname']", - " except TypeError:", - " # Source was not a dictionary-like object", - " # Maybe it is a file path", - " try:", - " content=open(source)", - " source=yaml.load(content)", - " name=source['modelname']", - " except IOError:", - " # Maybe it was already raw YAML content", - " source=yaml.load(source)", - " name=source['modelname']", - " print name", - "", - "analysis(\"modelname: Amazing\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSometimes we want to catch an error, partially handle it, perhaps add some\nextra data to the exception, and then re-raise to be caught again further up\nthe call stack. \n\nThe keyword \"`raise`\" with no argument in an `except:` clause will cause the\ncaught error to be re-thrown. Doing this is the only circumstance where it is\nsafe to do except: without catching a specfic type of error.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "try:", - " # Something", - " pass", - "except:", - " # Do this code here if anything goes wrong", - " raise", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nIt can be useful to catch and re-throw an error as you go up the chain, doing any clean-up needed for each layer of a program.\n\nThe error will finally be caught and not re-thrown only at a higher program\nlayer that knows how to recover. This is known as the \"throw low catch high\"\nprinciple.\n\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Context managers and decorators" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Context managers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWe saw that instead of separately `open`ing and `close`ing a file, we can have\nthe file be automatically closed using a context manager:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "with open('example.yaml') as foo:", - " print yaml.load(foo)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nHow could we define our own one of these, if we too have clean-up code we\nalways want to run after a calling function has done its work, or set-up code\nwe want to do first?\n\nWe can define a class that meets an appropriate protocol:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class verbose_context():", - " def __init__(self, name):", - " self.name=name", - " def __enter__(self):", - " print \"Get ready, \", self.name", - " def __exit__(self, exc_type, exc_value, traceback):", - " print \"OK, done\"", - "", - "with verbose_context(\"James\"):", - " print \"Doing it!\"", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nHowever, this is pretty verbose! Again, a generator with `yield` makes for an easier syntax:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from contextlib import contextmanager", - "", - "@contextmanager", - "def verbose_context(name):", - " print \"Get ready for action, \", name", - " yield name.upper()", - " print \"You did it\"", - "", - "with verbose_context(\"James\") as shouty:", - " print \"Doing it, \", shouty", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nAgain, we use `yield` to temporarily return from a function.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Decorators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nWhen doing functional programming, we may often want to define mutator\nfunctions which take in one function and return a new function, such as our\nderivative example earlier.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def repeater(func, count):", - " def _repeated(x):", - " counter=count", - " while counter>0:", - " counter-=1", - " x=func(x)", - " return x", - " return _repeated", - "", - "from math import sqrt", - "", - "fiftyroots=repeater(sqrt,50)", - "", - "print fiftyroots(100)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nIt turns out that, quite often, we want to apply one of these to a function as we're defining a class.\nFor example, we may want to specify that after certain methods are called, data should always be stored:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def reset_required(func):", - " def _with_data_save(self, *args):", - " func(self,*args)", - " self.stored_data.append(self.data)", - " return _with_data_save", - "", - "class SomeClass(object):", - " def __init__(self):", - " self.data=[]", - " self.stored_data=[]", - " def _step1(self, ins):", - " self.data=[x*2 for x in ins]", - " step1=reset_required(_step1)", - "", - "x=SomeClass()", - "", - "x.step1(\"Hello\")", - "print x.data", - "", - "x.step1(\"World\")", - "print x.data", - "", - "print x.stored_data", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nPython provides some \"syntactic sugar\" to make this kind of coding prettier:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def reset_required(func):", - " def _with_data_save(self, *args):", - " func(self,*args)", - " self.stored_data.append(self.data)", - " return _with_data_save", - "", - "class SomeClass(object):", - " def __init__(self):", - " self.data=[]", - " self.stored_data=[]", - " @reset_required", - " def step1(self, ins):", - " self.data=[x*2 for x in ins]", - "", - "x=SomeClass()", - "x.step1(\"Hello\")", - "x.step1(\"World\")", - "print x.stored_data", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAny function which accepts a function as its first argument and returns a function can be used as a **decorator** like this.\n\nMuch of Python's standard functionality is implemented as decorators: we've\nseen @contextmanager, @classmethod and @attribute. The @contextmanager\nmetafunction, for example, takes in an iterator, and yields a class conforming\nto the context manager protocol.\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Testing and functional programming." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nA few weeks ago we saw a test which loaded its test cases from a YAML file and\nasserted each input with each output. This was nice and concise, but had one\nflaw: we had just one test, covering all the fixtures, so we got just one . in\nthe test output when we ran the tests, and if any test failed, the rest were\nnot run. We can do a nicer job with a test **generator**:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def assert_examplar(**fixture):", - " answer=fixture.pop('answer')", - " assert_equal(greet(**fixture), answer)", - "", - "def test_greeter():", - " with open(os.path.join(os.path.dirname(", - " __file__),'fixtures','samples.yaml')", - " ) as fixtures_file:", - " fixtures=yaml.load(fixtures_file)", - " for fixture in fixtures:", - " yield assert_exemplar(**fixture)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe also remember seeing `with assert_raises()` used to do negative testing.\n\nWe can now imagine how this context manager might be implemented:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@contextmanager", - "def my_assert_raises(exception):", - " try:", - " yield", - " except exception:", - " pass", - " else:", - " raise Exception(\"Expected,\", exception, ", - " \" to be raised, nothing was.\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\nIn fact, we can now use an even easier way to define negative tests: by using `@raises` as a **decorator**:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "from nose.tools import raises", - "", - "@raises(TypeError, ValueError)", - "def test_raises_type_error():", - " raise TypeError(\"This test passes\")", - "", - "@raises(Exception)", - "def test_that_fails_by_passing():", - " pass", - "", - "test_raises_type_error()", - "", - "with assert_raises(AssertionError):", - " test_that_fails_by_passing()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nAgain, we can imagine how nose might implement this:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def my_raises(func, exception):", - " def _output(*args):", - " with assert_raises(exception):", - " func(*args)", - " return _output", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Metaprogramming" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Metaprogramming class attributes" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nIn our introductory metaprogramming example, we were working in the root namespace of a module, using the globals() function.\n\nWe want to be able to access the attribute dictionary for other objects to do\nmetaprogramming to, for example, programmatically create class member data or\nvariables.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Boring(object): pass", - "", - "x=Boring()", - "", - "x.name=\"James\"", - "", - "print x.name", - "", - "print x.__dict__", - "", - "print getattr(x,'name')", - "", - "setattr(x,'age',38)", - "", - "print x.age", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThe real power of accessing the attribute dictionary comes when we realise that\nthere is *very little difference* between member data and member functions.\nNow that we know, from our functional programming, that a function is just a\nvariable that can be *called* with `()`, we can set an attribute to a function,\nand\nit becomes a member function!\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "setattr(Boring, 'describe', lambda self: self.name+ \" is \"+str(self.age))", - "", - "", - "print x.describe(), x.describe, Boring.describe", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nNote that we set this as an attribute of the class, so it is available to other instances of `Boring`:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "y=Boring()", - "with assert_raises(AttributeError):", - " y.describe()", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def broken_birth_year(self):", - " import datetime", - " current=datetime.datetime.now().year", - " return current-self.age", - "", - "Boring.birth_year=broken_birth_year", - "", - "print x.birth_year()", - "", - "print x.birth_year", - "", - "print broken_birth_year.__name__", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can access the attribute dictionary for the local namespace inside a\nfunction with locals() but this *cannot safely be written to*. Lack of safe\nprogrammatic creation of local variables is a flaw in Python.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self, name, age, job, children_count):", - " for name,value in locals().iteritems():", - " if name=='self': continue", - " print \"Setting self.\", name, \" to \", value ", - " setattr(self, name, value)", - "", - "me=Person(\"James\", 38, \"Scientific Programmer\", 0)", - "", - "print me.name", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Metaprogramming warning!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\nUse this stuff **sparingly**!\n\nThe above example worked, but it produced Python code which is not particularly understandable.\nRemember, your objective when programming is to produce code which is **descriptive of what it does**.\n\nThe above code is **definitely** less readable, less maintainable and more error prone than:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Person(object):", - " def __init__(self, name, age, job, children_count):", - " self.name=name", - " self.age=age", - " self.job=job", - " self.children_count=children_count", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSometimes, metaprogramming will be **really** helpful in making non-repetitive\ncode, and you should have it in your toolbox, which is why I'm teaching you it.\nBut doing it all the time overcomplicated matters. We've talked a lot about the\nDRY principle, but there is another equally important principle:\n\n> **KISS**: *Keep it simple, Stupid!*\n\nWhenever you write code and you think, \"Gosh, I'm really clever\",you're\nprobably *doing it wrong*. Code should be about clarity, not showing off.\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Operator overloading" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Operator overloading" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe need to use a metaprogramming trick to make this notebook work.\nI want to be able to put explanatory text in between parts of a class definition,\nso I'll define a decorator to help me build up a class definition gradually.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "def extend(class_to_extend):", - " \"\"\" Metaprogramming to allow gradual implementation", - " of class during notebook. Thanks to", - " http://www.ianbicking.org/blog/2007/08/opening-python-classes.html \"\"\"", - " def decorator(extending_class):", - " for name, value in extending_class.__dict__.iteritems():", - " if name in ['__dict__','__module__', '__weakref__', '__doc__']:", - " continue", - " setattr(class_to_extend,name,value)", - " return class_to_extend", - " return decorator", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\nImagine we wanted to make a library to describe some kind of symbolic algebra system:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Term(object):", - " def __init__(self, symbols=[], powers=[], coefficient=1):", - " self.coefficient=coefficient", - " self.data={symbol: exponent for symbol,exponent", - " in zip(symbols, powers)}", - "", - "class Expression(object):", - " def __init__(self, terms):", - " self.terms=terms", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nSo that $5x^2y+7x+2$ might be constructed as:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "first=Term(['x','y'],[2,1],5)", - "second=Term(['x'],[1],7)", - "third=Term([],[],2)", - "result=Expression([first, second, third])", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis is pretty cumbersome.\n\nWhat we'd really like is to have `2x+y` give an appropriate expression.\n\nFirst, we'll define things so that we can construct our terms and expressions in different ways.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Term(object):", - " def __init__(self, *args):", - " lead=args[0]", - " if type(lead)==type(self):", - " # Copy constructor", - " self.data=dict(lead.data)", - " self.coefficient=lead.coefficient", - " elif type(lead)==int:", - " self.from_constant(lead)", - " elif type(lead)==str:", - " self.from_symbol(*args)", - " elif type(lead)==dict:", - " self.from_dictionary(*args)", - " else:", - " self.from_lists(*args)", - " def from_constant(self, constant):", - " self.coefficient=constant", - " self.data={}", - " def from_symbol(self, symbol, coefficient=1, power=1):", - " self.coefficient=coefficient", - " self.data={symbol:power}", - " def from_dictionary(self, data, coefficient=1):", - " self.data=data", - " self.coefficient=coefficient", - " def from_lists(self, symbols=[], powers=[], coefficient=1):", - " self.coefficient=coefficient", - " self.data={symbol: exponent for symbol,exponent", - " in zip(symbols, powers)}", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class Expression(object):", - " def __init__(self, terms=[]):", - " self.terms=list(terms)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe could define add() and multiply() operations on expressions and terms:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Term)", - "class Term(object):", - " def add(self, *others):", - " return Expression((self,)+others)", - " def multiply(self, *others):", - " result_data=dict(self.data)", - " result_coeff=self.coefficient", - " # Convert arguments to Terms first if they are", - " # constants or integers", - " others=map(Term,others)", - " for another in others:", - " for symbol, exponent in another.data.iteritems():", - " if symbol in result_data:", - " result_data[symbol]+=another.data[symbol]", - " else:", - " result_data[symbol]=another.data[symbol]", - " result_coeff*=another.coefficient", - " return Term(result_data,result_coeff)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Expression)", - "class Expression(object):", - " def add(self, *others):", - " result=Expression(self.terms)", - " for another in others:", - " if type(another)==Term:", - " result.terms.append(another)", - " else:", - " result.terms+=another.terms", - " return result", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can now construct the above expression as:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "x=Term('x')", - "y=Term('y')", - "", - "first=Term(5).multiply(Term('x'),Term('x'),Term('y'))", - "second=Term(7).multiply(Term('x'))", - "third=Term(2)", - "expr=first.add(second,third)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis is better, but we still can't write the expression in a 'natural' way.\n\nHowever, we can define what `*` and `+` do when applied to Terms!:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Term)", - "class Term(object):", - " def __add__(self, other):", - " return self.add(other)", - " def __mul__(self, other):", - " return self.multiply(other)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Expression)", - "class Expression(object):", - " def multiply(self, another):", - " # Distributive law left as exercise", - " pass", - " def __add__(self, other):", - " return self.add(other)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "x_plus_y=Term('x')+'y'", - "print x_plus_y.terms[0].data", - "", - "five_x_ysq=Term('x')*5*'y'*'y'", - "print five_x_ysq.data, five_x_ysq.coefficient", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nThis is called operator overloading. We can define what add and multiply mean when applied to our class.\n\nNote that this only works so far if we multiply on the right-hand-side!\nHowever, we can define a multiplication that works backwards, which is used as a fallback if the left multiply raises an error:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Expression)", - "class Expression(object):", - " def __radd__(self, other):", - " return self.__add__(other)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Term)", - "class Term(object):", - " def __rmul__(self, other):", - " return self.__mul__(other)", - " def __radd__(self, other):", - " return self.__add__(other)", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "print 5*Term('x')", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nIt's not easy at the moment to see if these things are working!\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "fivex=5*Term('x')", - "print fivex.data, fivex.coefficient", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can add another operator method `__str__`, which defines what happens if we try to print our class:\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Term)", - "class Term(object):", - " def __str__(self):", - " def symbol_string(symbol, power):", - " if power==1:", - " return symbol", - " else:", - " return symbol+'^'+str(power)", - " symbol_strings=[symbol_string(symbol, power)", - " for symbol, power in self.data.iteritems()]", - " prod='*'.join(symbol_strings)", - " if not prod:", - " return str(self.coefficient)", - " if self.coefficient==1:", - " return prod", - " else:", - " return str(self.coefficient)+'*'+prod", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "@extend(Expression)", - "class Expression(object):", - " def __str__(self):", - " return '+'.join(map(str,self.terms))", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "first=Term(5)*'x'*'x'*'y'", - "second=Term(7)*'x'", - "third=Term(2)", - "expr=first+second+third", - "print expr", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe can add lots more operators to classes. `__eq__` to determine if objects are\nequal. `__getitem__` to apply [1] to your object. Probably the most exciting\none is `__call__`, which allows us to define other classes that *behave like\nfunctions*! We call these callables.\n\n\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "", - "class MyCallable(object):", - " def __call__(self, name):", - " print \"Hello, \", name", - "", - "x=MyCallable()", - "", - "x(\"James\")", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": null - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n\n\nWe've now come full circle in the blurring of the distinction between functions and objects! The full power of functional programming is really remarkable.\n\nIf you want to know more about the topics in this lecture, using a different\nlanguage syntax, I recommend you watch the [Abelson and Sussman](https://www.youtube.com/watch?v=2Op3QLzMgSY)\n\"Structure and Interpretation of Computer Programs\" lectures. These are the Computer Science\nequivalent of the Feynman Lectures!\n" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Exercise" - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Exercise" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Numbers have units. $5\\mathrm{m}^2$ is not $5\\mathrm{J}$. $6\\mathrm{J}$ is the\nsame as $6\\mathrm{kg}\\mathrm{m}^2\\mathrm{s}^{2}$ which is the same as\n$2\\mathrm{N} \\cdot 3\\mathrm{m}$\n\nWrite a python library to implement handling quantities with units, and\nconverting between units, with a github repostiory and a setup.py file, and\nsome unit tests.\n\nYou should define operators for multiply, equality, and add for your class.\n\nYour unit tests should include things like:" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "assert(5*meters == 0.005*kilometers)\nassert((60*seconds).to(minutes).value==1)\nassert((60*seconds).to(minutes).unit==minutes)\nwith assert_raises(IncompatibleUnitsError):\n 5*meters+2*seconds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You don't have to implement every unit! You might want to load your unit definitions from a yaml config file." - ] - } - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tricks to avoid Repeating Yourself." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Avoid Boiler-Plate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Code can often be annoyingly full of \"boiler-plate\" code: characters you don't really want to have to type.\n", + "\n", + "Not only is this tedious, it's also time-consuming and dangerous: unnecessary code is an unnecessary potential place for mistakes.\n", + "\n", + "There are two important phrases in software design that we've spoken of before in this context:\n", + "\n", + "> Once And Only Once\n", + "\n", + "> Don't Repeat Yourself (DRY)\n", + "\n", + "All concepts, ideas, or instructions should be in the program in just one place.\n", + "Every line in the program should say something useful and important.\n", + "\n", + "We refer to code that respects this principle as DRY code.\n", + "\n", + "In this lecture, we'll look at some techniques that can enable us to refactor away repetitive code.\n", + "\n", + "Since in many of these places, the techniques will involve working with\n", + "functions as if they were variables, we'll learn some **functional**\n", + "programming. We'll also learn more about the innards of how Python implements\n", + "classes.\n", + "\n", + "We'll also think about how to write programs that *generate* the more verbose, repetitive program we could otherwise write.\n", + "We call this **metaprogramming**.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metaprogramming Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Consider a bunch of variables, each of which need initialising and incrementing:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "bananas=0\n", + "apples=0\n", + "oranges=0\n", + "bananas+=1\n", + "apples+=1\n", + "oranges+=1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "The right hand side of these assignments doesn't respect the DRY principle. We\n", + "could of course define a variable for our initial value:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "initial_fruit_count=0\n", + "bananas=initial_fruit_count\n", + "apples=initial_fruit_count\n", + "oranges=initial_fruit_count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "However, this is still not as DRY as it could be: what if we wanted to replace\n", + "the assignment with, say, a class constructor and a buy operation:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Basket:\n", + " def __init__(self):\n", + " self.count=0\n", + " def buy(self):\n", + " self.count+=1\n", + "\n", + "bananas=Basket()\n", + "apples=Basket()\n", + "oranges=Basket()\n", + "bananas.buy()\n", + "apples.buy()\n", + "oranges.buy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "We had to make the change in three places. Whenever you see a situation where a\n", + "refactoring or change of design might require you to change the code in\n", + "multiple places, you have an opportunity to make the code DRYer.\n", + "\n", + "In this case, metaprogramming for incrementing these variables would involve\n", + "just a loop over all the variables we want to initialise:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "baskets=[bananas, apples, oranges]\n", + "for basket in baskets: basket.buy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "However, this trick **doesn't** work for initialising a new variable:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from nose.tools import assert_raises\n", + "with assert_raises(NameError):\n", + " baskets=[bananas, apples, oranges, kiwis]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "So can we declare a new variable programmatically? Given a list of the\n", + "**names** of fruit baskets we want, initialise a variable with that name?\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Basket instance at 0x10c505170>" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "basket_names=['bananas', 'apples', 'oranges', 'kiwis']\n", + "\n", + "globals()['apples']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Wow, we can! Every module or class in Python, is, under the hood, a special\n", + "dictionary, storing the values in its **namespace**. So we can create new\n", + "variables by assigning to this dictionary. globals() gives a reference to the\n", + "attribute dictionary for the current module\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "for name in basket_names:\n", + " globals()[name]=Basket()\n", + "\n", + "\n", + "print kiwis.count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "This is **metaprogramming**.\n", + "\n", + "I would NOT recommend using it for an example as trivial as the one above. \n", + "A better, more Pythonic choice here would be to use a data structure to manage your set of fruit baskets:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "baskets={}\n", + "for name in basket_names:\n", + " baskets[name]=Basket()\n", + "\n", + "print baskets['kiwis'].count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Or even, using a dictionary comprehension:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n" + ] + } + ], + "source": [ + "baskets={name : Basket() for name in baskets}\n", + "print baskets['kiwis'].count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Which is the nicest way to do this, I think. Code which feels like\n", + "metaprogramming is needed to make it less repetitive can often instead be DRYed\n", + "up using a refactored data structure, in a way which is cleaner and more easy\n", + "to understand. Nevertheless, metaprogramming is worth knowing. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Functional programming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Functional Programming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Understanding to think in a *functional programming* style is almost as\n", + "important as object orientation for building DRY, clear scientific software,\n", + "and is just as conceptually difficult.\n", + "\n", + "Programs are composed of functions: they take data in (which we call\n", + "*parameters* or *arguments*) and send data out (through `return` statements.)\n", + "\n", + "A conceptual trick which is often used by computer scientists to teach the core\n", + "idea of functional programming is this: to write a program,\n", + "in theory, you only ever need functions with **one** argument, even when you think you need two or more. Why?\n", + "\n", + "Let's define a program to add two numbers:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "11" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add(a,b):\n", + " return a+b\n", + "\n", + "add(5,6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "How could we do this, in a fictional version of Python which only defined functions of one argument?\n", + "In order to understand this, we'll have to understand several of the concepts\n", + "of functional programming. Let's start with a program which just adds five to\n", + "something:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "11" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add_five(a):\n", + " return a+5\n", + "\n", + "add_five(6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "OK, we could define lots of these, one for each number we want to add. But that\n", + "would be infinitely repetitive. So, let's try to metaprogram that: we want a\n", + "function which returns these add_N() functions.\n", + "\n", + "Let's start with the easy case: a function which returns a function which adds 5 to something:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "def generate_five_adder():\n", + " def _add_five(a):\n", + " return a+5\n", + " return _add_five\n", + "\n", + "coolfunction = generate_five_adder()\n", + "coolfunction(7)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "OK, so what happened there? Well, we defined a function **inside** the other function. We can always do that:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "30" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def thirty_function():\n", + " def times_three(a):\n", + " return a*3\n", + " def add_seven(a):\n", + " return a+7\n", + " return times_three(add_seven(3))\n", + "\n", + "thirty_function()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "When we do this, the functions enclosed inside the outer function are **local** functions, and can't be seen outside:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "with assert_raises(NameError):\n", + " add_seven" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "There's not really much of a difference between functions and other variables\n", + "in python. A function is just a variable which can have () put after it to call\n", + "the code!\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "print thirty_function\n", + "x=[thirty_function, add_five, add]\n", + "\n", + "for fun in x:\n", + " print fun" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "And we know that one of the things we can do with a variable is `return` it. So we can return a function, and then call it outside:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Just passing the time...\n", + "Hello\n" + ] + } + ], + "source": [ + "def deferred_greeting():\n", + " def greet():\n", + " print \"Hello\"\n", + " return greet\n", + "\n", + "friendlyfunction=deferred_greeting()\n", + "\n", + "# Do something else\n", + "print \"Just passing the time...\"\n", + "\n", + "# OK, Go!\n", + "friendlyfunction()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "So now, to finish this, we just need to return a function to add an arbitrary amount:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def define_adder(increment):\n", + " def adder(a):\n", + " return a+increment\n", + " return adder\n", + "\n", + "add_3=define_adder(3)\n", + "\n", + "add_3(9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "We can make this even prettier: let's make another variable pointing to our define_adder() function:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "add = define_adder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "And now we can do the real magic:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "add(8)(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Closures" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You may have noticed something a bit weird:\n", + "\n", + "In the definition of `define_adder`, `increment` is a local variable. It should have gone out of scope and died at the end of the definition. How can the amount the returned adder function is adding still be kept?\n", + "\n", + "This is called a **closure**. In Python, whenever a function definition references a variable in the surrounding scope, it is preserved within the function definition.\n", + "\n", + "You can close over global module variables as well:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, James\n" + ] + } + ], + "source": [ + "name = \"James\"\n", + "def greet():\n", + " print \"Hello, \", name\n", + "\n", + "greet()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "And note that the closure stores a reference to the variable in the surrounding scope: (\"Late Binding\")\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, Matt\n" + ] + } + ], + "source": [ + "name=\"Matt\"\n", + "\n", + "greet()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Map and Reduce" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We often want to apply a function to each variable in an array, to return a new array. We can do this with a list comprehension:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "numbers=range(10)\n", + "\n", + "[add_five(i) for i in numbers]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "But this is sufficiently common that there's a quick built-in:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "map(add_five, numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "This **map** operation is really important conceptually when understanding\n", + "efficient parallel programming: different computers can apply the *mapped*\n", + "function to their input at the same time. We call this Single Program, Multiple\n", + "Data. (SPMD) **map** is half of the **map-reduce** functional programming\n", + "paradigm which is key to the efficient operation of much of today's \"data\n", + "science\" explosion. \n", + "\n", + "Let's continue our functional programming mind-stretch by looking at **reduce** operations.\n", + "\n", + "We very often want to loop with some kind of accumulator, such as when finding a mean, or finding a maximum:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def mean(data):\n", + " sum=0.0\n", + " for x in data:\n", + " sum+=x\n", + " return sum/len(data)\n", + "\n", + "mean(range(10))\n", + "\n", + "import sys\n", + "def my_max(data):\n", + " # Start with the smallest possible number\n", + " highest=sys.float_info.min\n", + " for x in data:\n", + " if x>highest:\n", + " highest=x\n", + " return highest\n", + "\n", + "my_max([2,5,10,-11,-5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These operations, where we have some variable which is building up a result,\n", + "and the result is updated with some operation, can be gathered together as a\n", + "functional program, taking in the operation to be used to combine results as an\n", + "argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10\n", + "10\n" + ] + } + ], + "source": [ + "def accumulate(initial, operation, data):\n", + " accumulator=initial\n", + " for x in data:\n", + " accumulator=operation(accumulator, x)\n", + " return accumulator\n", + "\n", + "def my_sum(data):\n", + " def _add(a,b):\n", + " return a+b\n", + " return accumulate(0, _add, data)\n", + "\n", + "print my_sum(range(5))\n", + "\n", + "def bigger(a,b):\n", + " if b>a:\n", + " return b\n", + " return a\n", + "\n", + "def my_max(data):\n", + " return accumulate(sys.float_info.min, bigger, data)\n", + "\n", + "print my_max([2,5,10,-11,-5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Now, because these operations, _bigger, and _add, are such that e.g. (a+b)+c = a+(b+c) , i.e. they are **associative**, we could apply our accumulation\n", + "to the left half and the right half of the array, each on a different computer, and then combine the two halves:\n", + "\n", + "1+2+3+4=(1+2)+(3+4)\n", + "\n", + "Indeed, with a bigger array, we can divide-and-conquer more times:\n", + "\n", + "1+2+3+4+5+6+7+8=((1+2)+(3+4))+((5+6)+(7+8))\n", + "\n", + "So with enough parallel computers, we could do this operation on eight numbers\n", + "in three steps: first, we use four computers to do one each of the pairwise\n", + "adds.\n", + "\n", + "Then, we use two computers to add the four totals.\n", + "\n", + "Then, we use one of the computers to do the final add of the two last numbers.\n", + "\n", + "You might be able to do the maths to see that with an N element list, the\n", + "number of such steps is proportional to the logarithm of N.\n", + "\n", + "We say that with enough computers, reduction operations are O(ln N)\n", + "\n", + "This course isn't an introduction to algorithms, but we'll talk more about this\n", + "O() notation when we think about programming for performance.\n", + "\n", + "Anyway, this accumulate-under-an-operation process, is so fundamental to\n", + "computing that it's usually in standard libraries for languages which allow\n", + "functional programming:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from functools import reduce\n", + "\n", + "def my_max(data):\n", + " return reduce(bigger,data,sys.float_info.min)\n", + "\n", + "my_max([2,5,10,-11,-5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "When doing functional programming, we often want to be able to define a function on the fly:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def most_Cs_in_any_sequence(sequences):\n", + " def count_Cs(sequence):\n", + " return sequence.count('C')\n", + " counts=map(count_Cs, sequences)\n", + " return max(counts)\n", + "\n", + "def most_Gs_in_any_sequence(sequences):\n", + " return max(map(lambda sequence: sequence.count('G'),sequences))\n", + "\n", + "data=[\n", + " \"CGTA\",\n", + " \"CGGGTAAACG\",\n", + " \"GATTACA\"\n", + "]\n", + "\n", + "most_Gs_in_any_sequence(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "The syntax here is that these two definitions are identical:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "func_name=lambda a,b,c : a+b+c\n", + "\n", + "def func_name(a,b,c):\n", + " a+b+c" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "lambda defines an \"anonymous\" function.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def most_of_given_base_in_any_sequence(sequences, base):\n", + " return max(map(lambda sequence: sequence.count(base), sequences))\n", + "\n", + "most_of_given_base_in_any_sequence(data,'A')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "The above fragment defined a lambda function as a **closure** over `base`. If you understood that, you've got it! \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def my_max(data): return reduce(lambda a,b: a if a>b else b, data,\n", + " sys.float_info.min)\n", + "\n", + "my_max([2,5,10,-11,-5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using functional programming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Probably the most common use in research computing for functional programming\n", + "is the application of a numerical method to a function. For example:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "% matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.0 -3.44190514264e-21\n" + ] + }, + { + "data": { + "text/plain": [ + "[,\n", + " ]" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from scipy.optimize import newton\n", + "from numpy import linspace, zeros\n", + "from matplotlib import pyplot as plt\n", + "solve_me=lambda x: x**2-x\n", + "\n", + "print newton(solve_me, 2), newton(solve_me,0.2)\n", + "\n", + "xs=linspace(-1,2,50)\n", + "solved=[xs,map(solve_me,xs),xs,zeros(50)]\n", + "plt.plot(*solved)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Sometimes such tools return another function:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.495\n" + ] + } + ], + "source": [ + "def derivative(func, eps):\n", + " def _func_derived(x):\n", + " return (func(x+eps)-func(x))/eps\n", + " return _func_derived\n", + "\n", + "derived=(xs,map(solve_me,xs),xs,map(derivative(solve_me,0.01),xs))\n", + "plt.plot(*derived)\n", + "print newton(derivative(solve_me,0.01),0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, coding your own numerical methods is bad:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.5" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import scipy.misc\n", + "\n", + "def derivative(func):\n", + " def _func_derived(x):\n", + " return scipy.misc.derivative(solve_me,x)\n", + " return _func_derived\n", + "\n", + "newton(derivative(solve_me),0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "If you've done a moderate amount of calculus, then you'll find similarities\n", + "between functional programming in computer science and Functionals in the\n", + "calculus of variations.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Iterators and Generators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Iterators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We've seen that in Python, anything which can be iterated over is called an iterable:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KIWIS\n", + "BANANAS\n", + "ORANGES\n", + "APPLES\n" + ] + } + ], + "source": [ + "for key in baskets:\n", + " print key.upper()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Surprisingly often, we want to iterate over something that takes a moderately\n", + "large amount of storage to store. For example, our map images in the\n", + "green-graph example.\n", + "\n", + "Our solution last time involved making an array of all the maps between London\n", + "and Birmingham. This kept them all in memory *at the same time*: first we\n", + "downloaded all the maps, then we counted the green pixels in each of them. This\n", + "would NOT work if we used more points. We could do this with a for loop and an\n", + "append(), but that's not as elegant as using a **generator**\n", + "\n", + "Consider the basic python `range` function:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" + ] + } + ], + "source": [ + "print range(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "499999500000" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total=0\n", + "for x in range(int(1e6)): total+= x\n", + "\n", + "total" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "While this was executing, the range() statement allocated a million integers.\n", + "This is very inefficient. We don't actually need a million integers, just each\n", + "integer in turn up to a million.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xrange(3)\n" + ] + } + ], + "source": [ + "print xrange(3)\n", + "\n", + "a=iter(xrange(3))" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with assert_raises(StopIteration):\n", + " print a.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "499999500000\n" + ] + } + ], + "source": [ + "with assert_raises(StopIteration):\n", + " print a.next()\n", + "\n", + "total=0\n", + "for x in xrange(int(1e6)): total+= x\n", + "\n", + "print total" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly:" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[('kiwis', <__main__.Basket instance at 0x10cc750e0>),\n", + " ('bananas', <__main__.Basket instance at 0x10cc75128>),\n", + " ('oranges', <__main__.Basket instance at 0x10cc75170>),\n", + " ('apples', <__main__.Basket instance at 0x10cc751b8>)]" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "baskets.items()" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "baskets.iteritems()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can make our own iterators by defining classes that implement next() and __iter__() methods: this is the iterator protocol.\n", + "\n", + "For each of the concepts we've talked about, python defines a protocol, a set of methods a class must implement, in order to be treated as a member of that concept.\n", + "\n", + "For the iterator protocol, the protocol that defines things that support `for x in y:`, the methods that must be supported are `next()` and `__iter__()`.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class fib_iterator(object):\n", + " def __init__(self, limit, seed1=1, seed2=1):\n", + " self.limit=limit\n", + " self.previous=seed1\n", + " self.current=seed2\n", + " def __iter__(self):\n", + " return self\n", + " def next(self):\n", + " (self.previous, self.current)=(\n", + " self.current, self.previous+self.current)\n", + " self.limit -=1\n", + " if self.limit<0: raise StopIteration() # This will be \n", + " # explained in a few slides!\n", + " return self.current" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "x=fib_iterator(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.next()" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "3\n", + "5\n", + "8\n", + "13\n" + ] + } + ], + "source": [ + "for x in fib_iterator(5):\n", + " print x" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "31" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(fib_iterator(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "In fact, if, to be iterated over, a class just wants to behave as if it were some other iterable, you can just implement `__iter__` and return `iter(some_other_iterable)`, without implementing `next`. For example, an image class might want to implement some metadata, but behave just as if it were just a 1-d pixel array when being iterated:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from numpy import array\n", + "class MyImage(object):\n", + " def __init__(self, pixels):\n", + " self.pixels=array(pixels,dtype='uint8')\n", + " self.channels=self.pixels.shape[2]\n", + " def __iter__(self):\n", + " # return an iterator over the pixels\n", + " # See future NumPy lecture for using reshape\n", + " return iter(self.pixels.reshape(-1,self.channels))\n", + " def show(self):\n", + " plt.imshow(self.pixels, interpolation=\"None\")\n", + "\n", + "x=[[[255,255,0],[0,255,0]],[[0,0,255],[255,255,255]]]\n", + "image=MyImage(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nkiRJ7fofJWP8/5vy7U4AAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "image.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image.channels" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "yellow\n", + "lime\n", + "blue\n", + "white\n" + ] + } + ], + "source": [ + "from webcolors import rgb_to_name\n", + "for pixel in image:\n", + " print rgb_to_name(pixel)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Technically, the **iterator** protocol is to implement both `__iter__` and\n", + "`next`, while the **iterable** protocol is to implement `__iter__` and return\n", + "an **iterator**.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "There's a fair amount of \"boiler-plate\" in the above class-based definition of\n", + "an iterable. Python provides another way to specify something\n", + "which meets the iterator protocol: generators.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "10\n" + ] + } + ], + "source": [ + "def my_generator():\n", + " yield 5\n", + " yield 10\n", + "\n", + "x=my_generator()\n", + "print x.next()\n", + "print x.next()\n", + "\n", + "with assert_raises(StopIteration):\n", + " print x.next()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "A function which has `yield` statements instead of a `return` statement returns\n", + "**temporarily**. Each call of next() returns control to the function. Where it\n", + "left off. Control passes back-and-forth between the generator and the caller.\n", + "Our fibonacci example therefore becomes:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def yield_fibs(limit, seed1=1,seed2=1):\n", + " current=seed1\n", + " previous=seed2\n", + " while limit>0:\n", + " limit-=1\n", + " current, previous = current+previous, current\n", + " yield current" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "31\n" + ] + } + ], + "source": [ + "print sum(yield_fibs(5))" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(list(yield_fibs(20)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exceptions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exceptions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "When we learned about testing, we saw that Python complains when things go wrong by raising an \"Exception\" naming a type of error:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "with assert_raises(ZeroDivisionError):\n", + " 1/0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Exceptions are objects, forming a class hierarchy. We just raised an instance\n", + "of the ZeroDivisionError class, making the program crash.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(ZeroDivisionError,\n", + " ArithmeticError,\n", + " StandardError,\n", + " Exception,\n", + " BaseException,\n", + " object)" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import inspect\n", + "inspect.getmro(ZeroDivisionError)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "So we can see that a zero division error is a particular kind of Arithmetic Error.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(TypeError, StandardError, Exception, BaseException, object)" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x=1\n", + "with assert_raises(TypeError):\n", + " for y in x: print y\n", + "\n", + "inspect.getmro(TypeError)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "When we were looking at testing, we saw that it is important for code to crash with a meaningful exception type when something is wrong.\n", + "We raise an Exception with `raise`. Often, we can look for an appropriate exception from the standard set to raise. \n", + "\n", + "However, we may want to define our own exceptions. Doing this is as simple as inheriting from Exception:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class MyCustomErrorType(Exception):\n", + " pass\n", + "\n", + "with assert_raises(MyCustomErrorType):\n", + " raise(MyCustomErrorType(\"Problem\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "You can add custom data to your exception:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class MyCustomErrorType(Exception):\n", + " def __init__(self, category=None):\n", + " self.category=category\n", + " def __str__(self):\n", + " return \"Error, cateory \" + str(self. category)\n", + "\n", + "with assert_raises(MyCustomErrorType):\n", + " raise(MyCustomErrorType(404))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "The real power of exceptions comes, however, not in letting them crash the program, but in letting your program handle them. We say that an exception has been \"thrown\" and then \"caught\".\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "anonymous\n" + ] + } + ], + "source": [ + "import yaml\n", + "try:\n", + " config=yaml.load(open(\"datasource.yaml\"))\n", + " user=config[\"userid\"]\n", + " password=config[\"password\"]\n", + "except IOError:\n", + " user=\"anonymous\"\n", + " password=None\n", + "\n", + "print user" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Note that we specify only the error we expect to happen and want to handle. Sometimes you see code that catches everything:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "anonymous\n" + ] + } + ], + "source": [ + "try:\n", + " config=yaml.lod(open(\"datasource.yaml\"))\n", + " user=config[\"userid\"]\n", + " password=config[\"password\"]\n", + "except:\n", + " user=\"anonymous\"\n", + " password=None\n", + "\n", + "print user" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "There was a mistyped function name there, but we did not notice the error, as the generic except caught it. \n", + "Therefore, we should catch only the error we want.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "('jamespjh', 'secret')\n", + "('anonymous', None)\n" + ] + } + ], + "source": [ + "with open('datasource2.yaml','w') as outfile:\n", + " outfile.write('userid: jamespjh\\n')\n", + " outfile.write('password: secret\\n')\n", + "\n", + "with open('datasource3.yaml','w') as outfile:\n", + " outfile.write('user: jamespjh\\n')\n", + " outfile.write('password: secret\\n')\n", + "\n", + "def read_credentials(source):\n", + " try:\n", + " datasource=open(source)\n", + " config=yaml.load(datasource)\n", + " user=config[\"userid\"]\n", + " password=config[\"password\"]\n", + " datasource.close()\n", + " except IOError:\n", + " user=\"anonymous\"\n", + " password=None\n", + " return user, password\n", + "\n", + "print read_credentials('datasource2.yaml')\n", + "\n", + "print read_credentials('datasource.yaml')\n", + "\n", + "with assert_raises(KeyError):\n", + " print read_credentials('datasource3.yaml')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This last code has a flaw: the file was successfully opened, the missing key was noticed, but not explicitly closed. It's normally OK, as python will close the file as soon as it notices there are no longer any references to datasource in memory, after the function exits. But this is not good practice, you should keep a file handle for as short a time as possible.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def read_credentials(source):\n", + " try:\n", + " datasource=open(source)\n", + " config=yaml.load(datasource)\n", + " user=config[\"userid\"]\n", + " password=config[\"password\"]\n", + " except IOError:\n", + " user=\"anonymous\"\n", + " password=None\n", + " finally:\n", + " datasource.close()\n", + " return user, password" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "The `finally` clause is executed whether or not an exception occurs.\n", + "\n", + "The last optional clause of a `try` statement, an `else` clause is called only if an exception is NOT raised. It can be a better place than the `try` clause to put code other than that which you expect to raise the error, and which you do not want to be executed if the error is raised. It is executed in the same circumstances as code put in the end of the `try` block, the only difference is that errors raised during the `else` clause are not caught. Don't worry if this seems useless to you; most languages implementations of try/except don't support such a clause.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def read_credentials(source):\n", + " try:\n", + " datasource=open(source)\n", + " except IOError:\n", + " user=\"anonymous\"\n", + " password=None\n", + " else:\n", + " config=yaml.load(datasource)\n", + " user=config[\"userid\"]\n", + " password=config[\"password\"]\n", + " finally:\n", + " datasource.close()\n", + " return user, password" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Exceptions do not have to be caught close to the part of the program calling\n", + "them. They can be caught anywhere \"above\" the calling point in\n", + "the call stack: control can jump arbitrarily far in the program: up to the `except` clause of the \"highest\" containing try statement.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f4(x):\n", + " if x==0:\n", + " return\n", + " if x==1:\n", + " raise ArithmeticError()\n", + " if x==2:\n", + " raise SyntaxError()\n", + " if x==3:\n", + " raise TypeError()" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f3(x):\n", + " try:\n", + " print \"F3Before\"\n", + " f4(x)\n", + " print \"F3After\"\n", + " except ArithmeticError:\n", + " print \"F3Except\"" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f2(x):\n", + " try:\n", + " print \"F2Before\"\n", + " f3(x)\n", + " print \"F2After\"\n", + " except SyntaxError:\n", + " print \"F2Except\"" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def f1(x):\n", + " try:\n", + " print \"F1Before\"\n", + " f2(x)\n", + " print \"F1After\"\n", + " except TypeError:\n", + " print \"F1Except\"" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1Before\n", + "F2Before\n", + "F3Before\n", + "F3After\n", + "F2After\n", + "F1After\n" + ] + } + ], + "source": [ + "f1(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1Before\n", + "F2Before\n", + "F3Before\n", + "F3Except\n", + "F2After\n", + "F1After\n" + ] + } + ], + "source": [ + "f1(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1Before\n", + "F2Before\n", + "F3Before\n", + "F2Except\n", + "F1After\n" + ] + } + ], + "source": [ + "f1(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "F1Before\n", + "F2Before\n", + "F3Before\n", + "F1Except\n" + ] + } + ], + "source": [ + "f1(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design with Exceptions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now we know how exceptions work, we need to think about the design implications... How best to use them.\n", + "\n", + "Traditional software design theory will tell you that they should only be used\n", + "to describe and recover from **exceptional** conditions: things going wrong.\n", + "Normal program flow shouldn't use them.\n", + "\n", + "Python's designers take a different view: use of exceptions in normal flow is\n", + "considered OK. For example, all iterators raise a `StopIteration` exception to\n", + "indicate the iteration is complete.\n", + "\n", + "A commonly recommended python design pattern is to use exceptions to determine\n", + "whether an object implments a protocol (concept/interface), rather than testing\n", + "on type.\n", + "\n", + "For example, we might want a function which can be supplied *either* a data\n", + "series *or* a path to a location on disk where data can be found. We can\n", + "examine the type of the supplied content:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import yaml\n", + "def analysis(source):\n", + " if type(source)==dict:\n", + " name=source['modelname']\n", + " else:\n", + " content=open(source)\n", + " source=yaml.load(content)\n", + " name=source['modelname']\n", + " print name" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Super\n" + ] + } + ], + "source": [ + "analysis({'modelname':'Super'})" + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with open('example.yaml','w') as outfile:\n", + " outfile.write('modelname: brilliant\\n')" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "brilliant\n" + ] + } + ], + "source": [ + "analysis('example.yaml')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "However, we can also use the try-it-and-handle-exceptions approach to this. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "brilliant\n" + ] + } + ], + "source": [ + "def analysis(source):\n", + " try:\n", + " name=source['modelname']\n", + " except TypeError:\n", + " content=open(source)\n", + " source=yaml.load(content)\n", + " name=source['modelname']\n", + " print name\n", + "\n", + "analysis('example.yaml')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This approach is more extensible, and **behaves properly if we give it some\n", + "other data-source which responds like a dictionary or string.**" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Amazing\n" + ] + } + ], + "source": [ + "def analysis(source):\n", + " try:\n", + " name=source['modelname']\n", + " except TypeError:\n", + " # Source was not a dictionary-like object\n", + " # Maybe it is a file path\n", + " try:\n", + " content=open(source)\n", + " source=yaml.load(content)\n", + " name=source['modelname']\n", + " except IOError:\n", + " # Maybe it was already raw YAML content\n", + " source=yaml.load(source)\n", + " name=source['modelname']\n", + " print name\n", + "\n", + "analysis(\"modelname: Amazing\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Sometimes we want to catch an error, partially handle it, perhaps add some\n", + "extra data to the exception, and then re-raise to be caught again further up\n", + "the call stack. \n", + "\n", + "The keyword \"`raise`\" with no argument in an `except:` clause will cause the\n", + "caught error to be re-thrown. Doing this is the only circumstance where it is\n", + "safe to do except: without catching a specfic type of error.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "try:\n", + " # Something\n", + " pass\n", + "except:\n", + " # Do this code here if anything goes wrong\n", + " raise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "It can be useful to catch and re-throw an error as you go up the chain, doing any clean-up needed for each layer of a program.\n", + "\n", + "The error will finally be caught and not re-thrown only at a higher program\n", + "layer that knows how to recover. This is known as the \"throw low catch high\"\n", + "principle.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Context managers and decorators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Context managers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "We saw that instead of separately `open`ing and `close`ing a file, we can have\n", + "the file be automatically closed using a context manager:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'modelname': 'brilliant'}\n" + ] + } + ], + "source": [ + "with open('example.yaml') as foo:\n", + " print yaml.load(foo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "How could we define our own one of these, if we too have clean-up code we\n", + "always want to run after a calling function has done its work, or set-up code\n", + "we want to do first?\n", + "\n", + "We can define a class that meets an appropriate protocol:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Get ready, James\n", + "Doing it!\n", + "OK, done\n" + ] + } + ], + "source": [ + "class verbose_context():\n", + " def __init__(self, name):\n", + " self.name=name\n", + " def __enter__(self):\n", + " print \"Get ready, \", self.name\n", + " def __exit__(self, exc_type, exc_value, traceback):\n", + " print \"OK, done\"\n", + "\n", + "with verbose_context(\"James\"):\n", + " print \"Doing it!\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "However, this is pretty verbose! Again, a generator with `yield` makes for an easier syntax:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Get ready for action, James\n", + "Doing it, JAMES\n", + "You did it\n" + ] + } + ], + "source": [ + "from contextlib import contextmanager\n", + "\n", + "@contextmanager\n", + "def verbose_context(name):\n", + " print \"Get ready for action, \", name\n", + " yield name.upper()\n", + " print \"You did it\"\n", + "\n", + "with verbose_context(\"James\") as shouty:\n", + " print \"Doing it, \", shouty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Again, we use `yield` to temporarily return from a function.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Decorators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "When doing functional programming, we may often want to define mutator\n", + "functions which take in one function and return a new function, such as our\n", + "derivative example earlier.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.0\n" + ] + } + ], + "source": [ + "def repeater(func, count):\n", + " def _repeated(x):\n", + " counter=count\n", + " while counter>0:\n", + " counter-=1\n", + " x=func(x)\n", + " return x\n", + " return _repeated\n", + "\n", + "from math import sqrt\n", + "\n", + "fiftyroots=repeater(sqrt,50)\n", + "\n", + "print fiftyroots(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "It turns out that, quite often, we want to apply one of these to a function as we're defining a class.\n", + "For example, we may want to specify that after certain methods are called, data should always be stored:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def reset_required(func):\n", + " def _with_data_save(self, *args):\n", + " func(self,*args)\n", + " self.stored_data.append(self.data)\n", + " return _with_data_save\n", + "\n", + "class SomeClass(object):\n", + " def __init__(self):\n", + " self.data=[]\n", + " self.stored_data=[]\n", + " def _step1(self, ins):\n", + " self.data=[x*2 for x in ins]\n", + " step1=reset_required(_step1)" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "x=SomeClass()" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['HH', 'ee', 'll', 'll', 'oo']\n" + ] + } + ], + "source": [ + "x.step1(\"Hello\")\n", + "print x.data" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['WW', 'oo', 'rr', 'll', 'dd']\n" + ] + } + ], + "source": [ + "x.step1(\"World\")\n", + "print x.data" + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[['HH', 'ee', 'll', 'll', 'oo'], ['WW', 'oo', 'rr', 'll', 'dd']]\n" + ] + } + ], + "source": [ + "print x.stored_data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Python provides some \"syntactic sugar\" to make this kind of coding prettier:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def reset_required(func):\n", + " def _with_data_save(self, *args):\n", + " func(self,*args)\n", + " self.stored_data.append(self.data)\n", + " return _with_data_save\n", + "\n", + "class SomeClass(object):\n", + " def __init__(self):\n", + " self.data=[]\n", + " self.stored_data=[]\n", + " @reset_required\n", + " def step1(self, ins):\n", + " self.data=[x*2 for x in ins]\n", + "\n", + "x=SomeClass()\n", + "x.step1(\"Hello\")\n", + "x.step1(\"World\")\n", + "print x.stored_data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Any function which accepts a function as its first argument and returns a function can be used as a **decorator** like this.\n", + "\n", + "Much of Python's standard functionality is implemented as decorators: we've\n", + "seen @contextmanager, @classmethod and @attribute. The @contextmanager\n", + "metafunction, for example, takes in an iterator, and yields a class conforming\n", + "to the context manager protocol.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Testing and functional programming." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "A few weeks ago we saw a test which loaded its test cases from a YAML file and\n", + "asserted each input with each output. This was nice and concise, but had one\n", + "flaw: we had just one test, covering all the fixtures, so we got just one . in\n", + "the test output when we ran the tests, and if any test failed, the rest were\n", + "not run. We can do a nicer job with a test **generator**:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def assert_examplar(**fixture):\n", + " answer=fixture.pop('answer')\n", + " assert_equal(greet(**fixture), answer)\n", + "\n", + "def test_greeter():\n", + " with open(os.path.join(os.path.dirname(\n", + " __file__),'fixtures','samples.yaml')\n", + " ) as fixtures_file:\n", + " fixtures=yaml.load(fixtures_file)\n", + " for fixture in fixtures:\n", + " yield assert_exemplar(**fixture)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also remember seeing `with assert_raises()` used to do negative testing.\n", + "\n", + "We can now imagine how this context manager might be implemented:" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@contextmanager\n", + "def my_assert_raises(exception):\n", + " try:\n", + " yield\n", + " except exception:\n", + " pass\n", + " else:\n", + " raise Exception(\"Expected,\", exception, \n", + " \" to be raised, nothing was.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "In fact, we can now use an even easier way to define negative tests: by using `@raises` as a **decorator**:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from nose.tools import raises\n", + "\n", + "@raises(TypeError, ValueError)\n", + "def test_raises_type_error():\n", + " raise TypeError(\"This test passes\")\n", + "\n", + "@raises(Exception)\n", + "def test_that_fails_by_passing():\n", + " pass\n", + "\n", + "test_raises_type_error()\n", + "\n", + "with assert_raises(AssertionError):\n", + " test_that_fails_by_passing()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Again, we can imagine how nose might implement this:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def my_raises(func, exception):\n", + " def _output(*args):\n", + " with assert_raises(exception):\n", + " func(*args)\n", + " return _output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Metaprogramming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metaprogramming class attributes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "In our introductory metaprogramming example, we were working in the root namespace of a module, using the globals() function.\n", + "\n", + "We want to be able to access the attribute dictionary for other objects to do\n", + "metaprogramming to, for example, programmatically create class member data or\n", + "variables.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Boring(object): \n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "x=Boring()\n", + "\n", + "x.name=\"James\"" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'James'" + ] + }, + "execution_count": 142, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.name" + ] + }, + { + "cell_type": "code", + "execution_count": 143, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'James'}" + ] + }, + "execution_count": 143, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'James'" + ] + }, + "execution_count": 144, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "getattr(x,'name')" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "38" + ] + }, + "execution_count": 145, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "setattr(x,'age',38)\n", + "\n", + "x.age" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "The real power of accessing the attribute dictionary comes when we realise that\n", + "there is *very little difference* between member data and member functions.\n", + "Now that we know, from our functional programming, that a function is just a\n", + "variable that can be *called* with `()`, we can set an attribute to a function,\n", + "and\n", + "it becomes a member function!\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 147, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "setattr(Boring, 'describe', lambda self: self.name+ \" is \"+str(self.age))" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'James is 38'" + ] + }, + "execution_count": 148, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + " of <__main__.Boring object at 0x114f99710>>" + ] + }, + "execution_count": 149, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.describe" + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + ">" + ] + }, + "execution_count": 150, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Boring.describe" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Note that we set this as an attribute of the class, so it is available to other instances of `Boring`:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'Boring' object has no attribute 'name'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mBoring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdescribe\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(self)\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0msetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mBoring\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'describe'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m+\u001b[0m \u001b[0;34m\" is \"\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mage\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m: 'Boring' object has no attribute 'name'" + ] + } + ], + "source": [ + "y=Boring()\n", + "\n", + "y.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def broken_birth_year(self):\n", + " import datetime\n", + " current=datetime.datetime.now().year\n", + " return current-self.age" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "Boring.birth_year=broken_birth_year" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1977" + ] + }, + "execution_count": 155, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.birth_year()" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + ">" + ] + }, + "execution_count": 156, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x.birth_year" + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'broken_birth_year'" + ] + }, + "execution_count": 157, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "broken_birth_year.__name__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can access the attribute dictionary for the local namespace inside a\n", + "function with locals() but this *cannot safely be written to*. Lack of safe\n", + "programmatic creation of local variables is a flaw in Python.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 161, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self, name, age, job, children_count):\n", + " for name,value in locals().iteritems():\n", + " if name=='self': continue\n", + " print \"Setting self.\", name, \" to \", value \n", + " setattr(self, name, value)" + ] + }, + { + "cell_type": "code", + "execution_count": 162, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setting self. job to Scientific Programmer\n", + "Setting self. children_count to 0\n", + "Setting self. age to 38\n", + "Setting self. name to James\n" + ] + } + ], + "source": [ + "me=Person(\"James\", 38, \"Scientific Programmer\", 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 163, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'James'" + ] + }, + "execution_count": 163, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "me.name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metaprogramming warning!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Use this stuff **sparingly**!\n", + "\n", + "The above example worked, but it produced Python code which is not particularly understandable.\n", + "Remember, your objective when programming is to produce code which is **descriptive of what it does**.\n", + "\n", + "The above code is **definitely** less readable, less maintainable and more error prone than:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Person(object):\n", + " def __init__(self, name, age, job, children_count):\n", + " self.name=name\n", + " self.age=age\n", + " self.job=job\n", + " self.children_count=children_count" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "Sometimes, metaprogramming will be **really** helpful in making non-repetitive\n", + "code, and you should have it in your toolbox, which is why I'm teaching you it.\n", + "But doing it all the time overcomplicated matters. We've talked a lot about the\n", + "DRY principle, but there is another equally important principle:\n", + "\n", + "> **KISS**: *Keep it simple, Stupid!*\n", + "\n", + "Whenever you write code and you think, \"Gosh, I'm really clever\",you're\n", + "probably *doing it wrong*. Code should be about clarity, not showing off.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Operator overloading" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Operator overloading" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We need to use a metaprogramming trick to make this notebook work.\n", + "I want to be able to put explanatory text in between parts of a class definition,\n", + "so I'll define a decorator to help me build up a class definition gradually.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def extend(class_to_extend):\n", + " \"\"\" Metaprogramming to allow gradual implementation\n", + " of class during notebook. Thanks to\n", + " http://www.ianbicking.org/blog/2007/08/opening-python-classes.html \"\"\"\n", + " def decorator(extending_class):\n", + " for name, value in extending_class.__dict__.iteritems():\n", + " if name in ['__dict__','__module__', '__weakref__', '__doc__']:\n", + " continue\n", + " setattr(class_to_extend,name,value)\n", + " return class_to_extend\n", + " return decorator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "Imagine we wanted to make a library to describe some kind of symbolic algebra system:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Term(object):\n", + " def __init__(self, symbols=[], powers=[], coefficient=1):\n", + " self.coefficient=coefficient\n", + " self.data={symbol: exponent for symbol,exponent\n", + " in zip(symbols, powers)}\n", + "\n", + "class Expression(object):\n", + " def __init__(self, terms): self.terms=terms\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "So that $5x^2y+7x+2$ might be constructed as:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 170, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "first=Term(['x','y'],[2,1],5)\n", + "second=Term(['x'],[1],7)\n", + "third=Term([],[],2)\n", + "result=Expression([first, second, third])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This is pretty cumbersome.\n", + "\n", + "What we'd really like is to have `2x+y` give an appropriate expression.\n", + "\n", + "First, we'll define things so that we can construct our terms and expressions in different ways.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "class Term(object):\n", + " def __init__(self, *args):\n", + " lead=args[0]\n", + " if type(lead)==type(self):\n", + " # Copy constructor\n", + " self.data=dict(lead.data)\n", + " self.coefficient=lead.coefficient\n", + " elif type(lead)==int:\n", + " self.from_constant(lead)\n", + " elif type(lead)==str:\n", + " self.from_symbol(*args)\n", + " elif type(lead)==dict:\n", + " self.from_dictionary(*args)\n", + " else:\n", + " self.from_lists(*args)\n", + " \n", + " def from_constant(self, constant):\n", + " self.coefficient=constant\n", + " self.data={}\n", + " \n", + " def from_symbol(self, symbol, coefficient=1, power=1):\n", + " self.coefficient=coefficient\n", + " self.data={symbol:power}\n", + " \n", + " def from_dictionary(self, data, coefficient=1):\n", + " self.data=data\n", + " self.coefficient=coefficient\n", + " \n", + " def from_lists(self, symbols=[], powers=[], coefficient=1):\n", + " self.coefficient=coefficient\n", + " self.data={symbol: exponent for symbol,exponent\n", + " in zip(symbols, powers)}" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class Expression(object):\n", + " def __init__(self, terms=[]):\n", + " self.terms=list(terms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We could define add() and multiply() operations on expressions and terms:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "@extend(Term)\n", + "class Term(object):\n", + " def add(self, *others):\n", + " return Expression((self,)+others)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Term)\n", + "class Term(object):\n", + " def multiply(self, *others):\n", + " result_data=dict(self.data)\n", + " result_coeff=self.coefficient\n", + " # Convert arguments to Terms first if they are\n", + " # constants or integers\n", + " others=map(Term,others)\n", + " for another in others:\n", + " for symbol, exponent in another.data.iteritems():\n", + " if symbol in result_data:\n", + " result_data[symbol]+=another.data[symbol]\n", + " else:\n", + " result_data[symbol]=another.data[symbol]\n", + " result_coeff*=another.coefficient\n", + " return Term(result_data,result_coeff)" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Expression)\n", + "class Expression(object):\n", + " def add(self, *others):\n", + " result=Expression(self.terms)\n", + " for another in others:\n", + " if type(another)==Term:\n", + " result.terms.append(another)\n", + " else:\n", + " result.terms+=another.terms\n", + " return result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can now construct the above expression as:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x=Term('x')\n", + "y=Term('y')\n", + "\n", + "first=Term(5).multiply(Term('x'),Term('x'),Term('y'))\n", + "second=Term(7).multiply(Term('x'))\n", + "third=Term(2)\n", + "expr=first.add(second,third)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This is better, but we still can't write the expression in a 'natural' way.\n", + "\n", + "However, we can define what `*` and `+` do when applied to Terms!:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Term)\n", + "class Term(object):\n", + " def __add__(self, other):\n", + " return self.add(other)\n", + " def __mul__(self, other):\n", + " return self.multiply(other)" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Expression)\n", + "class Expression(object):\n", + " def multiply(self, another):\n", + " # Distributive law left as exercise\n", + " pass\n", + " \n", + " def __add__(self, other):\n", + " return self.add(other)" + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'x': 1}" + ] + }, + "execution_count": 185, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x_plus_y=Term('x')+'y'\n", + "x_plus_y.terms[0].data" + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'y': 2, 'x': 1} 5\n" + ] + } + ], + "source": [ + "five_x_ysq=Term('x')*5*'y'*'y'\n", + "print five_x_ysq.data, five_x_ysq.coefficient" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "This is called operator overloading. We can define what add and multiply mean when applied to our class.\n", + "\n", + "Note that this only works so far if we multiply on the right-hand-side!\n", + "However, we can define a multiplication that works backwards, which is used as a fallback if the left multiply raises an error:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Expression)\n", + "class Expression(object):\n", + " def __radd__(self, other):\n", + " return self.__add__(other)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 188, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Term)\n", + "class Term(object):\n", + " def __rmul__(self, other):\n", + " return self.__mul__(other)\n", + " def __radd__(self, other):\n", + " return self.__add__(other)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<__main__.Term object at 0x114f804d0>\n" + ] + } + ], + "source": [ + "print 5*Term('x')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "It's not easy at the moment to see if these things are working!\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 190, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'x': 1} 5\n" + ] + } + ], + "source": [ + "fivex=5*Term('x')\n", + "print fivex.data, fivex.coefficient" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can add another operator method `__str__`, which defines what happens if we try to print our class:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 192, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Term)\n", + "class Term(object):\n", + " def __str__(self):\n", + " def symbol_string(symbol, power):\n", + " if power==1:\n", + " return symbol\n", + " else:\n", + " return symbol+'^'+str(power)\n", + " \n", + " symbol_strings=[symbol_string(symbol, power)\n", + " for symbol, power in self.data.iteritems()]\n", + " \n", + " prod='*'.join(symbol_strings)\n", + " \n", + " if not prod:\n", + " return str(self.coefficient)\n", + " if self.coefficient==1:\n", + " return prod\n", + " else:\n", + " return str(self.coefficient)+'*'+prod" + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "@extend(Expression)\n", + "class Expression(object):\n", + " def __str__(self):\n", + " return '+'.join(map(str,self.terms))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "first=Term(5)*'x'*'x'*'y'\n", + "second=Term(7)*'x'\n", + "third=Term(2)\n", + "expr=first+second+third" + ] + }, + { + "cell_type": "code", + "execution_count": 195, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5*y*x^2+7*x+2\n" + ] + } + ], + "source": [ + "print expr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We can add lots more operators to classes. `__eq__` to determine if objects are\n", + "equal. `__getitem__` to apply [1] to your object. Probably the most exciting\n", + "one is `__call__`, which allows us to define other classes that *behave like\n", + "functions*! We call these callables.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, James\n" + ] + } + ], + "source": [ + "class MyCallable(object):\n", + " def __call__(self, name):\n", + " print \"Hello, \", name\n", + "\n", + "x=MyCallable()\n", + "\n", + "x(\"James\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "We've now come full circle in the blurring of the distinction between functions and objects! The full power of functional programming is really remarkable.\n", + "\n", + "If you want to know more about the topics in this lecture, using a different\n", + "language syntax, I recommend you watch the [Abelson and Sussman](https://www.youtube.com/watch?v=2Op3QLzMgSY)\n", + "\"Structure and Interpretation of Computer Programs\" lectures. These are the Computer Science\n", + "equivalent of the Feynman Lectures!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numbers have units. $5\\mathrm{m}^2$ is not $5\\mathrm{J}$. \n", + "\n", + "$6\\mathrm{J}$ is the\n", + "same as $6\\mathrm{kg}\\mathrm{m}^2\\mathrm{s}^{-2}$ which is the same as\n", + "$2\\mathrm{N} \\cdot 3\\mathrm{m}$\n", + "\n", + "Write a python library to implement handling quantities with units, and\n", + "converting between units, with a github repostiory and a setup.py file, and\n", + "some unit tests.\n", + "\n", + "You should define operators for multiply, equality, and add for your class.\n", + "\n", + "Your unit tests should include things like:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``` python\n", + "assert(5*meters == 0.005*kilometers)\n", + "assert((60*seconds).to(minutes).value==1)\n", + "assert((60*seconds).to(minutes).unit==minutes)\n", + "with assert_raises(IncompatibleUnitsError):\n", + " 5*meters+2*seconds\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You don't have to implement every unit! You might want to load your unit definitions from a yaml config file." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Session 7" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/015mandels.ipynb b/session08/015mandels.ipynb new file mode 100644 index 00000000..e301a524 --- /dev/null +++ b/session08/015mandels.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xmin=-1.5\n", + "ymin=-1.0\n", + "xmax=0.5\n", + "ymax=1.0\n", + "resolution=300\n", + "xstep=(xmax-xmin)/resolution\n", + "ystep=(ymax-ymin)/resolution\n", + "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", + "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel1(position,limit=50):\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Many Mandelbrots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare our naive python implementation which used a list comprehension, taking 662ms, with the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 673 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data2=[]\n", + "for y in ys:\n", + " row=[]\n", + " for x in xs:\n", + " row.append(mandel1(complex(x,y)))\n", + " data2.append(row)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data2=[]\n", + "for y in ys:\n", + " row=[]\n", + " for x in xs:\n", + " row.append(mandel1(complex(x,y)))\n", + " data2.append(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Interestingly, not much difference. I would have expected this to be slower, due to the normally high cost of **appending** to data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "plt.imshow(data2,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We ought to be checking if these results are the same by comparing the values in a test, rather than re-plotting. This is cumbersome in pure Python, but easy with NumPy, so we'll do this later." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try a pre-allocated data structure:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data3=[[0 for i in range(resolution)] for j in range(resolution)]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 660 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "for j,y in enumerate(ys):\n", + " for i,x in enumerate(xs):\n", + " data3[j][i]=mandel1(complex(x,y))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for j,y in enumerate(ys):\n", + " for i,x in enumerate(xs):\n", + " data3[j][i]=mandel1(complex(x,y))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plt.imshow(data3,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Nope, no gain there. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try using functional programming approaches:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 654 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data4=[]\n", + "for y in ys:\n", + " bind_mandel=lambda x: mandel1(complex(x,y))\n", + " data4.append(map(bind_mandel,xs))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data4=[]\n", + "for y in ys:\n", + " bind_mandel=lambda x: mandel1(complex(x,y))\n", + " data4.append(map(bind_mandel,xs))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plt.imshow(data4,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That was a tiny bit slower." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, what do we learn from this? Our mental image of what code should be faster or slower is often wrong, or doesn't make much difference. The only way to really improve code performance is empirically, through measurements." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Faster Mandelbrots?" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/01intro.ipynb b/session08/01intro.ipynb new file mode 100644 index 00000000..a257f759 --- /dev/null +++ b/session08/01intro.ipynb @@ -0,0 +1,313 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performance programming" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We've spent most of this course looking at how to make code readable and reliable. For research work, it is often also important that code is efficient: that it does what it needs to do *quickly*." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is very hard to work out beforehand whether code will be efficient or not: it is essential to *Profile* code, to measure its performance, to determine what aspects of it are slow." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we looked at Functional programming, we claimed that code which is conceptualised in terms of actions on whole data-sets rather than individual elements is more efficient. Let's measure the performance of some different ways of implementing some code and see how they perform." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Two Mandelbrots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You're probably familiar with a famous fractal called the [Mandelbrot Set](https://www.youtube.com/watch?v=AGUlJus5kpY)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For a complex number $c$, $c$ is in the Mandelbrot set if the series $z_{i+1}=z_{i}^2+c$ (With $z_0=c$) stays close to $0$.\n", + "Traditionally, we plot a color showing how many steps are needed for $\\left|z_i\\right|>2$, whereupon we are sure the series will diverge." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's a trivial python implementation:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel1(position,limit=50):\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xmin=-1.5\n", + "ymin=-1.0\n", + "xmax=0.5\n", + "ymax=1.0\n", + "resolution=300\n", + "xstep=(xmax-xmin)/resolution\n", + "ystep=(ymax-ymin)/resolution\n", + "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", + "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 665 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data=[[mandel1(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQwAAAEACAYAAABGTkjoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXl8U1X6/98nNyCD/kxJGUBhpq1QKPh1CsIogl+LioA4\nwjgyLsC4AO4KI4vi1rRl3AVH3MZRGBcK6uACjgoIo1VBVND2q7JYpK0DCAwtrePg0tyc3x/n3uQm\nTdq0SZq03PfrlSa5uctJeu/nPuc5z3kesLGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbFJAmOA\nbUA5cHOS22JjY5PCaMAOIBPoAJQC/ZPZIBsbm/jgSMA+T0IJRiVQDzwPjE/AcWxsbFqZRAhGT+Bf\nlve7jGU2NjZtnEQIhkzAPm1sbFIAZwL2uRv4heX9L1BWhp8uIA8m4MA2NjZNUgYMbOnGIo4NMXEC\n24EzgT3AR8DFwFbLOvKxBBw4Fv4B/CbZjQhDS9vVLd4NsfACcGEz1u/egmOE26bnkcHvO7uNF+lQ\n8A0UHK82/KB4IKc6N+P90IlYYBi8+4x1+6v15WWC8zKX8JpzO+u9Kxg6qQzeh0M1arXd/w0cZx/h\nXzdFc38nk/0t2CZarlVPLb7uE2FheIHrgdWoEZNFBIuFTYJJpFg0l5aIRTT4xSIMWaIC70Yndw2+\nkdvcC9RCN5CunvdOd9GjuI5XqifDRomYHP1xu9M80WgJ3UisaMRCIgQD4E3jYdOKpJJQQHzFItS6\nCKLa8nof9Li+DtLhtoML4DJgG+yd5EIKwVje4NNNw+ANEG9IdfXXqH2Y1kUooSJhfq9ECkeqikYi\nnJ5tkr7JbkAEom1Xa4rF8a14LGhCLAxGHBWyoBrYCs8PGcdP5wrOFm9yzJJaPnloOLyGutotYpEI\nYv2dUu0GALZg+GnrgtGa/E8U68RiXbRk2xH/z/KmJvB84aaVfOL6FZ+sGQ7lIMqlij+2rkdk6yKW\ndkbzOzVFqomGLRhtmG6WRyoRT7EIZ1008F8YvokGuGHvkDQyqUTsl8qSiCAMjflEwrXLujxRfhqT\nVPr/2oJhE1cS7bdo6sKmOwEHZ38oI5dKMiEnjg1LAqkiGrZgtEFS0aqAVnRyWklvuEgi0OZtZ9mo\n8YzVRrCWkawenJeY4xsk2sqA1PifJ2qUxCZBpMJJE45YL5hotg/bFQFlUZwKXAq8BgKJ/mw/2AQT\n5at4+vnwFjsbiksz/RZNDakeDqMntoXRhkhVsYiVpvwWnd0hYpFO4OLvj78bsu24DBhuLBsOe4e4\nEFMlu7alI9KlEpZste2c4qIGfo9QQWqulRHp+7QnbMFoA6RqF8SkVboi6QQLhbGs/jGQNwoYDmnU\nUp8NZMOfsmZxNm9CDrxDHtqTemD7y+C+hZ6wDW/SRxIliRSNZJ4LtmCkOKkuFLGOiETa3rQqOv8J\nrqhcqKyB0Ec36HA78IHaxl33LT/vuIfZmfPIdxxF6WlDkbOLmNjvVeT5gkcnT2Hb5Aw+yByIqDFC\nxk0HaZhjQ8utDPP7JYpknRe2YKQwqSwWsdLYXBHrHBH+AX9dOiPQ9QizA1EtoQY61kje7HQO8zUv\nUgLvFQESdhQinxDcoP2CW7iLId+W8mjBlOAuSaj1QnysjUQOuybj/EjE5LNoSLnJZ6lEqgtFIhyc\nPY8MEQpQF3R/cDzuIdN7ATuLj4eNlo0s650+4w0qZBZfa28jZTi3o2Cp/JRBlJKzpgrWE4jLsK5u\nOkL7AzcCV8Kh8siT0ZpLIhyizXGCxjr5zLYwUoz2LBaR7rZBZn82SihM5yXg3VjEGeKfMIxgX4Yb\nthVkQH/4Z8U5fCBO4UZvpKEPySRtFKXhZnZ3tzzM42YDBwNtsLaxtaNYm6I1zxlbMGyiJpF98s6m\nL+EmdaD6P4EcJ9BO0RHpsFjrEbgvGj6MQTM2UCoGUn8yeN2SOlwIR+T8TcXeNYzgHfXGGC2xdkuk\nW6D9n66GZ4cbC/fFzxFq0pZHUWzBSAFSNcTbJJZ+uPXmHUrPIy1dESMyk3TYuDSXDgeB/aD/XEP7\nQGePPk11IS4z1usPN8l7uVgM4gj33+nYpYABxRXMd3gbHkgIWOKhmq701GawbVQGjks9/DRTcN7D\nxUo8smH1I6ex/t0T+WZ0GqwEniYoViNeVkZTv0tLaK1zx/ZhpACpKhQm8UqAE0qQk9PwRzz/6Tgu\nWrMykEElHRzv+njjr6eTSaUK8wYGUsqxjj8q56aJEAQvCP5MjM+HFYUU+8qYVPkCWzL70m/N14it\napufLhP8rPZ79H93UmKxlQZT3+Ply7AST79GU/6MWH0YtmAkmVQWi1isiqaw3q2DnJ0DgC6W924Q\nfyxAOCSzdA0XdWzmRK6UT3KvvJkSbSPRI0BIRMWtyOPuJkv/PTs3Ha8q6AB7J7v4lEGkyVpOWVgK\nH6Ku5vLwggHxvdjjsS9bMNopqSwU0HyxaM76EVPtQcCvYERvymzBFdMXssh9HdQVqhTTjVkS0XJ/\nAaJG8sZdp1OLixGUUCEzOdU5Hv3DIrKGbKFi0wA2Ds5laEZZg+nvqSwcjYmGPUpiE1ea269u7vrN\nCoSqUfNC5ok7KK7+XSAffaxiAXBTIfI0wdh+bzNRG8RyJjDcOZ7XvSVQAzsXHs+jg6cwtKAs9mM1\nk1R2itoWRivTniyL5p7YkcSiMQuDdJCXCrQ++XHRiSCEMERIIgQU6d9xe+V8FaOxETWsuhd/wp3W\ntDJi3V8kK8O2MNoQqSoW0Xrsuzdj3VCiEgsrlqS9Yofkx4OFFMs43+2lxFpG56a6BcgvhRKIGpRY\nNGNGa7yjOmPZX6LONVswWolUFYtoiPVCaMl8jNUP58FlsHp6Hrhh09G5THbkxtCKxhBws4eOOyRi\nW/RmTKTvFe8uRSqJhi0YrUCqikW0VkUstHTy1ujCEurdUE4f6rMhTdQhP/TE2JrwiGn5FN91XpPW\nRDhrqDHRiLe1kQrYPowEkspCEcvn0RCNUITtjhgBXPWPw46jMxmgXYbYeQsy6y51tiagEKcQsFDf\nxfniJYbJDXztfJtx3jReGTmpQVbxSMmCQ/0Z4UhW3IbVn5GKhYxsSE2xiEYIki4WRth3p3t19Cs0\n9B2FaMcZKpGgqr1Sdmd6Vw99qncgEUi5n5dvuD7ux4lXRq7WKKYUCbtLkgBssWgB1qnlNeAd4ISV\nRlmAhJf33oc8WMRyOYGvu/Zjpq4F8mWEEMlJ25zvHY/uSnO2j+f5aHdJ4kyqiUWihaIlAhExNyc0\nGFLFDdtmZFBBFmNXvQ1nF7SwpU2zRT5D/yWVyHKhhMq8jZeHXz+W7kk0RGNFNMfS2I89rJpSpJJY\nNGeYtDmYE8bMR3NokJsTwmb9Bvy+A4lgwOYK1skzYWxhM1sbHUJMZKmvlP5rKqFaBYsFXYkR2hjv\nWayhRDOE3drOUFsw4kSqiUVz1wkVgkiPuBEmw1XYYkSA/HUR85feFp8Iz7As5Sv6oI3JV20KVzqx\nEdGIV/LgxmhMOFpTNGLtklQC3wI6UA+chPq3vwBkGJ9fANSGbNduuiSpIhTRnjTW9Ro7sYMugmwa\nrUEazjSP6NQMR+i63S3L0oHhak7JI5lTme7oGbHNsSAcIN7IR9+gNczEFfr9GqnF2lQ0aEuwzuq1\nToIL7Y5E0z2ZoJ6S1iWRwAhgEEosAOYCb6HKgq4z3rdLUkEsmtOtiEYswqb0f5JAFqpGtrE+GhBJ\nLBprpMl+QEpmOBMgFgJEtge9vBDvtpBBQ7Mt4bpRoQ+DcNZGSy0O67adjdornf+kXvc8suH/vjUs\njXh0SULVahzwjPH6GeC3cTiGTRhaeoKYSWs6u6FztuURLimuG54/bjzbijP8CW6aRbiuBwRn/25s\nW1B39GqQvmYeOxqkgDwVixF2ZCSSaIRyKv4sXvHopoSd/r8VdbWlB++/NUUj1i7JTqAO1SV5AnUv\nOkggo4FAGXRdQrZrs12SZFsVsU47N8VCXi7Q+uqkTfiGmlk9AwlruqOyWj1tvHeDnCUQSOQKgXjN\nuKispnc1EUVh49pchq4pg9cAAdsWZpBTUQW3h1/fjzURcDrqh3eDwMfME+9kgXYbEKMTVAjEknx8\nfR3q+5gJc8zvZMVcHsHuH7R2A4+La0iTteScWNVgHy2pDm8SlC9kPNT/0SivsBLK/xVYb1/IcziS\n3SUZjuqOnA1cB/xvyOfBs3tsWkS80rlZ71qiXKJLjeo9vQLp8brjT1dnPmS2YFtWBprjB4SI8K+M\nZEEACNg4Olfl6LxEcLzzAerD1Rgxc2xaLZIwd/U9Q9yMdKxjnP58s757OASS3RNDDhLG6pHZgvOK\niwNDvqG39O7wycPDqSCTnLeq1G8ZUu+kpSMqQdu5gQPQ4SNgploUznJJpJURa6TnN8bzv4FXUH6M\nfUAP1Fy/Y4gw0/Yfltd9jUeqkwzrIpEZqgUSnoGNBbkMrTCsgIOwN8tF9z51aJd6cH1/Fd9qf8H7\n5s/UXdYMM3QTee6FeZJ3h5OLy3h+0nieSpvG5l8P5n3vNXSoJGg0QmYL3Fftpq7zE/h2FMIGY/tu\nIftMV23OooKrHX9lJc0rsGz54hTrpWRRqX4Dy/6BQKV38/uNg1IxCHkuiNcI/A7mdkOBn0smaQOp\n0dO5butiOBe4giArw+q0jIYGXURQIu4WOD/woqMFrW+NADVffw58Ef0hmyQWwegMaMB/gCOBUSgb\ncSUq7/K9xvOr4Tb+TQwHbm1aUyjicXeIah9b8Z9dJy38DMc+nc/v7MO34mgAKv8gcF1wFXV70/G9\nWRToskR7pzStlWGCiategbOLYNMpPDVoGsP7HMD3TKE6+aVAuzQfWdADJDj6+PjxoIOTjt5AqWM1\nQkCGfiFVzudZ6N3NdK0XufpoBsvNsOl66FoP73WAPxQ03aa5BXC+JHfIh0x2AGIgC727YAhUygwe\nPNnL595nGVBcwcAZHzBBLufWTQ/i6O0BnGh42KP/mR4L6wJi4gZfX8GcIfOg961cf4VA/lVwfeUi\n9RuEdHHCWRpWEWk0ZL4/yGGqW6iXB8Si55GB0ZhQ0QD4H8uu/t70r9QosQhGd5RVYe6nGFgDbAJe\nBKYSGFZts7Q1sYiGQzXQOR11MrvBUe5Dz3YilkqyJm0hl1JWbr6QNwafQW5GGWw3NnSjTvzQ8oKh\nnebuINMF5xUsYeXXE8gbsxqX3peVzkP8TRxgibdUnRlGBOWNupOX5HaqNIBCNrtyKa0cDKxGApXa\nC4CD6UufRPoKKXWsphS1rpGzL7ovfk8h3AtlSH9nebrWCwCJjkDwEufDJYWUnnoLZb1XM1U/Gtd/\nr6Su81+RN3vo+fUt6OmdAvtMB8fPJSMda9mx7ThWal/S58kd6rtlE5TMONJwbMTuSkhBp20PZ3D8\nVTvZ/YSbHpPqovvOccYODW+E1hKL1pgG3SCPpjlEapyMZKvnD0YNZFjxpzw8aRojWUvvuio6fIjq\nWFbjL+wjpUBskIGYBStmpqy+AveE3Sw9YiJr5ZkscHhBQK4+ms+cq9B3FAHwZuYIxmp5/mt/vN6X\nFdqXDb1f8cjl2QT+QzhgltfJYDYxURuoRlOEhz2+Lhyzphb2g+OSd2HqqYj+Ejm7yC9dv/RdxNfz\n+yEOSPSPtGbFcUT0B10OUgg0dHwHHCpB8fvq49AExY3FZ8Tq9LQFIwyJFopEWBJN7TOcYEg3ON/O\nR3+2iI2Tc5kr7+HdBaP5YuZxDHBehngzn6JRc8hfeh8PT7yCwWzGJWsZ4Lwc7stHzClEINGfLQou\nYQh+M1qeC1q2B3mfB2YHRjWE4UeYpA3E9f3V1P69R3TditbGKlIC5unf4XEeyW79If4pT2eSIzcw\n7V4IxP35+H7nUPNRtspA5nEIL6wQ/M8LN8RhmVuzrTADJOTcYJR7BL8AHaoJH9Bl3W2yR0naHYkU\ni3gnVYmWSDEAogfs0h/ip9/AMG08c8U96L/T6P1tFRn1FyDHFJF/6/3IP8zzlxgUAhZ6d5ExczsP\neXcjhxcoJ6H1zmj2udOVA1X6gDlFQceWCCZ98gpyiofan/0FLknMPJGYsVo0EvK1o/BJwTU8xmRt\niX+5ua6cU8SjWVMQ+2VAIKwO0tD4k1NhTnERZMPzxeMaLTqNgFIGqpEYLPtIB85N/NwWowlJIeUs\njEQJRaIFork1QEw6ZxMINoKAf6I/yD6C87KKWeHYHryRAPp4oLwQEBT7PuUPzlzVtVhprJMOjkIf\nfFWIEJLd+kN8Kgcx1hE6opGgbDityakeeD9E6ARk6hdSUTwg0F0LV/TZKiKXwTdD0ui5uYbiweeR\nRSVDPWWBrotlhGTVpBGM2fyOqqWSg98PRTXwDBxaCZ9ECEffh21hxIV4i0W84iaiOU5L8N+JtgAr\ngC9RXYoajEFwyYq8ixpuKIHyIv+bF7kQ39GFaIN1f/yE7CaQO4qQUvk5HpCzOUcLN/zZxsUC4P2i\nBosEZ7Kz8nh1UvUHbZlO/Z8IjuGwioXpfJZQOPgmJq9+maGbylSd1zW6GrK1WCTrxBnUZ4McLqg3\nxX4bKqba8Gkk8ryzBSPOpEruRZNGQ5JrQp79ywVF79wUYaPAhb5C+xLq4JfV29XdLgdEdiBWT0qY\nr3kT7adMIpYvJgSs8jBTL8HZ5xHIhm/OcuErn8cRbg/aY39v2CUxum6zz5pHzwU1dOEgu0anq+U5\nEt+OeUyb9DDbphth+TkwhcXc6ipiTdZpdFiAsuz+BrwfWzRptBy2ghGvAsixpN6P9ZgtoUE/1zSV\na9Sj/lzYN+RoPM4jmzZcpQRZQJXzBfYOcbFs8HgcvT1k+CzWSbsVixCkhDGFzHd48cn9TMt8mJ7a\nH9VnfTy84XskKJL1pzsFvy1YimOfmiAj5xRxg+MXHPvAQRy9PawelYdAslg7wMViGbPPmoeYKKmV\nLu6vyOdo6thb4Gp2GYRYOSwFIx5dkGQ5MBN6TDcc0cVDT20Gl3u7RnexCwH3eagkk80MQUqo0mIP\n227TSFikHVCWlZDM3H4nZ9SVKH+RYV10KIfefIW8u4j5mtcQX6kqskkYu/lt/6JSx2oWOL0UbZ/D\nKZvLoBxOWVOqHNFmV6eVOOwEI14WRVukKS+6RJ2gPilY7DwAnBDdjnvAMMdv1Ylv7uhwJzASS6ao\n5D7XzMBoUjo8OmSKYcCF5Cw1+m/yJKt/RIlyH1Gu/BVbgfWwTpwJQ+GR4qn+NeOVHjASh80oSSxC\nkQoC0ZI2NFn02MQNsocgffEuDi4/hpkT72KBoz7KowiY64F7ClrQwsOLpbKUCXUr6FADIzLfpMTx\nIVGpq4CZege+4jheXTgxUJltH+rE2A+HjDiYSMFbJv/r32PLaPeC0daFAhIgFhA28a7sK5hTUMT5\n8iXWyjPJ146K8mjtYIi0FRDCgxAFfKE/Q3/HZVH/ZPN83zFVLKKKDIZeXxbsdwKoDgRtWQknGrEK\nxmHXJYmGttztCEe0AT3igOT+TfmcsrkUT4ej4M1oK43ZYhENUhYhpWA5E5r3kwkow8grEkpjYeYJ\noF0KRnNHQJIx0hENLW1P2GxNVqyTmkKcZuI1Cevh++oiZo66swVHt4mMREq4QxwFzzWj7KOEMw2n\nqfZYPvJkgTZHDwoJby3anWA0twuSSgJhJWHtaiQ5jZUjdkgeqMpnj+8hpvi6kusbA88VJKpVhx/T\no+8V5DvVDN4/Zc3iXL0fa6afhvcfwRPNE+3sNGk3pRLbi1DESqPWhTnHw0q4H8KIPhTpEiEkA2Up\ni7UDwOp4NvXw5mD0c2ek7wGGOa4C9qGvmoxYKf3T5huzLhJRUrFNC0Z7FIlYEvuahB0NcQM3obJZ\n/QYVSmxJAmOKiTYznx9qithxdAbHa5cB1vlXtq8ifkQ3OpKrj+bTyuGwA246qxBxvQzOPWpgTaKT\nSNqsYNhi0QShoyA9oMuIPZAH3z7aXaV3s8x2lMMFc84qxFd/O53ccKO3A1J649kimxZQ+WOWSiO4\nBe573xMkFq3puzBpcz6M5oZzp5ojMxyxtrHBfJFwtUq7wBtHjGVqp0V485zBCXfd6vP52u1w0xP4\nNhaogbf7m+GYs4krQsAGfQXzOt2h5ouUG4Wpk92uJB23WXEY7dGagPhmAYdAV0S+LLhpcCH3v5XP\n6rPyOP3bd+lU+wPjMl6iqzzAU5XTVfCPGxU52A1kF9CGegK1P1ohu5VNMOIWDwvvuoKRrCVnU5X6\n31QTHKgVkiO0qUproT6Mdh+HES+xSLWh00SJBengnOlliliMo9+P/FJUcaurCPnvDnSlmmliEfVu\ncOz3oZ2cT/25qHofXWG396HADm2xaHXk3UVM13pRSWbc9hnvcz1lfRixCkVTP1S4z+PtUY72uM0h\nmgpa3l5ORIFEH9YJ7S868l5APEGmXskmhoAL5CUqiKiTWzk5q10u5stZgO23SB4qTuPiH5ZxMP1Y\nNfdkG/5kyf6Tp4aISYUT7fxMmS5JS0O4w2Uzs15U8fjxYhGSeCh8UyLRYFSkPzATVg3JQ/oEYzuM\nIK/+ZEo0S+JNS5djoBzNVBZxw8dPwkkpmirvMMP89wgBvjvU/0QiEO/LBt0SCO6ahDvnzXO4zXdJ\nYslJESoWPY+C7Gdjb1O44zSnKxPPrk+zxQJU1qwa2MxgzumQR4b3As4XLwVvaOlylDpWM93RC05u\nmEHKJjmof4+Amz3B1ejMqmqN0NLiz9GQtC5JQiaFSeBB6HwyUK5U1/zx4mWmtZb/I5p/esQ5IhJ4\nBm7bsICa+nk8mJPDyO1rYfxOWBHGgvDPsLb9FimDALHzFtZnnIisFJyYtZ6lciL9yyvDrh5aVS1R\nXZOkdUmWt2CjcBdraDeks5FBuYFXGThU3nohtNESy90g4gxUy/wQebJApEvkMHBc5oP37C5Hm0EI\nxEf5Rm4Mpeo/HSykw7WEHTGxElqrBGOTNt8liYZI5n3Yu70kYL49ibp4xgF/VR/3PDKxJltjmMe2\nPlpK1DNQa2RgaO49u8vRppBSiYXRfXQduooO/wizXmjxo3RlZXfOjv+5nrKjJDHxNIFiwacCw4C5\nwWZbskQjHjQ6AzUcNcp5hpCM8/ZjpVZL64wJ2cSMIRaCOSzr9JtAqUoT08oIDdbrD/QBbo9vc1Ja\nMBrzFzTqS6gOPO9d6AqqQ2lebPEIq/VfuNkE6k/Ead9NHjMS7jCv3cB+kF8Vss2RwdW+rDB1QmxS\nFVUlbhKDxKeBIVWTcDPMuhvrjIPOr0HPD+PXFU/JLklLRhgi/SDvkBco1mMhlipRnd3BgVLWUnYN\nPo8jUYuFWTndTDprtEvrk89aOZJzrvxn/BtnkzCkhEnaQI51/DFo/s95xcWBkRPr0Fw2cC5sPC7X\nb4H0PLKdZg2P5ktF/cWzYeJbr7CtICPsRi25qBsMY7qBk1GOVncj68ZAcwVIZgsGFWxAe1RXJ1M3\nkP0EPp9gurMX8qpk+bptWop/FNwsnO02ijP31dn4cG5wvZNzYe9gF0OXhMnQFSPRnDmLgXNQo/tm\nGmk38AKQAVQCFwC1xme3AFMAHZgOrAmzzwajJM1Rv8YqlHd2E7AozB/XjMU3cghESmsWqSsR8WI1\n1T0d3Lfspubunqr6VHn41ZvbVYlKJEzz1ByfTwfHPI8/6EcIyRf6MwAM/f4D6jo/0bxG2KQMQsAe\n35/Z6cvi1A7jweWBukJm6k4eWHKH/7z+Znoa1/Ior0yapM55S87PvsauWtqGaCyMvwFjQpbNBd5C\nHX+d8R5gAHCh8TwGeCyaY8RDLPyYF9DlqAs61BlkrpNOA0dh6AUa8c5ubnszMAs2FuRS/USv4FqY\nYZyQzbEUmiUWgON7H9oeHS4z7kYCxun98PkEA7TLOJs3qTvSFou2zlNyGqc6xyN9IA+qkpQvcT5X\nTFqobhr9YbGYwsvFk4OLPseJaATjPeBgyLJxqBQsGM+/NV6PB5YB9SjLYwdwUsytjBYB9W+jRkaA\nny4zhDScaJikBz/MizriBWuJdZD7Be5jdzO0skxNPQ61IFogHC3yf7hB76nhvdrJsszxgIDhHlZo\nHQGQPqh0vAByYjN3bJNKSFT1+KCERhKqtC0sevB6Vo9S/rr8392P6CZhJqqr3NgIWjNp6SiJ1Tdr\nVkcAOBZV1tdkF9Az0g7iSc8jAQkdfoWyLN6Hjm9Iti3NIGdNVfiNrBmnTC4FXgv53Ipl3fTf72LD\nEcNUFyTcPyXckJdJdQt9HKH7MvYhalQmpgvTVjJRDCSvZDUl2meB9STA0hYc0CZlkOFjcaXvM5j1\nGWNmjwBOx/Ex/NRH0OFpEM/KuGYWj8ewaqD6buTPG7DY8nqQ8YgbNagLaR/kFFexemIeo7eWBGIz\nrKMaVn9DNsqWiiYZYnc4UPILOvQ7hO7uFBCaBoWNw2wbKhRN/UMj3SHC1El1fCARwM3iXkqwh04P\nGwTM0p3MYj5l4h1EnUSkwzu74K1a+DbaulRN0FLB2Af0APYCx6AcogC7gV9Y1utlLGvAlBYcNGqr\npNp49AXKYdTmd1Unaj18MymNXkur8Z7ixDnbizfPqe7OEAh4sR4w3Bi3gWODD31bJ8gBh/Axc8ad\nfCWP45XNk9QKTxMQDNMOM4WqmoATtiWEioUbv/D4VhVCJWzw1XL2D29Q13kD8Bk27RMh5uD76ihk\nsUCUS+oKKuhwG7ABTjoEJ3WE3YZgPBLjsVo6rLoSdU/FeH7VsvwioCOQhbpnfxRLA2OiGvgSxDOS\njVm5MByOKa7F+3Mnq487jcKXbmLf9KMDMQvDYdtZGQ2GqMJOP3UTsK32g9ftZK08kxXOL/lmsAvt\npPzg+A9jfPyDhblo8/LZ9rBlqLcFPotGP8uGejekUUvdkT2wxaI9IaBPcOpEKZ9D65Ov/BbVkDOp\nCtYDB+J/9GgEYxkq13Q/4F+o8Yd7gLOAL4EzjPcAW4AXjec3gWtphSmQjYZ571OtGXpDmbrjl4NY\nLxm96V3yr7qfAT9tgeEqCe7swUVqwMlwVq5amIc28+9RXdDO3t9SppUhx3k4/qctfOZ9Fu0lPUh8\n5HDBYseaaLOQAAAgAElEQVRUdukPkVNcFVxEKFrRCLeeOdGsj8DxqI+NWbkccKVxfNV2kPb8kfaF\nRHxVSJH+ncoq7htNsb6K170lSiS24h9KNYnnhMtouiQXR1g+MsLyu4xHXGks9iIi5o/mRkmY5eIU\nNZItf81CSnD09oEo5JfeCUxjEXdOnsltmxYw1jkC37O/j+zUtHRhfjx4FJtduSyXB5ivefmfndvZ\nXdIVFlrWz5bkUkavZTUqa3ck30Q0fg8To5vjeMwDUz1QoCqpK+L+b7BJASRQJ9KQq+6AhYXqfCkn\nMIM1gVMUUi7SM1ai+pFq8HcX+m+u4nX9DB6q30WV80Wu4XHOZB31Zncix7KdG64oWBiwGtJh7ygX\ne0e5OOJeH1fzF+YvuA2A0zL/ycviPH9gF+kgqmECy3nv4sE8P2lcoCtkYrU2Qh/hsKjojV4n8qki\n5GQ1Nm8+bNohEuZrllSK1TRI5ZAo2rRgRD3jNNwPuRJyllRBDQwUpdSJNDg3nxLtEPdyE8td42Hn\nrdw5ZGbgwk6HRQ9eHxCBbipt2tXyMeRWQZljFcwpAgkljt7coP1CXeyWtGLpdXV0oZaJy15l46Tc\nQHi5+Yh2Io1VXNJhsNgMN+fbKnFYIBB42DvKFfc4i6aPnBzke81YubndkaCJYVbMERBzpCIb6AbL\nRo3nCd/VgZyX/oSKMNXXlcfrbqCDMfy6dUgmx1duh953sUV/mlrSqCWNsx2nN7hYhYDX9RLOyfkn\nW7Zncbx2Ka/rJaxlJPPn3oa4r5BivZSLi1ewbNJ4buVuZvMA192wOLq7hfV7ZgM58M3gNM6Wb1KW\ndzK8byfLab8IQAZyfr5vLA7TJbH6MFojNDwliTmfhYBHJ0+h/mSY5BhIidMSb2Ze+FKw6Lzr+MqV\nQX027B3iYtgPG5DH3YX0CZbLCQzTXmQ5E0Ce0eAA8iYP6zgT+WURA7RL+WN9B8Y6RjDf4YV7lTOy\ni6hF9oNJzoHcJW7huuLF4WugNoH8OWgn57NYTKHMuQret52d7RslFvqHITcFy+zkRNAmBSMmsTB/\n0D5w7cd/44guHn7lHR1hLEfCiiIqyWSzK5f5zKLoiHxjXUn+mvuRvqUschxATakJ2fbe742+pkRK\nwVeit9IiY3uAr+iNty+87i3hCXl1oI3W50jfw0o2+KSggqyg/du0X/QdhYjtxhtrV9boMicixUJK\nJ9AJR9RiEWFUQ2YLtBydPaO7sJwJ3Kg7WeDQI+9HSubKu/k/bTVS6iB6mR8gxxSaLyNwX+AzCSsc\n2wMfCYF8Lp8n5Ubcrmqq6coQuQlHiQ+xqAD9QcNCsJiYkb6jzBY4+nhAShaJBAy+26QkHx6XS+Zx\nlSxnAtd7FqmFblRk1DPAyoY5PWMl5S2MuM05MedclEv0f2v0qKjjuopFzBbzydC7NrKhoEzTA+6J\ngIkQG1LCH4oo0/Zwr5zL9FueVNbIokI+159B+0Jn9fS8wIzbMHkbze8lkGR4L4i9TTZtB6GGz4/t\ne1CViPgN6oY4TnDer4up/1NiDpvygmEl6tT7obNTQ0wzUS5hvRrmPKailiqn1sgeJci1zW9sNEgJ\n8jPKtFXIe9ToCqd6qBNpnPDER2SIyuB6DKHT8i3f6wPHMBb6dpOrj05MW21SC3PovFxVsKMrMBz2\nnnU0Ly+ZTIcJiTlsSndJrNZFs+t0WF9bd2QVEyM/4gbfBVwsl1H7gys5CWYsPgfZVzAs/VPkwUIG\niMvYoj9Nzv4q1S0JN7s2HciBY8UMw3G+ujVbbpN01Hnj6O2hyPcdtaTxQPUdMAe4Iv5HS1kLIyFi\nYaVbYD2BZJaYT97PShrZoJVYVIisUdaG9MHx2ufsneQKZNQKFQtAmstsP+dhiuByvStr5UgWaF51\nY5lrR3pGh1lb1EyMGvqZedEZvoFtWRksZwJ3/FDESsc2kp9+P+AjEQL+XH8jL4nzA6M7od2SbiBq\nYKae0saiTbwRgedc3yierJxBydIxyNcLVExGHHNghDtsaxMxcKs5lkXYuqJukJcLPpz8K4Z+XAb/\nCCx33OhBINE/KmLPiWn01GYYQVoXk5rJZQR0yUfUFrLF9zQ5FVVqzsB+Ar4NN3wz2MWx2h9tC+Nw\nQqiyEawHWa6mtbMff2qH0KCtdlOM2UqkzoA1bV6jKfSMZWdMfp0KMtn461zIVjk3GQ5HH7qKh/Td\nbBySS5XIUCtLSWqKBYCEg0VIYC53c1zWF4FQc/BbHZVk2mJxuCFB650P1SCqpVKEVqiLkzKCEWpZ\nmBnAmww+CTPk+M8bzuHi4hWcWPd/OE79kWHO3/LI4Kl8e+QTVIpMbuFu1omRZOgXQZonzE5TCaUE\nKyrPp1J7US0K8WVkUqWqfNscRgjkzQUNpxAkqCsSOGpykO8R3qLI/iVsq8ggZ7CRhzPcD9BUyjpr\nZqtslYdiUNYGysSqwLoqFL8NIeDmfD64ZxCD6wL1JqpdLj5lEGOFnY7vcMMh8tGf1VQ39VLgfBrt\njkAb7pKEioW/OLEbckZVqR/AdE6aRMjCDQSLhZVu8HzWOKbxFDxnuQu3KbEAFWpexGYGU+1yAfif\n2953sYkHPjkP7RIdsuGKrIVxLykQjqRZGF9a3lidm/5CRANQxWQl4ZPrhhIqGNYZqTnwU7bgiC6e\nNj/9Wzhgnv4fzucl0qjloOzC8drnSHl/sptm08oIAV/oz1BLGhNZSsWJA4KKFpm0CwvDtCgaiAUE\n5k8MJ1ARpbFkMuHySFhnfKarWIuZuhPeLIjXV0gK0gd3aP+PTxlIJZlUkgHYYnH4IfiVPoZTfvyA\n4emfUDEyvFjEm5RxejZwbo6FR4dMUV2T0G5GaDaqMJ/LcwUnTl+vckRkpnGE++8s0E6DsW0/R4SU\ngkmbX6GCTGpFmppta3N4IGCqL50fDxbw6dJhHPz8WBYeuCJiec54RxWljGA04A249qG/oc3WA7kz\nuwc/ZA5ob+c3FJB0EBskvxMv45jpo+fmGqTvc5Dr2nyXBFDf4aRCqunKV/ShVnRJdotsWgsJi53V\ndPwIxAcSsUBy/Sg1UzWRw6kmKSEYYQOw9hkzS0doMJSAPwL8wiD6wvu+lWin6MERkMbnt6+Zz5Z3\ns7jxxDuB9lUmUEpBKQOplJlUaS8kuzk2rYZAICEbtDJdZQj/snXEAlJEMCJSgypkkAPbCjKQ2QJO\nJmi05JSKUrz9nMH+DctISu+6KkY61pGn72zNliceKdnEEHbQp31YTTZRIpGyB6uyRvB+yeAGnybS\nfwGpLhgGEsHxt+1kzaTTGjgzqYYXhpwL6SD7C+YUGIlnugHZsOnogZxzxT+DU/C1Eyp/zKT0x9xk\nN8OmtRDqjxx+Nedop3HKqNKEB2qFkvQZSxET9prUqBoiu+5Mp2e/Gn78WGncV64MetdVca9rFpsY\nzMDJZRxX9zVSCDW6gqr+VSvS+OUT26h6KuFfpdWp+9kTcL8d4XlYIGCgPppPi4fBxgI4AmV9Ez7Z\nb6JIumAAUSUt7VFZy0Pbr+RasZBcyniJ83ncdQ0u6njnh9O5ptPjuFy1rNx8EUMGf8wISgC4V95E\nlbOd9vElgSKVNu0bCWXO1ez1uujxRl2rzBsJR1K7JFElKXUD+0GUwwSxnHKZzXRHL95xHOIpprFO\nnkHdr7rzjvYR7/wwAnlSERO12cohSCaD+QSGt9e7sIT3DyW7ETaJRqj0BdIHPYrrwo6VtoZ1YTQl\nKUg5kIAvwqqSbuBU49lcPly9r8+GI7p4VEoyJI1NCBEOyNVHU7ppKPy67cde2By+CAds8L7K0IIy\nVe48jHURSTBCtaXNRnqafgagYRBWDWycnhuYS5INq4bkse7oEfzSexGcmm9sGHl0QPqgVFsDJ9n1\nOWzaLmZNrZc4P1CsKJntiWKdxcA5qPQcJxjLCoBpwL+N97eiqrUD3AJMAXRgOrAmzD6l/BhVbbqc\nhhaGMRKizcxnvb6CTCqpJY23fCOZrvUKs7vGuAm4r5nb2NgkH7NQkXiQBoWWIXUtjL8BY0KWSWAB\nMMh4mGIxALjQeB4DPNboMcwapeECt9Lhf72ncA2P8xTTWMtIZnzyZBTNDcUWC5u2R7FeChLEM4Qt\ntNzazk6TaEZJ3gMywywPp1LjgWVAPVAJ7ABOAhoGQWxAFZLdarw362+U488qtUxczFNMI7/qLo7u\nUYNMmsvFxqYVETDx4RVcfN2KVqnI3hxiGVa9AbgE2ATMAmqBYwkWh11Az3Abr5qex0HSqJmRznUV\ni6Eanh8yjosqVvKTW3Cd6yFcso75ebfBe4XUxdBQG5u2gM/I0SlqgHLjOYx1kUxaKhiPA6Y3cR4w\nH5gaYd2wnsmxjhH+1zcQGPacyCBjI6OzJm2npU37Js93MnO5F/EWym6PJBCtHNUZjpYKxn7L66dQ\nKW4AdgO/sHzWy1jWgIvveJof6MT3dGZOv08ZkS0MC2OFYWEsxCVrWZB3G7xvD4vatF9KtA95lxH4\nvipRV5abyKkpmykanxqPeBGtUyATJQrmKMkxwDfG6xuBX6Omgw5ApeA+CdUVWUsgb5YVKf8MjEON\nK9cQGB0xfRj94ZtRacqHUXknrh411H3WHU6yxcOmfSMc4FtQGBhBtHZLIkR4ttYoSTQWxjIgD1W9\n8V+ABxgBDEQJQQVwlbHuFuBF49kLXEukYInhBH6QagIJQLKNZfthIkupJY15mbfioo7poiWjJDY2\nbQvpg2XTxzNZy0W/tij5tbUsJC/SM4o4DMdMDxv0V8mkkoN0Ya0cyXRHWB9qI8zBTmFn09YQxpXp\n+7AQUigOI3mCcR2BIVUr3YF02Lgwl6ELy5R4DIdVWXlIKbhGPk5VXr/o/BoCkJHDx21sUh1TOGbp\nGvePzA8SjWQIRvJmq64n/FwSgGyUWNQYn3WDMdUl/JQNVV2eN0SgCQTk6mMo23Sy7fewabNIqUTj\nfF5Sc6xWJrc9yZtLYvotQsWiBhUz/yEB82u/Wt6xBoq8/4U+5lySCLNQBczSnUwTT+E6IYU6gDY2\nLSBXH83QyjLl3zO77EkiaYJxqMYwqcINE1k9w+X4s2c9kjWVKkcGC7+8gjzfULbI41god+M6dDV5\nvqGMkzmIjz0s1UuZLR5gAssZeEQpyet5JRoBp7bXqfs2JqWO1YjeHhyXeNi71uWfUmFND9FU4fJ4\nkRoJdKppUjW/yUxjer8n+eEjDYCzXGs5ru5rXnK5GPGzt7mHWziu7mtuHbKDiytWACrj1s2ue6nU\nH2ufiXIFMF6kxCxGm8STq4+mR3FhYGZ3tRKN1pxXkvScnodCxpfDYoSJye6Cjh9KOn4o6V1XRcdy\nyVQWsbLqfHI2VdGhHBasvs1f8r5DDayTZ7bfjFvPedQAt037R0KptlpZ25fB6rV5/ptsVImo4kTS\nRkn+G67iGQQsDTM3hhu4DLYOziSnuEqVtq+xrDMOJRDrjR0fFIg+xqhIN6g/GdYePYJ75c2UaO0r\nEXCuHEOarKXE0b6+l03jCNGd1/UX6SIPMnRwWdgCzCbtJ4FONJgJdLZB/8JKRLkMOEMNi+SDrIFo\n23X/iIqQMsha+cqVwTrOpMTZu9Wbn1CEYAib6M2OZLfEptXZx9mV7zB8xCcNPkm0LyMlBCOoD2bt\nmoyDvTNcyvF5wPKZOdy6Q9Ul2TUxvWGXxnh/UKYxX/OCLE5M45OGJI1a0kSdnTn8MEJM8/CF/jSU\ng/dXTv+oSWt1S1JCMMJyOTx61hS67/xWCcZW47HP8tgCchocM6k2MKJiPORwwaBRGxi6+f/YrS9E\niBOAM5P1beKLEIiPPAxmE4PZRO6sD5PdIptWQi4qZD6zqD8ZxFAJ98Kjn0wBWkc0UsKHYRJUo2QA\nalrbazQcBQj1CocryJwNMl0g3BKGw09dBLe45rFg9a1wdtsO5BIClvjKOI4KDso0xmoj7GDWwwmh\nLtxc32gqf8jk257d0QdqUB6+ervVj9FmfRi7/9tIanQ3gQsgtKsRbgjJjNuwUo1ykAKUQ8eDkgVr\nboMbBA3Vpe0gBBTp33EcFaRRSxdRy0LvrmQ3y6Y1kSoCtFRbQ8YRVciDRf4pFZBYP0bS4zBM0eh5\npFLHzmbB5ZOBm43X5kzWpiLc9tFQC7rBslHjqaYrrJewo21bFwv1XUwQy0mvq6Pa5SKNWg7SBZV6\nxOawwgefOU/C9+wpTJv0ME+NvMEfl9HzyMTUKkmahRFqEPgtjhrY9lYGPE3AL2FiGR1pgGl5hO54\nP1xUsZKnmAaXtOXsXQJu9jBEbCK9TiUsNJ9T2BNlk0CEyMf7tBPK4cmK6a2Syi+pp5rpu7RyqBpy\nsqoaF4fGPoPApDY3qvJ7VgZlq4daqpyfQIZ+YYvbnRwk4r5CMqmkQw3+R4+KOg5KV7IbZ9PKCAH6\nmxqiRoURiCsjO7Hi2QFPiXuTVTh2/xfKv4byf1nmm0SimrDicfrDb7DskfFsnJGL3C6olS5kusD1\n/VU8LHfxgZzHMjHRXw075REwXuaQoV9Ij011gdEg43tnUUmb+B42cUNK0M7Otywwni3d9kT4MlJC\nMEwizSs1hSP00QBj2T+XnkMWlQzdVIb4UnJKYRmsh7rOTzBD68nQTWVkUmn8yBKVXTAVEZDmQQD3\nMJedlcf7s5EB/twImVRhD5McZgjQdxQqgUgnEBVNYodXU0owIFg0mnLaBAmHxcpwXvIitTJNDclW\no4K+asD3ZiHep4tgG/TUZhjjkx5UGtJUQ8D/5pNXsxqAnIoqFelqioVFNHpU1sHOW5PSSpskIcGb\nDj+dK3CM80E2PLp2SnAJUgvx6pYkLQ7jvSZWsH7BaEyrIFXNtry27siaS2B44P0HWQPZxBDWyjNY\n6agl+UkUA1nCHI7Z7NZ70WNNXcMMZcZ3kcPA0cdjGxmHK0Iw1ZfODtmHd50b8d1WCK/BofLgm+4+\n2nAcRlM0x9KAkC6K9XWka39/8HrzmUXJjyMa2aCVmFoAXVTfVAj43HsCPZYYYmGGxJsYVpWowRaL\nwxkpWawdYKRYy0yvU91I7klM1yTpcRiNYQ2r2P3fpi2NQzWWH8k6o3Wf5bU194ax7jDHiyCXkgpX\nnfhSsqFmENfwOMu4mJw1VcFDyxD83aqBbbDH9xDLxQQW+aZQpq1u3UbbJBmBXl6I2CTZMzhNnRcJ\nKimcshZGOKK2NKyjJqF3ZVBdluFAOuzNcpHh1YksFoLEzkE5gVx9DNxsTCB7v5A0aim7ciiVZAaX\njAodFbJ8r2FsYIajJ2VOWywOC8wRvj4eQKrh1fVwzNo6zvtDMfXLE3PYlLYwIHzwZoswLi6ZLdB+\nrrMnqwvLmUAlmVQ5DzSyoSRX70CZFo9GWBACnssn9+IPuclxL9V3d6XyrgwWXHkbx2sSfYGGeEg2\nSC3vx7SUatScmUrthVQwkGxaCwkbfK+QyZ95ifOhQC0WKyQvd5mMeDoxh01Zp6eVUMFoqmsSNInN\nSn8Cjs9s8PUTaCflq+zi2qrwOxPwpu8dXNTxkjyfDKqY7ugFSHjT08RENktNFAHjvP1YqW0P7FrA\nQt8urqpbxDpXHvfIubxTfHZwYadIcSghSYd8lwq0PvlM1buyyNGYANq0F+TOwkBtH/N8Me+w24AD\nDSej9VVP7c/p2Rgxxcibd+wd8PivL+fHg0WUOSOIBQLGecikkiF1KrHwHT8WoW7lgqLRN4GYyFRf\nVxp2WwTc3JmZutN8Rx/xVYMj9GEHzi9hrDaCq8RfAm20Pkf6HtajlUsEkCkqG9nIpj2h9cmHHOON\nNfpxK36xiDdtUjAgDhNrJFy3ZDEdP5IU62Xk6UMbriMkU195lN51Kl9oj011bOg0HHbeihCS34u/\ns0G/kPPFS8C6BgcQ9xUyUqxDZKukJw86vbzhK2Gmz+n3WRwkDfElLPGWcQv38OikKUrQmvnPFv8G\n/cMiprJI+UTsbOLtHp9PWchBROrCxok20SWB8H6MSF2TRnOEmpxsfNYN9o5y8aTvCvJ/dx+sfJnx\nvp+4QL7A5KoXKcq8lduXzPdvpu3X0f9HU87Ibqpg9DXyUVb8biK8WmA5wEQQS5EfFSJrBAIJbqjP\nhh1HZ3L80p2snziIoUv+D/GhxfkQaRJdpB/E6GItKxjPpFtfQd7Ttmfj2kSDQIh8dutd6FHxLeIZ\nqXLGRCjYfNh3SUyitjLCjUePg22TMsANpQwkTRyElUXk6Z2Zy71M+HYl9L6L2zfND5q7cfmNjymT\nrxrYDwLJ4+I6yIFc3xiVLk9Anm8nD+u7VJ7R/YEIzWqXi1pcFE88j1OKy5R32xzJMfN6RBMKYq5j\n3FE2MwR5T1uejWsTPRIpizjmrTrEazKhFkUoTQnGL4C3gS+Az4HpxnI38BbwJbAGSLNscwvq8toG\njIpnY6MhqmAVN6qFArYNyWCs421mOHuRoV/A4+Ja1nEmHculclVss2xXA08WTg9c3NXQY00dx6yp\n5ce5Dh4X1zBz1l0AvPv1GaqrYoqLMaqxnAmcuuwTLi5eEeysgmARCH2EwyIs8zUdpnlgSUEUP4BN\nm0bALJ9l2C5kLkkiaWpYtR6VKK8UOArYjBKKy43n+1BpbuYajwHAhcZzT2AtygryxdrQSMOrjQZ0\nGVXgAX+1KBPpFgy4sgLXwr34vnIgEcyWToSQ3LZkAZTD63oJmvZ39Gt/779ohTl2ae53q9pvpy7/\nAZ6F315N2qG9rO/Yj57/W4MvN6DJcoegLCuX3RPdsISAr8IqFJGwBmuF/jCAvLYAmV2IVqSzQb5K\nBlX0rNyHzLqrkZ3atEUE4JK1OM72kKuP5iZ5L25qGV1QEkhnmaAiR01ZGHtRYgHwHery6ImqBvKM\nsfwZ4LfG6/HAMpTQVAI7gJPi19zwNNo16Q70h40P58Kl+IO2Vg85jaIn5rCl0wBYD2K95IFPDAeS\nYRGcPf0dvAsuiMoB6d1xNL/yDoJXC/niiAGc0OES9AlakJUg1kumyMX01Gao7lC0YmEl3HrGPkS5\nRL9WY2hFGV3ravk8I4eI9Wdt2igC2duDx3kU0qfKKE7SRjPWmQfDUDfGkJtjPKe5N8eHkQkMQlUG\n6U7gVLfe/I8FrAkmd6EEJjkY8RZchipm+wF8MykN7d86oyvexXP+ffRYGMgvITZIFYpt6XKI12Rw\npnIT64XbDZwHvYx0rGWc3pdjN9fi3VikZpea7AO+hFOml+K9fR45N1Q1XyzCHTsEUSOhXCXX+VYc\njevQPuCEZh7AJnWRsKMokAtKAlyCt7wI/g2kw7biDH8kc7yJNtLzKOAlYAbwn5DPJI3HGMYt/jDq\nqE+zKyKBvrBqSB5jHiqBcjjm9Vp0NPgA9BM0sBYNEwQCYcxi0JHorkoZOPt9j/7vTuhuS2RmueGG\nDt2H2a1xy+DlTTmtwv3jawjuntTgr+6tnZ3P694SzhYjgL80sXObtkfwJSXl/WjZHmbqTmbzAJVk\n0vtPVXT4B3S+Nb7dkmgEowNKLJ4DXjWW7QN6oLosxxCY8bAb5Sg16WUsa8Biy+tBxiNumBdSd9g2\nMYMxa0qCnYemtRDqF9iH6mxFOUqRnreLz4/Ige0EvNXRdDOaO1ZuXdcqHqGi4VZT3X1ScA83o4xB\nm8MB6YP5Di8L+CPi43x+6KNBNbzTEd7RoL4DfFsf+3Ga6pIIYBGwBfizZflKlEcA4/lVy/KLgI5A\nFuqe91G4HZ8DTDEe8RALvx/D9BibYeCCYEEIdzFbxWRlyPtQLMsPvNuLBcwMzBqNsJ6fCCkFm0Wk\nbQ2x0IaqvBglmi0W7Y9GwicEKqp45608N/h8+rq2oP32B4Z/DAUuuK1jYIgzFpoSjOHAZOB04FPj\nMQa4BzgLNax6hvEelLC8aDy/CVxLI12SaEMOmoVA2TtDgb6Qs6RKZSC3zs0I7Q6Yj3I49KFKPHKo\nPOQz66MGOAgOKflrxQw2DskNJO2xWi6h2xlESjnYaPpBK6EzVmvAsduH824dfYcZiyEY7+vn/00y\nfG0t6bFNKEJIivTvQhZCpu9Cpupdmf/WHfi+PIKdsjcVSwag39+JDhPi3Ib47i5qZOjs2+bMSI20\nbs8jjTiMbNQ3yzFel6MuLDNjVYS7dKQLNWJsR1/8Hmn33N3U3N1TDWuF5q9oYv+RiCqmxMy61V+A\nG0S6RBSpkRHhACSqFqcQnPL9Buo6P9G8RtikDELAbt9DVPgyGe4cDy4P1BUyy+fk/iX5/sJd38xI\n41r5KK9MmuSPAzInobWbSM+4WhtmBqqDNHQYhhGLpu7qET+XKFvqAFS/14u9BZHT/bfE8RS1tQGI\nfdI/QqI/WORvn76jkP6bq8ipqKLuSFss2jo9Pq7j1A7jeVjfxYaaE8nVx7Bg9W1qRM6wNnua3VGj\nqxxPp2fKWBhWorE2Is0tCZra3h+WFY/n4oIVYe/8Lf0hG8xVGYBy/4Y4M+MdNNOoxWENUusOsq+A\nLkagmSGajks8POTdzfSrnoSnCuLbOJtWQf650N/lPa+gmJcLJvtrk/jpC/wGNg7OZeiJZap7TXws\njJQUDJPGhCNSjoxQwdhb7KLHpDplvlgEIx4Xs/9Y2QSJRSKmFTc4ZighaQf9ocLpgWc5TLD9uF9S\nIbMY68hLXCNt4ooQUKyXMoJ3OKbAqHZXDfKgQEjZ0DTvjlKGS4Fpyi+3+7/xSQKc8hm3ItFoTIbl\nbtujuC44r2cc8QtDqg9I1ADp4E2XSCk4xzkPFbVv0xaQEiY7c9F3rFALDMe72GeMJ4S7QZnzpSL4\n01pKSguGeY03O0XfZagf8VLgCpSs3gOHTg+skohCtU0RjxBdU6QaWBqWlH1+K8MQCtwgfw4d0zxG\nBQNbLNoaPumgyzF7OJh9bPjAwlDf3FZgfcOMW7GSMk7PxojkEA1rNAgCynol6kddYbzGUvQ5CZjH\ntj5aStTdHktKQk61a5e0KYSAj4y5QFJS2/kJ6n8TZr1QsagOhAfE+1xvE4JhEpVouFHquhUVJWLE\nLOzNtcUAABxeSURBVCTix4sH8RCPxtC8OgxXVbKmlDyamIPYJASBZOGQK2GVx5/q8Z+uEN9ThFG/\nxLUpOcjHjBfdYthJ95DXPY+Czo/BoWsCy+N9IUbjColnteyoEx5DsOPzZrjzrJnc4fx//NJ7EbN5\ngBscv8A2MdoQQuCYewf6AA2JAAHiAxkoatXIiFzoeW+et22+8tl+gktvNId9Ia93fwfll0Zau+WE\nm6waz/UbI5r6sn7ME8gYITlRfMLr9SVUOV9gOecTSSxyfaNZ6NsdMH9tUgMpVRa1cpW6gC8JiEUj\nJNKSTrpgmLRUOEJFY5+Mj5kfr4s+Hvtp6ruENUH/AWPeKuGcDnnwTD4lmmVKj0CVSDB4XFxLH8rh\nZDvFX2ogWK+vCLw1utVB6RIsNNUFiecAYcqNkoSKRjRdFusP0p3wP1BoNyEZFVRD29lcrKLRWFdF\n26XzeXFvjv96O194+/EUHVjQL59pv36EqSxicF0ZHV/zIAT8UFNIx01AOuzxlXGsmNGCltnECyHg\ndd87DNtUinyuNFCjxpwFHSHRr0mi/XQpY2FEorlWRyQh2Ed8uwqxEmsbQk+MQ5YTqX5BBxaLKejb\nO1ElMrjr2zugm5d/05VFTKVDDfi6aegfFtLxH6jqaQegp2aLRTIRcz0s1HeRRUXc9hnvcz3lBQOa\n311JFVFoinh0VayYouGY6+O+SR7EVsnoG97lFde5+HxHUCu7sEhTtyaxVSI2oO5e+9WdbUl9aWBn\nz9n+jNZFcNpdq6kVacxnFtuGZKjJk9a5UCFmaWtbF5ACoyQtpbmjK/EcuUgUsbTR2kXpbMZemLiB\nHtBl8R4kgv880g29IjjrtBwumD2yiAULbkPcXMhMr1NlIpf2qEqyEA44+r9XUbvnWHgN1R2x1B85\nFBLFGU4wQm9IbX6UpKXEq6uSSsS1jaE5M/ZCTUlPDt59LN7TnIHlZu7S9ZIHHslH3FTID9WFTGWR\nLRZJRkrI7FQFQqUvmFNQFEgMRZTpD+JMm7UwQmlvFkdL29fA0rBindEa7kDWqNBu6nlvlou/+yYw\nXevVwhbZxIwQCLoB+/CtKlRZ4baiEj4Z1kak7ohtYUSgvfo5mov1xGkw3GZmC7MuD/cjVBMkLGWO\ngaqq23MFcWqlDWnR+4gEs1mv/5VC/b+cN6qY1Q/nKUuDxq2MRJzfKTesGitGydOoiToTeSuTsHaF\nm6QWhp+yBbceXcgCh47kALAKWJ2IFh2ePCzgD9GtWqR7GPJtKafUlOLo4+HlB99F+42OvjLgh+p5\nZOs4PduNhWFlP82zOFJtyNWkpe1p1MqABvlAgw5yLjAcjujiYf7q25Ubw/+wfRqxIgTM830Hfyho\n1nbrXHlQDr5rChEbJfr9mt+x3Zq+jHYpGLGSasIRK1FPRkqHOUOK+GBwLkXe/8BYO/IzvqhuyAQR\nTfooCwIGUsrGs3IbfpaAYkWN0e4FI9a5KqkgHLFaGdC0aEghcN+6m/lLb+clMQGP86goLQoBN9sx\nG9Gw1HceP9QUklNRRZ4+NOrt8rWjeIDZqtaMUazKn1GtP/6M9VaHd6LO23YvGCbxEI5kikdLjt+c\nPq2Qkuq7eiEnFTLfoRtaEUWJRQHCrsQYESFgoW8X83zfcdHmlSqydj2c73jJP2W9KaQULJh/GxeK\nFwKjWNkwZ20RjIVH1k1ttW7JYSMYJi0VDZNkC0csNGVlmJXpBZIpelfgs+h2vA82+FZEfQEcTkjg\na5HBzd/OR2yT/viY6zctQjQ2uGmdOSwl3FTIV/RR0Z/9geFwJutgI1w/aZF/1XgWXg7HYScYELto\nQPKEI6HHrIEfDxaxW/8zi50HottGSphTSJaoYLDYBECGfpgXTRIYggtIwfx+t6vEN+Ykshqoz4Yd\n9EbMDSTHQQi43wMI3hhyhn9nub7R3FjvJL/v/f6iWRtH5TKQUn9QXmtxWAoGNH8kJRLJGGGJ5VgN\nrAxrf9gNHf8h6bH5W+bp30UR3iNAeMjQL6THpjombl6B3FlIlfPFFrauDSMErPIwy+dE0I1FVTew\nx/cQIJFfFjLWcX1QFbyOt0leLZiI3kNTBaceKOBh/V/smeVG7ixgzJp3EA6Y6kvneXEx89fegb5U\nwyXrmJNVRC1p9Cioa3RoPJR4nJ/tJtIzFmLJ+hWO1orriPY4oWZqZ2v5AfAHAZn9YzkMtMt8yPcK\nG93vOL0fK9MvRjgkvofUvUd2EzhG5wPqGrrR62SB5o2ypW0cIRDiDPTyPMQO1b1zXO/jh48cdLhd\nqPohVszyD6cKvhmZxmIxBc/q+9DTNeR6gfaIju7R/BXNSIc5k4u4s85DhxrwdpF0+AilBG8C7weS\n/kZKZ2lUTrQjPWMhHl0UK61lccRsZfT//+2deXhUVZbAf7eqgog0IYGvA0RNsYQBRppVQKBtF0Rg\npkVGW2SZ/loFNxr4BBEaaCowNioKduPXjdKCCwZsRkRQo6LYYAMNTcDAIFtYkhlsAiNZaAYXUnXn\nj/te3kvl1Za8WoLv933vq6qXt9y6effUOeeeew6qWn0uqhZtJpr0FPx0y5vWJ3cybOvRYi2iKg//\nbreR5OWsrHXM467FFPi31rOlKc7goNkhKZFyMx07HFAP1SHwj3HTZC5KWAQ/GPo6ngyJSwSYt+dZ\nVt1+Fzv79ECUS/y3uxE7Za3o3FvZTJNiidguSTumXacrKkP+IPUxns+d46XS0IWG3dpGQ5PmRHv9\nel27AsqmppNZeZ6mrX7NB/6t3H5yK5cyYZ/oCRyFWfPgmfk84G/NBPEy6bzGJ3IIi+V0zvElYvA8\nRPl8I8dkMcixrhoN5rRMZ4THomiSnhH7RQkrwmsyKYeAkf7ObHBdtPzjNLlEvS03ZckKHsX651yY\n0WcBi/J85PtGMeaXG1SJzzLNCZ2FEZUroEK2VJ93Aq9ghPkPArZHv6akvkTSMK4B/gx8ARzAqBif\nB5zCqOg+3HTOr1DuncPAUJvamTDs1jbMJMtRGuohkmUqaU6T9yXbqzfylJyFe72f4y2updSzFvHh\nPBYsfAKxykdPoeXKkJKpnmxKn/8nprqzkdsWqP+0OXu1yVZvc/I8oDvzDASS/D6jYMUC0r9+uHHk\n3xCwwH8BASwTk8j3jw/6u0A8O49flqxQvzy6yWfWKMwbwDZYNN6HOCoZM36DWlRmdmKaHxgJvUQR\nh2/LMa4Hqq83xjdbuE4kW6aNthUBzYE9wJ3APcA/gCVBx3cDVgPXA9mo8lqdgUDQcSnlwwiF3dpG\nMHZrHOGuV8ePoQX7yK4gcoHB8NfbejLIvZYF/pd47pvpeJuWsoYxtKSSticroRi+6y8Y2qKAUuHl\nxO6u8C6I4AVtZh/JHbDaeyd/4h42uo7UHCIyfBwsb095IIMS4WVcq/VQkaf9tTtRT+nGEyFg1zzo\nZ2hAp+VvyTpRhSgH19YAcsZ8OD4bOiwEBIzycXBde657/jhLH5vIpCEr1Yn1Hcxmp3QruDQN0oqB\nV1GrVjXMBYtCKTMQfx9GGUpYAFxAyb9sQt90JLAGuASUAMeAfvVtXLKJp7YB9s+whLtOKC1DnEH9\nQh2EG94oIsdfTSF9OP9FG55mFhlU0HZTZc2S6iZzJFsmD+ekryvieRDH1P5aX6QcZLngzin5CNd3\n/FE8qD0sMxACVssi/IUuumwq5YZP9rFX9CHnq0OqPQJ2yCfhxGytgXpDRdy1EKFpEACcmI0QktPX\nZ5B+8SHVkJl5ZJeeUZnKtoPf4ybw+gICNKUgsJU7/J1hfR6lIofq6zw8mvdKbW3iXIwbGGt9BBye\nn8MVMwKU9Uk3kuiUh69ulswUfV6gF8p6ApgM7ANWAC21fe1QporOKQwB0yiJt9AwkyhzpSYqUHuV\nuQL3GT+HxnlZzVhmsojtffow5ts1ZJeaWlVuetXL9YVwx4tzkvXzx+MPNGXLR8PY4D4Cu6/ivurW\njHf3UIPurDp2sctPaesu2sk++lYV0cO7F1DyIsc/WhX1GfcgCOgRGKbiHHb74OSc6Jfdz8qD3T61\nVB8tCtN/iqWBUzWxEHe71sEqHz3a72VB9QWydp+n6qrlwBjEojxOXdvGqG1aLtUMxleSzdzKxi5j\nYEIex+mI6CRrZ/m2KDikow96y3QEekqCg9Blcin+iW41nZokonV6NgfeAqaiNI1lgL4y6T+AxcAD\nIc5t9EscY10y3xAa5MQ0XSPq87PA06WaVVPu4rq9x/GXuzl8Ww5dCkspy72aNm1OUXZtOm3OVtX+\ntTRnsDZjstsFkjJvC+i4AAb5YCu8snwS9136PbygzhdI/B/Mxz1Mv8DbpBXD3sxBuDkErOZk6T9T\n5m+BEDDE35JKsYESkcNXfVuz9ZubqPr5S9F916fnwyLw+jvzRKCI9pSQISrpWFWKRPDcLgmFAs4K\n/lY1kCbvao/usz7IkvBzaJtXWWvwywxw9/eBuIeC6lsYseRTprT6Iw8eX0maflAM1clC1s7V+t7z\nVDX+7u7g02qI949ONLZMGvAeaqb3txZ/96IyDnYHZmn7ntZeP0Qt0dsVdI4cYfrQWdsaA4kSHGYa\nIjzM5+p+jGaZwGBgBMq7/irGL1kuqpj1q9qK9lYuDk++lu7/fRj/O00NuzmSTa498Ds3qxWWvav2\n4zkKaQPW8s25n5E2t/bhspUAVd+rzjVoRY0TsaxvOkX05EX5EBtMPpFYEQKEkHzp/x1tCjVheMh0\ngClfpuws+LfJ+ayfPLa2wASjgwfAmskjGfPxBtWfZ6ltWlA/p2QtwZFLzVS47CQQN8mQvgv9/QHU\njIXOf6qXuPkwBMrkOEhtYdHW9H4UhodqI3Av0ARoj/qKpgo6Bv9q2hqLsEgWdvk5zHauzBW4RIDM\ndl+qh1r3Qxyjppi1KAbX0QDdSkqoDlwZ+sJW6rZpcAzYtI8mcyWu1yUHqh8nrYI6MwaiWCKs7Pcg\n2u2p4BM5hA3ue6P+3lZIqSqiZ68OukmwAxcQRyVvjx9n+BOCR+YZ6DVlB+1FiZrBOIQtwqLOeeUo\nx2c/EM8r7SdS4t/rgNGmraFEkjSDgc+A/RimxWxgDNBT23cSeMjUztnA/UA1yoSxStPUKGZJwpEM\nTUMnVo0j+Pjs5tBsGcgPBPTTgopMHncEkGGcfPPqAhbK2Qz27MF/k7tOXc86mHM0dMOYJszCeOKs\n1j/oswHB1zHlGBUd8wAJUmkkDUJAy68f4uw37UjbRe2apcED3Dw1GkwmyG4CpsFC72PM6b0kZKEh\nHauBHmrhWDOzprUcFbhQDhePhtcurGjoLIkTGt4Akik0dGIRHlbmSTCWiYPfBuaiBnlxnVOiI1yi\nF/M9s4L2mcLVXZ3y7M/6JUB08hH4yKWEZrDACCcozETQJuqTPq9OQufBQH9gcehp1EhaqCMwkkwq\nCA2IXnBEIzQgSHB0po6KbcZqgFjmZwglNKwymZsFxiAgF17wTmCKKx6TbgKXKwAFPvw73IbACCUs\nIsx4mLEjz6bZ93SxApDW60WiMVkbKjCc0PAGksgZlHBEOzNiPk5/mK0ER60Hf6chAKK1xc3H1QgP\nfaAFCw6rhMTaPpkruNQPmq7ww4w4hZALyQ8uPExFmadu1GowMQhNu6gROmGET6Km5B2BYQPxWocS\nK9GuWwl28Ef1KxhGuESizlShedDpwkMfcMGC4xxccczPtGm/Ycnjsd87GgRjWXblI3DUWBVaa8ZE\na4cVdpkfkQgnEKIVFnbEFDmrVW0kkUFekYhmVqW+af/MWyyEDE6yQguFFkgO9u3AELEZPohPpKeU\nqxnn6smRoTmGADNL3CRoFRDd7Fii1yY5AsNmUkloQPwfqPoKjlhoSSXruAtax8/lJiUM/G4HcpDA\nNdC09ClCgFUwDdEuYl0qEMv/1q7n0jFJ4kCq+DV0IkWP2rEE3zxQYjZbgn0bmeBu4cf/CzcSQTsx\nFfgKtUg6HmQhMh4m/4pbaJ9zEDrNh0ewHJF2CAq71g1Fi50/Yo6GESdSTdOA6M2UhhLN4AlZYKkc\nqICvZ3o45M3B3XGesRAtTghxhqXnJqocmQKQP2TUC/m238fORYbJwhEYcSQVhQYkTmjUWz0vg7T3\n4BHxIpyYzXZvf7XozH+7DS2rixyUx9Q5y2lbWMnJT7pR4F/L23njI5+oEel72pkHJdbr2P0MOiZJ\nnEmVGZRgIpkhdmUK+/L/QpsoF8tDl/nbNPZGtpwczoftf8LAwiJ2+CUD3XHyYWyfj/yxlof0oGTY\ntq2WAWqxzIjYrQUkU6sw4wRuJZBUExo69Qn6ipWYIkszUSHleiKeLvBdf3irxZ2Mc1mUC7QBIeDb\nivkqRHw7xvJ9Uxh8tEFZqSIsrLSLR9WLE7jVGEg1Z6hOrPEb0RwbTChNI6SWcY6apSKyEzTN8MU1\nT4KU8EyL6czptEQlMg5OORgFqSIoIH7msOPDSDB21EJJFeoTwxETWrTnmQ7pvFFdZG9mFSHggzzI\n9dWUM5znac6mDjfWLHT7ff79av0GkbWL74OwAEdgOAQRq4Mu1uNjEhqZKkPGXPkk41uvj+HEKFjk\nQ3wmKThyC/n+Iu4W69h2aQMj3D+BTOgw5Qse3fMKO/PiYwKFI1X8FVY4Powkk4omik59fRbRnFdn\nJSYof0VXai88ywTXY8ocmR7wkC4r2SP68FBgOc8wk63uncSEAE7MgQ6/wRsYzcnCbmr9CFA2XiXn\nSaeKAb/bh9il1RIpNjSMeGoWdlwrknbRUB+Go2EkmVQ2T+o7HRjNOXU0DS1o6838O4ziSrrT8wEf\nBf4tTOBlrheFPMxL9HJ9zlZPjMJipA8B5HvvQZyYTQEjDKfmOcisOs+/lH7EDYVFKh9nqDSEMXzP\naEhW+Yn64AgMh7hQr0HQFUbv2cjOoT3gpxip+W5S9Ti6FJYy7ORWhm3aSptNVeRXawntRffwWcWF\ngDd8LH37QQB68zl0XEjHqlJG3ZZfk2Xr0/Qb2ZYzQGXlhsTVvLSJRPz4OCZJinE5miiRzs++SjNL\nWqOywb4Kl5aBe5eLtK8ucam/h7TOa/Ef+5nKl6lVAes1ZQdPyGe4+/wGpBQcb5nDy0xgibhkeZ/V\nsoib2UKbTVXIs0JpEaZkOTJT4NlfTfVcj6o6JoFF1JgkZq2ooRqB3RpFtMLCMUkcEkY81eaL5ajl\nIs+oG6XNBdfGANU7PIgKuK/alNNPMyP2Lh1EL1FE2i5oUiFpSSUiTEauce6hbEGVbQwWFqBSFVb/\nyIN4VapYDAFk2b8qtbGYH1Y4cRgpRqrGaujEVMLA4lwszq8Vo1GMMkW2AZkgukpc/X3k+G9Vg9gU\nEyGQdMkrhQy4eWgBJdJLiXsL1kNSkF+9iZ7sC90wQByS+uEqQ/Uho40Wh8dMPIRFIv1gjkmSwqSy\n4AD7yh/o1CqDAEbU532o8lnmtHnm3J9aGj+ZK9jp7cFA18igKwuYMA+xcj4j/Z1ZWzWO5S3uZ9Lk\nlUbiX52gIC27TJF4aRWxCgvHJLmMSeUZlIZiNYD0gVljApQDd8DEcUuNMghWF8hU26VMGPFtgVbF\nTMBgn3rN9SEehqXVp1jIHPak92BS3sq6gsJCWDSUeM6AJOP5cDSMRkAqaxp2O0LrxGcIVeSojm9C\nK1IsWwmeeGE+d7GOStmS4e4nORj4dz6XPZktn6LU/SZCQH6giDGFG5CtBJ7p1fjbuQ2NJcJCs4Zo\nGPH0V9RHYDRUw3AERiPichUc4YQGhFigptNNe+0Ph6eoEo+UK/OkrH062Q+W8/flGWSdOI/QSx+W\nw4y8BTw7ZF6dGit2hYCnmqDQcQTG94xUFRrx1DR0QgoOvWbHLzAKMmmrXEU/iSs3gD/frQp6asJB\nlgvDwRnDalS7U+fVh2QKDMeH4WALiYhLCOtTKEaVBC/G8E20ArFKsurwXUzsu1Tt00sznol9JVtj\nFxZ24EyrNjLMD0yqaRsNmXINPj9c4p0agpedmy4gEYgfSt4fezMZopKxhVOiXqau3z8WEhFbkWxh\nAY6G4WAzdg4cq0EbceZCW/8hzkk4pELKvZTULDBrrKSCsABHYDRqUuUhspN6CZxQmkM5PMIfKMGr\nPutxHRZEEkTJrA2SSv/nSAKjKbALKAIOAk9p+zOBj4GjwCagpemcX6EsycPAUDsb61CXVEzI09DY\nA/O5UWsZ5n2Zxuto/kTvqv30Hr9DrYDVN/NxhM4tGqmd3ydhAZEFxjfAzUBP4Efa+8HALJTA6Axs\n1j6DmuQarb0OA/4QxT1SgqPJbkAIom1XIh+sAwm8F0TnT9jyj6AdWm6News30uQ9yfuM4PT4lvSe\nsl2FfGdhFH0OV1m+ATS0n1JNWEB0g/mi9toEcAMVqIwFr2n7XwPu1N6PBNYAl4AS4BjQz6a2xpXG\nLjAgcdrGF1Eel7BFVq1gy4Xan8lEaRLbgaPQNq+SVlVV3CI+paxvek10KE8T0kzRsfoe0Xy3aPvJ\nilQUFhDdLIkL2At0BJah+iELo8/Mzu12qKh/nVNAti0tdYiaVFrA1tCZE53gWZOQyYOz4K95PRns\n2UP1II/KmqU1pMlfJM91/TW0BjldMMr7Bu96jrC9egMDxu2j2baG+TLsIlWFBUQnMAIokyQd+Ahl\nlpiRhE/PGupve6O4d8L4BtoCp5PdjmDq26541kP5OsY2xUtoADXmxPkmtCWL0wjIEBVUF3sQO2Td\nEX4I6ApiiWR97ngokIiPjWs1w77l7LH2EyREWPSO/y0Mfg08jnJottH2tcWYtJqF4c8A+BDob3Gd\nIgxB42zO5myJ27YQR1pjzIBcCXwG3IrKQzRT2z8LZQmCcnYWofwd7YHjJC/83MHBIcF0R5kORcB+\nYIa2PxP4BOtp1dkoZ+dhID7FMB0cHBwcHBwcghmG0j6KMcyaZFCC0po+B/6m7QsXkBYPVqLccv9l\n2pfsoDirNuWhZrw+17bhCW7TNcCfUTN0B4Ap2v5k9lWoNuWR3L66rIIt3ShzxQukob5U1yS15SR1\nZ+AXAU9o72di+GbixY+BXtQenKHaoPuH0lD9d4z4BMVZtckHTLM4NlFtaoOaqQNoDhxBPTfJ7KtQ\nbUp2X4Ga7AE1C7oTFWxpS18lOgqzH6pBJajgrjdRwV7JItghGyogLV78BRUIF00bEhUUZ9UmsHZe\nJ6pNZaiHGuACanI0m+T2Vag2QXL7CuIYbJlogZEN/I/pczIDuyTKcVsITNT2hQpISyThguJOmY5L\ndN9NBvYBKzDU2WS0yYvSgHaROn2lt0kPWkx2X7lQwuwMhtlkS18lWmDIBN8vHINQ/+ThwCSUKm5G\nn7dOJpHakKj2LUNNk/dEBSItDnNsPNvUHFgHTAWCV48kq6+aA29pbbpAavSVHmx5NXAj9gVbJlxg\nfIlyFulcQ23plkj0CLz/Bdaj1LAz1A5IS0aUbqg2BPfd1dq+RHAW4yF7GUNlTWSb0lDCYhXwjrYv\n2X2lt+kNU5tSoa90qoD3gT4kv6/qhQcVzOVF2VfJcno2A36gvb8KtURpKKED0uKJl7pOz2QHxQW3\nqa3p/WPA6gS3SQCvA88H7U9mX4VqU7L76rILthyO8igfQ03nJIP2qE4qQk2J6e0IF5AWD9YAfwe+\nQ/l27ovQhkQExQW36X7UwNiPssvfobZvJxFtGoxSs4swpiuHkdy+smrTcJLfV06wpYODg4ODg4OD\ng4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODw+XE/wOl7RK9GWsMxAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "plt.imshow(data1,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will learn this lesson how to make a version of this code which works Ten Times faster:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "def mandel_numpy(position,limit=50):\n", + " value=position\n", + " diverged_at_count=np.zeros(position.shape)\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+position\n", + " diverging=value*np.conj(value)>4\n", + " first_diverged_this_time=np.logical_and(diverging, diverged_at_count==0)\n", + " diverged_at_count[first_diverged_this_time]=limit\n", + " value[diverging]=2\n", + " \n", + " return diverged_at_count" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]\n", + "values=xmatrix+1j*ymatrix\n", + "data_numpy=mandel_numpy(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "plt.imshow(data_numpy,interpolation='none')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 51.2 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data_numpy=mandel_numpy(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note we get the same answer:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(sum(abs(data_numpy-data1)))" + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Two Mandelbrots" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/020numpy.ipynb b/session08/020numpy.ipynb new file mode 100644 index 00000000..e70aa8cb --- /dev/null +++ b/session08/020numpy.ipynb @@ -0,0 +1,2077 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xmin=-1.5\n", + "ymin=-1.0\n", + "xmax=0.5\n", + "ymax=1.0\n", + "resolution=300\n", + "xstep=(xmax-xmin)/resolution\n", + "ystep=(ymax-ymin)/resolution\n", + "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", + "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try using **numpy**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NumPy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numerical Python, NumPy, is a library that enables us to do much faster work with floating point data than ordinary python." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## NumPy constructors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy's core type is the `ndarray`, or N-Dimensional Array:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.]],\n", + "\n", + " [[ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.]],\n", + "\n", + " [[ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.],\n", + " [ 0., 0.]]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.zeros([3,4,2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We rarely construct an ndarray directly:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[-6917529027641081856, -6917529027641081856],\n", + " [ 4404674571, 4427832408]],\n", + "\n", + " [[ 4427863464, 4427850368],\n", + " [ 0, 0]]])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.ndarray([2,2,2],dtype='int')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can convert any Python iterable into an `ndarray` with the `array` magic constructor:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0 1 2 3 4]\n" + ] + } + ], + "source": [ + "x=np.array(xrange(5))\n", + "print x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But NumPy arrays can only contain one type of data, unlike Python lists, which is one source of their speed:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array(['1', '1.0', 'one'], \n", + " dtype='|S32')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array([1,1.0,'one'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy decided to make them all strings." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The real magic of numpy arrays is that most python operations are applied, quickly, on an elementwise basis:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 2, 4, 6, 8])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x*2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ndarray multiplication is element wise, not matrix multiplication or vector dot product:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 4, 9, 16])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x*x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numpy's mathematical functions also happen this way, and are said to be \"vectorized\" functions." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 1. , 1.41421356, 1.73205081, 2. ])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sqrt(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numpy contains many useful functions for creating matrices. In our earlier lectures we've seen `linspace` and `arange` for evenly spaced numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. ,\n", + " 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5,\n", + " 9. , 9.5, 10. ])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.linspace(0,10,21)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ,\n", + " 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.arange(0,10,0.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " Here's one for creating matrices like coordinates in a grid:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xmin=-1.5\n", + "ymin=-1.0\n", + "xmax=0.5\n", + "ymax=1.0\n", + "resolution=300\n", + "xstep=(xmax-xmin)/resolution\n", + "ystep=(ymax-ymin)/resolution\n", + "\n", + "ymatrix, xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[-1. -1. -1. ..., -1. -1. -1. ]\n", + " [-0.99333333 -0.99333333 -0.99333333 ..., -0.99333333 -0.99333333\n", + " -0.99333333]\n", + " [-0.98666667 -0.98666667 -0.98666667 ..., -0.98666667 -0.98666667\n", + " -0.98666667]\n", + " ..., \n", + " [ 0.98 0.98 0.98 ..., 0.98 0.98 0.98 ]\n", + " [ 0.98666667 0.98666667 0.98666667 ..., 0.98666667 0.98666667\n", + " 0.98666667]\n", + " [ 0.99333333 0.99333333 0.99333333 ..., 0.99333333 0.99333333\n", + " 0.99333333]]\n" + ] + } + ], + "source": [ + "print ymatrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can add these together to make a grid containing the complex numbers we want to test for membership in the Mandelbrot set." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "values=xmatrix+1j*ymatrix" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[-1.50000000-1.j -1.49333333-1.j -1.48666667-1.j ...,\n", + " 0.48000000-1.j 0.48666667-1.j 0.49333333-1.j ]\n", + " [-1.50000000-0.99333333j -1.49333333-0.99333333j -1.48666667-0.99333333j\n", + " ..., 0.48000000-0.99333333j 0.48666667-0.99333333j\n", + " 0.49333333-0.99333333j]\n", + " [-1.50000000-0.98666667j -1.49333333-0.98666667j -1.48666667-0.98666667j\n", + " ..., 0.48000000-0.98666667j 0.48666667-0.98666667j\n", + " 0.49333333-0.98666667j]\n", + " ..., \n", + " [-1.50000000+0.98j -1.49333333+0.98j -1.48666667+0.98j ...,\n", + " 0.48000000+0.98j 0.48666667+0.98j 0.49333333+0.98j ]\n", + " [-1.50000000+0.98666667j -1.49333333+0.98666667j -1.48666667+0.98666667j\n", + " ..., 0.48000000+0.98666667j 0.48666667+0.98666667j\n", + " 0.49333333+0.98666667j]\n", + " [-1.50000000+0.99333333j -1.49333333+0.99333333j -1.48666667+0.99333333j\n", + " ..., 0.48000000+0.99333333j 0.48666667+0.99333333j\n", + " 0.49333333+0.99333333j]]\n" + ] + } + ], + "source": [ + "print values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Arraywise Algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use this to apply the mandelbrot algorithm to whole *ARRAYS*" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "z0=values\n", + "z1=z0*z0+values\n", + "z2=z1*z1+values\n", + "z3=z2*z2+values" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 24.06640625+20.75j 23.16610231+20.97899073j\n", + " 22.27540349+21.18465854j ..., 11.20523832 -1.88650846j\n", + " 11.57345330 -1.6076251j 11.94394738 -1.31225596j]\n", + " [ 23.82102149+19.85687829j 22.94415031+20.09504528j\n", + " 22.07634812+20.31020645j ..., 10.93323949 -1.5275283j\n", + " 11.28531994 -1.24641067j 11.63928527 -0.94911594j]\n", + " [ 23.56689029+18.98729242j 22.71312709+19.23410533j\n", + " 21.86791017+19.4582314j ..., 10.65905064 -1.18433756j\n", + " 10.99529965 -0.90137318j 11.33305161 -0.60254144j]\n", + " ..., \n", + " [ 23.30453709-18.14090998j 22.47355537-18.39585192j\n", + " 21.65061048-18.62842771j ..., 10.38305264 +0.85663867j\n", + " 10.70377437 +0.57220289j 11.02562928 +0.27221042j]\n", + " [ 23.56689029-18.98729242j 22.71312709-19.23410533j\n", + " 21.86791017-19.4582314j ..., 10.65905064 +1.18433756j\n", + " 10.99529965 +0.90137318j 11.33305161 +0.60254144j]\n", + " [ 23.82102149-19.85687829j 22.94415031-20.09504528j\n", + " 22.07634812-20.31020645j ..., 10.93323949 +1.5275283j\n", + " 11.28531994 +1.24641067j 11.63928527 +0.94911594j]]\n" + ] + } + ], + "source": [ + "print z3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So can we just apply our `mandel1` function to the whole matrix?" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel1(position,limit=50):\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmandel1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmandel1\u001b[0;34m(position, limit)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmandel1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mposition\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m50\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mposition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mwhile\u001b[0m \u001b[0mabs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m<\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m-=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mposition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" + ] + } + ], + "source": [ + "mandel1(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "No. The *logic* of our current routine would require stopping for some elements and not for others. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can ask numpy to **vectorise** our method for us:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "mandel2=np.vectorize(mandel1)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data5=mandel2(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "%matplotlib inline\n", + "plt.imshow(data5,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Is that any faster?" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 641 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data5=mandel2(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is not significantly faster. When we use *vectorize* it's just hiding an plain old python for loop under the hood. We want to make the loop over matrix elements take place in the \"**C Layer**\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What if we just apply the Mandelbrot algorithm without checking for divergence until the end:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel_numpy_explode(position,limit=50):\n", + " value=position\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+position\n", + " diverging=abs(value)>2\n", + "\n", + " \n", + " return abs(value)<2" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python2.7/site-packages/IPython/kernel/__main__.py:5: RuntimeWarning: overflow encountered in square\n", + "/usr/local/lib/python2.7/site-packages/IPython/kernel/__main__.py:5: RuntimeWarning: invalid value encountered in square\n", + "/usr/local/lib/python2.7/site-packages/IPython/kernel/__main__.py:6: RuntimeWarning: overflow encountered in absolute\n", + "/usr/local/lib/python2.7/site-packages/IPython/kernel/__main__.py:6: RuntimeWarning: invalid value encountered in greater\n", + "/usr/local/lib/python2.7/site-packages/IPython/kernel/__main__.py:9: RuntimeWarning: invalid value encountered in less\n" + ] + } + ], + "source": [ + "data6=mandel_numpy_explode(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, we need to prevent it from running off to $\\infty$" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel_numpy(position,limit=50):\n", + " value=position\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+position\n", + " diverging=abs(value)>2\n", + " # Avoid overflow\n", + " value[diverging]=2\n", + " \n", + " return abs(value)<2" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data6=mandel_numpy(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 48.1 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "data6=mandel_numpy(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n/Ze2FeBAFQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "%matplotlib inline\n", + "plt.imshow(data6,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wow, that was TEN TIMES faster." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There's quite a few NumPy tricks there, let's look at them:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Logical Arrays" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False False False False False\n", + " False False False False False False False False True True True True\n", + " True True True True True True True True True True True True\n", + " True True True True True True True True True True True True]\n" + ] + } + ], + "source": [ + "diverging=abs(z3)>2\n", + "print diverging[30]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we apply a logical condition to a NumPy array, we get a logical array." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[False False False False False False True True True True]\n" + ] + } + ], + "source": [ + "x=np.arange(10)*1.0\n", + "y=np.ones([10])*5\n", + "z=x>y\n", + "print z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Logical arrays can be used to index into arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([], dtype=float64)" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[x>80]" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0., 1., 2., 3., 4., 5.])" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x[np.logical_not(z)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And you can use such an index as the target of an assignment:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0. 1. 2. 3. 4. 5. 5. 5. 5. 5.]\n" + ] + } + ], + "source": [ + "x[z]=5\n", + "print x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Broadcasting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In our example above, we didn't compare two arrays to get our logical array, but an array to a scalar integer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we apply an operation to things of different shapes, NumPy will **broadcast** the smaller index:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[False False False False False False False False False False]\n" + ] + } + ], + "source": [ + "print x>5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This can be used quite creatively:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 3)\n" + ] + } + ], + "source": [ + "row=np.array([[1,2,3]])\n", + "column=np.array([[0],[2],[4]])\n", + "print row.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(3, 1)\n" + ] + } + ], + "source": [ + "print column.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 0, 0],\n", + " [ 2, 4, 6],\n", + " [ 4, 8, 12]])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "row*column" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(4, 4, 2)\n", + "[[[ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]]\n", + "\n", + " [[ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]]\n", + "\n", + " [[ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]]\n", + "\n", + " [[ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]\n", + " [ 2. 2.]]]\n" + ] + } + ], + "source": [ + "x=np.ones([4,1,2])\n", + "y=np.ones([1,4,1])\n", + "print (x+y).shape\n", + "print x+y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More Mandelbrot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, we didn't calculate the number-of-iterations-to-diverge, just whether the point was in the set." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's correct our code to do that:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel4(position,limit=50):\n", + " value=position\n", + " diverged_at_count=np.zeros(position.shape)\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+position\n", + " diverging=abs(value)>2\n", + " first_diverged_this_time=np.logical_and(diverging, \n", + " diverged_at_count==0)\n", + " diverged_at_count[first_diverged_this_time]=limit\n", + " value[diverging]=2\n", + " \n", + " return diverged_at_count" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data7=mandel4(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQwAAAEACAYAAABGTkjoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXl8U1X6/98nNyCD/kxJGUBhpq1QKPh1CsIogl+LioA4\nwjgyLsC4AO4KI4vi1rRl3AVH3MZRGBcK6uACjgoIo1VBVND2q7JYpK0DCAwtrePg0tyc3x/n3uQm\nTdq0SZq03PfrlSa5uctJeu/nPuc5z3kesLGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbFJAmOA\nbUA5cHOS22JjY5PCaMAOIBPoAJQC/ZPZIBsbm/jgSMA+T0IJRiVQDzwPjE/AcWxsbFqZRAhGT+Bf\nlve7jGU2NjZtnEQIhkzAPm1sbFIAZwL2uRv4heX9L1BWhp8uIA8m4MA2NjZNUgYMbOnGIo4NMXEC\n24EzgT3AR8DFwFbLOvKxBBw4Fv4B/CbZjQhDS9vVLd4NsfACcGEz1u/egmOE26bnkcHvO7uNF+lQ\n8A0UHK82/KB4IKc6N+P90IlYYBi8+4x1+6v15WWC8zKX8JpzO+u9Kxg6qQzeh0M1arXd/w0cZx/h\nXzdFc38nk/0t2CZarlVPLb7uE2FheIHrgdWoEZNFBIuFTYJJpFg0l5aIRTT4xSIMWaIC70Yndw2+\nkdvcC9RCN5CunvdOd9GjuI5XqifDRomYHP1xu9M80WgJ3UisaMRCIgQD4E3jYdOKpJJQQHzFItS6\nCKLa8nof9Li+DtLhtoML4DJgG+yd5EIKwVje4NNNw+ANEG9IdfXXqH2Y1kUooSJhfq9ECkeqikYi\nnJ5tkr7JbkAEom1Xa4rF8a14LGhCLAxGHBWyoBrYCs8PGcdP5wrOFm9yzJJaPnloOLyGutotYpEI\nYv2dUu0GALZg+GnrgtGa/E8U68RiXbRk2xH/z/KmJvB84aaVfOL6FZ+sGQ7lIMqlij+2rkdk6yKW\ndkbzOzVFqomGLRhtmG6WRyoRT7EIZ1008F8YvokGuGHvkDQyqUTsl8qSiCAMjflEwrXLujxRfhqT\nVPr/2oJhE1cS7bdo6sKmOwEHZ38oI5dKMiEnjg1LAqkiGrZgtEFS0aqAVnRyWklvuEgi0OZtZ9mo\n8YzVRrCWkawenJeY4xsk2sqA1PifJ2qUxCZBpMJJE45YL5hotg/bFQFlUZwKXAq8BgKJ/mw/2AQT\n5at4+vnwFjsbiksz/RZNDakeDqMntoXRhkhVsYiVpvwWnd0hYpFO4OLvj78bsu24DBhuLBsOe4e4\nEFMlu7alI9KlEpZste2c4qIGfo9QQWqulRHp+7QnbMFoA6RqF8SkVboi6QQLhbGs/jGQNwoYDmnU\nUp8NZMOfsmZxNm9CDrxDHtqTemD7y+C+hZ6wDW/SRxIliRSNZJ4LtmCkOKkuFLGOiETa3rQqOv8J\nrqhcqKyB0Ec36HA78IHaxl33LT/vuIfZmfPIdxxF6WlDkbOLmNjvVeT5gkcnT2Hb5Aw+yByIqDFC\nxk0HaZhjQ8utDPP7JYpknRe2YKQwqSwWsdLYXBHrHBH+AX9dOiPQ9QizA1EtoQY61kje7HQO8zUv\nUgLvFQESdhQinxDcoP2CW7iLId+W8mjBlOAuSaj1QnysjUQOuybj/EjE5LNoSLnJZ6lEqgtFIhyc\nPY8MEQpQF3R/cDzuIdN7ATuLj4eNlo0s650+4w0qZBZfa28jZTi3o2Cp/JRBlJKzpgrWE4jLsK5u\nOkL7AzcCV8Kh8siT0ZpLIhyizXGCxjr5zLYwUoz2LBaR7rZBZn82SihM5yXg3VjEGeKfMIxgX4Yb\nthVkQH/4Z8U5fCBO4UZvpKEPySRtFKXhZnZ3tzzM42YDBwNtsLaxtaNYm6I1zxlbMGyiJpF98s6m\nL+EmdaD6P4EcJ9BO0RHpsFjrEbgvGj6MQTM2UCoGUn8yeN2SOlwIR+T8TcXeNYzgHfXGGC2xdkuk\nW6D9n66GZ4cbC/fFzxFq0pZHUWzBSAFSNcTbJJZ+uPXmHUrPIy1dESMyk3TYuDSXDgeB/aD/XEP7\nQGePPk11IS4z1usPN8l7uVgM4gj33+nYpYABxRXMd3gbHkgIWOKhmq701GawbVQGjks9/DRTcN7D\nxUo8smH1I6ex/t0T+WZ0GqwEniYoViNeVkZTv0tLaK1zx/ZhpACpKhQm8UqAE0qQk9PwRzz/6Tgu\nWrMykEElHRzv+njjr6eTSaUK8wYGUsqxjj8q56aJEAQvCP5MjM+HFYUU+8qYVPkCWzL70m/N14it\napufLhP8rPZ79H93UmKxlQZT3+Ply7AST79GU/6MWH0YtmAkmVQWi1isiqaw3q2DnJ0DgC6W924Q\nfyxAOCSzdA0XdWzmRK6UT3KvvJkSbSPRI0BIRMWtyOPuJkv/PTs3Ha8q6AB7J7v4lEGkyVpOWVgK\nH6Ku5vLwggHxvdjjsS9bMNopqSwU0HyxaM76EVPtQcCvYERvymzBFdMXssh9HdQVqhTTjVkS0XJ/\nAaJG8sZdp1OLixGUUCEzOdU5Hv3DIrKGbKFi0wA2Ds5laEZZg+nvqSwcjYmGPUpiE1ea269u7vrN\nCoSqUfNC5ok7KK7+XSAffaxiAXBTIfI0wdh+bzNRG8RyJjDcOZ7XvSVQAzsXHs+jg6cwtKAs9mM1\nk1R2itoWRivTniyL5p7YkcSiMQuDdJCXCrQ++XHRiSCEMERIIgQU6d9xe+V8FaOxETWsuhd/wp3W\ntDJi3V8kK8O2MNoQqSoW0Xrsuzdj3VCiEgsrlqS9Yofkx4OFFMs43+2lxFpG56a6BcgvhRKIGpRY\nNGNGa7yjOmPZX6LONVswWolUFYtoiPVCaMl8jNUP58FlsHp6Hrhh09G5THbkxtCKxhBws4eOOyRi\nW/RmTKTvFe8uRSqJhi0YrUCqikW0VkUstHTy1ujCEurdUE4f6rMhTdQhP/TE2JrwiGn5FN91XpPW\nRDhrqDHRiLe1kQrYPowEkspCEcvn0RCNUITtjhgBXPWPw46jMxmgXYbYeQsy6y51tiagEKcQsFDf\nxfniJYbJDXztfJtx3jReGTmpQVbxSMmCQ/0Z4UhW3IbVn5GKhYxsSE2xiEYIki4WRth3p3t19Cs0\n9B2FaMcZKpGgqr1Sdmd6Vw99qncgEUi5n5dvuD7ux4lXRq7WKKYUCbtLkgBssWgB1qnlNeAd4ISV\nRlmAhJf33oc8WMRyOYGvu/Zjpq4F8mWEEMlJ25zvHY/uSnO2j+f5aHdJ4kyqiUWihaIlAhExNyc0\nGFLFDdtmZFBBFmNXvQ1nF7SwpU2zRT5D/yWVyHKhhMq8jZeHXz+W7kk0RGNFNMfS2I89rJpSpJJY\nNGeYtDmYE8bMR3NokJsTwmb9Bvy+A4lgwOYK1skzYWxhM1sbHUJMZKmvlP5rKqFaBYsFXYkR2hjv\nWayhRDOE3drOUFsw4kSqiUVz1wkVgkiPuBEmw1XYYkSA/HUR85feFp8Iz7As5Sv6oI3JV20KVzqx\nEdGIV/LgxmhMOFpTNGLtklQC3wI6UA+chPq3vwBkGJ9fANSGbNduuiSpIhTRnjTW9Ro7sYMugmwa\nrUEazjSP6NQMR+i63S3L0oHhak7JI5lTme7oGbHNsSAcIN7IR9+gNczEFfr9GqnF2lQ0aEuwzuq1\nToIL7Y5E0z2ZoJ6S1iWRwAhgEEosAOYCb6HKgq4z3rdLUkEsmtOtiEYswqb0f5JAFqpGtrE+GhBJ\nLBprpMl+QEpmOBMgFgJEtge9vBDvtpBBQ7Mt4bpRoQ+DcNZGSy0O67adjdornf+kXvc8suH/vjUs\njXh0SULVahzwjPH6GeC3cTiGTRhaeoKYSWs6u6FztuURLimuG54/bjzbijP8CW6aRbiuBwRn/25s\nW1B39GqQvmYeOxqkgDwVixF2ZCSSaIRyKv4sXvHopoSd/r8VdbWlB++/NUUj1i7JTqAO1SV5AnUv\nOkggo4FAGXRdQrZrs12SZFsVsU47N8VCXi7Q+uqkTfiGmlk9AwlruqOyWj1tvHeDnCUQSOQKgXjN\nuKispnc1EUVh49pchq4pg9cAAdsWZpBTUQW3h1/fjzURcDrqh3eDwMfME+9kgXYbEKMTVAjEknx8\nfR3q+5gJc8zvZMVcHsHuH7R2A4+La0iTteScWNVgHy2pDm8SlC9kPNT/0SivsBLK/xVYb1/IcziS\n3SUZjuqOnA1cB/xvyOfBs3tsWkS80rlZ71qiXKJLjeo9vQLp8brjT1dnPmS2YFtWBprjB4SI8K+M\nZEEACNg4Olfl6LxEcLzzAerD1Rgxc2xaLZIwd/U9Q9yMdKxjnP58s757OASS3RNDDhLG6pHZgvOK\niwNDvqG39O7wycPDqSCTnLeq1G8ZUu+kpSMqQdu5gQPQ4SNgploUznJJpJURa6TnN8bzv4FXUH6M\nfUAP1Fy/Y4gw0/Yfltd9jUeqkwzrIpEZqgUSnoGNBbkMrTCsgIOwN8tF9z51aJd6cH1/Fd9qf8H7\n5s/UXdYMM3QTee6FeZJ3h5OLy3h+0nieSpvG5l8P5n3vNXSoJGg0QmYL3Fftpq7zE/h2FMIGY/tu\nIftMV23OooKrHX9lJc0rsGz54hTrpWRRqX4Dy/6BQKV38/uNg1IxCHkuiNcI/A7mdkOBn0smaQOp\n0dO5butiOBe4giArw+q0jIYGXURQIu4WOD/woqMFrW+NADVffw58Ef0hmyQWwegMaMB/gCOBUSgb\ncSUq7/K9xvOr4Tb+TQwHbm1aUyjicXeIah9b8Z9dJy38DMc+nc/v7MO34mgAKv8gcF1wFXV70/G9\nWRToskR7pzStlWGCiategbOLYNMpPDVoGsP7HMD3TKE6+aVAuzQfWdADJDj6+PjxoIOTjt5AqWM1\nQkCGfiFVzudZ6N3NdK0XufpoBsvNsOl66FoP73WAPxQ03aa5BXC+JHfIh0x2AGIgC727YAhUygwe\nPNnL595nGVBcwcAZHzBBLufWTQ/i6O0BnGh42KP/mR4L6wJi4gZfX8GcIfOg961cf4VA/lVwfeUi\n9RuEdHHCWRpWEWk0ZL4/yGGqW6iXB8Si55GB0ZhQ0QD4H8uu/t70r9QosQhGd5RVYe6nGFgDbAJe\nBKYSGFZts7Q1sYiGQzXQOR11MrvBUe5Dz3YilkqyJm0hl1JWbr6QNwafQW5GGWw3NnSjTvzQ8oKh\nnebuINMF5xUsYeXXE8gbsxqX3peVzkP8TRxgibdUnRlGBOWNupOX5HaqNIBCNrtyKa0cDKxGApXa\nC4CD6UufRPoKKXWsphS1rpGzL7ovfk8h3AtlSH9nebrWCwCJjkDwEufDJYWUnnoLZb1XM1U/Gtd/\nr6Su81+RN3vo+fUt6OmdAvtMB8fPJSMda9mx7ThWal/S58kd6rtlE5TMONJwbMTuSkhBp20PZ3D8\nVTvZ/YSbHpPqovvOccYODW+E1hKL1pgG3SCPpjlEapyMZKvnD0YNZFjxpzw8aRojWUvvuio6fIjq\nWFbjL+wjpUBskIGYBStmpqy+AveE3Sw9YiJr5ZkscHhBQK4+ms+cq9B3FAHwZuYIxmp5/mt/vN6X\nFdqXDb1f8cjl2QT+QzhgltfJYDYxURuoRlOEhz2+Lhyzphb2g+OSd2HqqYj+Ejm7yC9dv/RdxNfz\n+yEOSPSPtGbFcUT0B10OUgg0dHwHHCpB8fvq49AExY3FZ8Tq9LQFIwyJFopEWBJN7TOcYEg3ON/O\nR3+2iI2Tc5kr7+HdBaP5YuZxDHBehngzn6JRc8hfeh8PT7yCwWzGJWsZ4Lwc7stHzClEINGfLQou\nYQh+M1qeC1q2B3mfB2YHRjWE4UeYpA3E9f3V1P69R3TditbGKlIC5unf4XEeyW79If4pT2eSIzcw\n7V4IxP35+H7nUPNRtspA5nEIL6wQ/M8LN8RhmVuzrTADJOTcYJR7BL8AHaoJH9Bl3W2yR0naHYkU\ni3gnVYmWSDEAogfs0h/ip9/AMG08c8U96L/T6P1tFRn1FyDHFJF/6/3IP8zzlxgUAhZ6d5ExczsP\neXcjhxcoJ6H1zmj2udOVA1X6gDlFQceWCCZ98gpyiofan/0FLknMPJGYsVo0EvK1o/BJwTU8xmRt\niX+5ua6cU8SjWVMQ+2VAIKwO0tD4k1NhTnERZMPzxeMaLTqNgFIGqpEYLPtIB85N/NwWowlJIeUs\njEQJRaIFork1QEw6ZxMINoKAf6I/yD6C87KKWeHYHryRAPp4oLwQEBT7PuUPzlzVtVhprJMOjkIf\nfFWIEJLd+kN8Kgcx1hE6opGgbDityakeeD9E6ARk6hdSUTwg0F0LV/TZKiKXwTdD0ui5uYbiweeR\nRSVDPWWBrotlhGTVpBGM2fyOqqWSg98PRTXwDBxaCZ9ECEffh21hxIV4i0W84iaiOU5L8N+JtgAr\ngC9RXYoajEFwyYq8ixpuKIHyIv+bF7kQ39GFaIN1f/yE7CaQO4qQUvk5HpCzOUcLN/zZxsUC4P2i\nBosEZ7Kz8nh1UvUHbZlO/Z8IjuGwioXpfJZQOPgmJq9+maGbylSd1zW6GrK1WCTrxBnUZ4McLqg3\nxX4bKqba8Gkk8ryzBSPOpEruRZNGQ5JrQp79ywVF79wUYaPAhb5C+xLq4JfV29XdLgdEdiBWT0qY\nr3kT7adMIpYvJgSs8jBTL8HZ5xHIhm/OcuErn8cRbg/aY39v2CUxum6zz5pHzwU1dOEgu0anq+U5\nEt+OeUyb9DDbphth+TkwhcXc6ipiTdZpdFiAsuz+BrwfWzRptBy2ghGvAsixpN6P9ZgtoUE/1zSV\na9Sj/lzYN+RoPM4jmzZcpQRZQJXzBfYOcbFs8HgcvT1k+CzWSbsVixCkhDGFzHd48cn9TMt8mJ7a\nH9VnfTy84XskKJL1pzsFvy1YimOfmiAj5xRxg+MXHPvAQRy9PawelYdAslg7wMViGbPPmoeYKKmV\nLu6vyOdo6thb4Gp2GYRYOSwFIx5dkGQ5MBN6TDcc0cVDT20Gl3u7RnexCwH3eagkk80MQUqo0mIP\n227TSFikHVCWlZDM3H4nZ9SVKH+RYV10KIfefIW8u4j5mtcQX6kqskkYu/lt/6JSx2oWOL0UbZ/D\nKZvLoBxOWVOqHNFmV6eVOOwEI14WRVukKS+6RJ2gPilY7DwAnBDdjnvAMMdv1Ylv7uhwJzASS6ao\n5D7XzMBoUjo8OmSKYcCF5Cw1+m/yJKt/RIlyH1Gu/BVbgfWwTpwJQ+GR4qn+NeOVHjASh80oSSxC\nkQoC0ZI2NFn02MQNsocgffEuDi4/hpkT72KBoz7KowiY64F7ClrQwsOLpbKUCXUr6FADIzLfpMTx\nIVGpq4CZege+4jheXTgxUJltH+rE2A+HjDiYSMFbJv/r32PLaPeC0daFAhIgFhA28a7sK5hTUMT5\n8iXWyjPJ146K8mjtYIi0FRDCgxAFfKE/Q3/HZVH/ZPN83zFVLKKKDIZeXxbsdwKoDgRtWQknGrEK\nxmHXJYmGttztCEe0AT3igOT+TfmcsrkUT4ej4M1oK43ZYhENUhYhpWA5E5r3kwkow8grEkpjYeYJ\noF0KRnNHQJIx0hENLW1P2GxNVqyTmkKcZuI1Cevh++oiZo66swVHt4mMREq4QxwFzzWj7KOEMw2n\nqfZYPvJkgTZHDwoJby3anWA0twuSSgJhJWHtaiQ5jZUjdkgeqMpnj+8hpvi6kusbA88VJKpVhx/T\no+8V5DvVDN4/Zc3iXL0fa6afhvcfwRPNE+3sNGk3pRLbi1DESqPWhTnHw0q4H8KIPhTpEiEkA2Up\ni7UDwOp4NvXw5mD0c2ek7wGGOa4C9qGvmoxYKf3T5huzLhJRUrFNC0Z7FIlYEvuahB0NcQM3obJZ\n/QYVSmxJAmOKiTYznx9qithxdAbHa5cB1vlXtq8ifkQ3OpKrj+bTyuGwA246qxBxvQzOPWpgTaKT\nSNqsYNhi0QShoyA9oMuIPZAH3z7aXaV3s8x2lMMFc84qxFd/O53ccKO3A1J649kimxZQ+WOWSiO4\nBe573xMkFq3puzBpcz6M5oZzp5ojMxyxtrHBfJFwtUq7wBtHjGVqp0V485zBCXfd6vP52u1w0xP4\nNhaogbf7m+GYs4krQsAGfQXzOt2h5ouUG4Wpk92uJB23WXEY7dGagPhmAYdAV0S+LLhpcCH3v5XP\n6rPyOP3bd+lU+wPjMl6iqzzAU5XTVfCPGxU52A1kF9CGegK1P1ohu5VNMOIWDwvvuoKRrCVnU5X6\n31QTHKgVkiO0qUproT6Mdh+HES+xSLWh00SJBengnOlliliMo9+P/FJUcaurCPnvDnSlmmliEfVu\ncOz3oZ2cT/25qHofXWG396HADm2xaHXk3UVM13pRSWbc9hnvcz1lfRixCkVTP1S4z+PtUY72uM0h\nmgpa3l5ORIFEH9YJ7S868l5APEGmXskmhoAL5CUqiKiTWzk5q10u5stZgO23SB4qTuPiH5ZxMP1Y\nNfdkG/5kyf6Tp4aISYUT7fxMmS5JS0O4w2Uzs15U8fjxYhGSeCh8UyLRYFSkPzATVg3JQ/oEYzuM\nIK/+ZEo0S+JNS5djoBzNVBZxw8dPwkkpmirvMMP89wgBvjvU/0QiEO/LBt0SCO6ahDvnzXO4zXdJ\nYslJESoWPY+C7Gdjb1O44zSnKxPPrk+zxQJU1qwa2MxgzumQR4b3As4XLwVvaOlylDpWM93RC05u\nmEHKJjmof4+Amz3B1ejMqmqN0NLiz9GQtC5JQiaFSeBB6HwyUK5U1/zx4mWmtZb/I5p/esQ5IhJ4\nBm7bsICa+nk8mJPDyO1rYfxOWBHGgvDPsLb9FimDALHzFtZnnIisFJyYtZ6lciL9yyvDrh5aVS1R\nXZOkdUmWt2CjcBdraDeks5FBuYFXGThU3nohtNESy90g4gxUy/wQebJApEvkMHBc5oP37C5Hm0EI\nxEf5Rm4Mpeo/HSykw7WEHTGxElqrBGOTNt8liYZI5n3Yu70kYL49ibp4xgF/VR/3PDKxJltjmMe2\nPlpK1DNQa2RgaO49u8vRppBSiYXRfXQduooO/wizXmjxo3RlZXfOjv+5nrKjJDHxNIFiwacCw4C5\nwWZbskQjHjQ6AzUcNcp5hpCM8/ZjpVZL64wJ2cSMIRaCOSzr9JtAqUoT08oIDdbrD/QBbo9vc1Ja\nMBrzFzTqS6gOPO9d6AqqQ2lebPEIq/VfuNkE6k/Ead9NHjMS7jCv3cB+kF8Vss2RwdW+rDB1QmxS\nFVUlbhKDxKeBIVWTcDPMuhvrjIPOr0HPD+PXFU/JLklLRhgi/SDvkBco1mMhlipRnd3BgVLWUnYN\nPo8jUYuFWTndTDprtEvrk89aOZJzrvxn/BtnkzCkhEnaQI51/DFo/s95xcWBkRPr0Fw2cC5sPC7X\nb4H0PLKdZg2P5ktF/cWzYeJbr7CtICPsRi25qBsMY7qBk1GOVncj68ZAcwVIZgsGFWxAe1RXJ1M3\nkP0EPp9gurMX8qpk+bptWop/FNwsnO02ijP31dn4cG5wvZNzYe9gF0OXhMnQFSPRnDmLgXNQo/tm\nGmk38AKQAVQCFwC1xme3AFMAHZgOrAmzzwajJM1Rv8YqlHd2E7AozB/XjMU3cghESmsWqSsR8WI1\n1T0d3Lfspubunqr6VHn41ZvbVYlKJEzz1ByfTwfHPI8/6EcIyRf6MwAM/f4D6jo/0bxG2KQMQsAe\n35/Z6cvi1A7jweWBukJm6k4eWHKH/7z+Znoa1/Ior0yapM55S87PvsauWtqGaCyMvwFjQpbNBd5C\nHX+d8R5gAHCh8TwGeCyaY8RDLPyYF9DlqAs61BlkrpNOA0dh6AUa8c5ubnszMAs2FuRS/USv4FqY\nYZyQzbEUmiUWgON7H9oeHS4z7kYCxun98PkEA7TLOJs3qTvSFou2zlNyGqc6xyN9IA+qkpQvcT5X\nTFqobhr9YbGYwsvFk4OLPseJaATjPeBgyLJxqBQsGM+/NV6PB5YB9SjLYwdwUsytjBYB9W+jRkaA\nny4zhDScaJikBz/MizriBWuJdZD7Be5jdzO0skxNPQ61IFogHC3yf7hB76nhvdrJsszxgIDhHlZo\nHQGQPqh0vAByYjN3bJNKSFT1+KCERhKqtC0sevB6Vo9S/rr8392P6CZhJqqr3NgIWjNp6SiJ1Tdr\nVkcAOBZV1tdkF9Az0g7iSc8jAQkdfoWyLN6Hjm9Iti3NIGdNVfiNrBmnTC4FXgv53Ipl3fTf72LD\nEcNUFyTcPyXckJdJdQt9HKH7MvYhalQmpgvTVjJRDCSvZDUl2meB9STA0hYc0CZlkOFjcaXvM5j1\nGWNmjwBOx/Ex/NRH0OFpEM/KuGYWj8ewaqD6buTPG7DY8nqQ8YgbNagLaR/kFFexemIeo7eWBGIz\nrKMaVn9DNsqWiiYZYnc4UPILOvQ7hO7uFBCaBoWNw2wbKhRN/UMj3SHC1El1fCARwM3iXkqwh04P\nGwTM0p3MYj5l4h1EnUSkwzu74K1a+DbaulRN0FLB2Af0APYCx6AcogC7gV9Y1utlLGvAlBYcNGqr\npNp49AXKYdTmd1Unaj18MymNXkur8Z7ixDnbizfPqe7OEAh4sR4w3Bi3gWODD31bJ8gBh/Axc8ad\nfCWP45XNk9QKTxMQDNMOM4WqmoATtiWEioUbv/D4VhVCJWzw1XL2D29Q13kD8Bk27RMh5uD76ihk\nsUCUS+oKKuhwG7ABTjoEJ3WE3YZgPBLjsVo6rLoSdU/FeH7VsvwioCOQhbpnfxRLA2OiGvgSxDOS\njVm5MByOKa7F+3Mnq487jcKXbmLf9KMDMQvDYdtZGQ2GqMJOP3UTsK32g9ftZK08kxXOL/lmsAvt\npPzg+A9jfPyDhblo8/LZ9rBlqLcFPotGP8uGejekUUvdkT2wxaI9IaBPcOpEKZ9D65Ov/BbVkDOp\nCtYDB+J/9GgEYxkq13Q/4F+o8Yd7gLOAL4EzjPcAW4AXjec3gWtphSmQjYZ571OtGXpDmbrjl4NY\nLxm96V3yr7qfAT9tgeEqCe7swUVqwMlwVq5amIc28+9RXdDO3t9SppUhx3k4/qctfOZ9Fu0lPUh8\n5HDBYseaaLOQAAAgAElEQVRUdukPkVNcFVxEKFrRCLeeOdGsj8DxqI+NWbkccKVxfNV2kPb8kfaF\nRHxVSJH+ncoq7htNsb6K170lSiS24h9KNYnnhMtouiQXR1g+MsLyu4xHXGks9iIi5o/mRkmY5eIU\nNZItf81CSnD09oEo5JfeCUxjEXdOnsltmxYw1jkC37O/j+zUtHRhfjx4FJtduSyXB5ivefmfndvZ\nXdIVFlrWz5bkUkavZTUqa3ck30Q0fg8To5vjeMwDUz1QoCqpK+L+b7BJASRQJ9KQq+6AhYXqfCkn\nMIM1gVMUUi7SM1ai+pFq8HcX+m+u4nX9DB6q30WV80Wu4XHOZB31Zncix7KdG64oWBiwGtJh7ygX\ne0e5OOJeH1fzF+YvuA2A0zL/ycviPH9gF+kgqmECy3nv4sE8P2lcoCtkYrU2Qh/hsKjojV4n8qki\n5GQ1Nm8+bNohEuZrllSK1TRI5ZAo2rRgRD3jNNwPuRJyllRBDQwUpdSJNDg3nxLtEPdyE8td42Hn\nrdw5ZGbgwk6HRQ9eHxCBbipt2tXyMeRWQZljFcwpAgkljt7coP1CXeyWtGLpdXV0oZaJy15l46Tc\nQHi5+Yh2Io1VXNJhsNgMN+fbKnFYIBB42DvKFfc4i6aPnBzke81YubndkaCJYVbMERBzpCIb6AbL\nRo3nCd/VgZyX/oSKMNXXlcfrbqCDMfy6dUgmx1duh953sUV/mlrSqCWNsx2nN7hYhYDX9RLOyfkn\nW7Zncbx2Ka/rJaxlJPPn3oa4r5BivZSLi1ewbNJ4buVuZvMA192wOLq7hfV7ZgM58M3gNM6Wb1KW\ndzK8byfLab8IQAZyfr5vLA7TJbH6MFojNDwliTmfhYBHJ0+h/mSY5BhIidMSb2Ze+FKw6Lzr+MqV\nQX027B3iYtgPG5DH3YX0CZbLCQzTXmQ5E0Ce0eAA8iYP6zgT+WURA7RL+WN9B8Y6RjDf4YV7lTOy\ni6hF9oNJzoHcJW7huuLF4WugNoH8OWgn57NYTKHMuQret52d7RslFvqHITcFy+zkRNAmBSMmsTB/\n0D5w7cd/44guHn7lHR1hLEfCiiIqyWSzK5f5zKLoiHxjXUn+mvuRvqUschxATakJ2fbe742+pkRK\nwVeit9IiY3uAr+iNty+87i3hCXl1oI3W50jfw0o2+KSggqyg/du0X/QdhYjtxhtrV9boMicixUJK\nJ9AJR9RiEWFUQ2YLtBydPaO7sJwJ3Kg7WeDQI+9HSubKu/k/bTVS6iB6mR8gxxSaLyNwX+AzCSsc\n2wMfCYF8Lp8n5Ubcrmqq6coQuQlHiQ+xqAD9QcNCsJiYkb6jzBY4+nhAShaJBAy+26QkHx6XS+Zx\nlSxnAtd7FqmFblRk1DPAyoY5PWMl5S2MuM05MedclEv0f2v0qKjjuopFzBbzydC7NrKhoEzTA+6J\ngIkQG1LCH4oo0/Zwr5zL9FueVNbIokI+159B+0Jn9fS8wIzbMHkbze8lkGR4L4i9TTZtB6GGz4/t\ne1CViPgN6oY4TnDer4up/1NiDpvygmEl6tT7obNTQ0wzUS5hvRrmPKailiqn1sgeJci1zW9sNEgJ\n8jPKtFXIe9ToCqd6qBNpnPDER2SIyuB6DKHT8i3f6wPHMBb6dpOrj05MW21SC3PovFxVsKMrMBz2\nnnU0Ly+ZTIcJiTlsSndJrNZFs+t0WF9bd2QVEyM/4gbfBVwsl1H7gys5CWYsPgfZVzAs/VPkwUIG\niMvYoj9Nzv4q1S0JN7s2HciBY8UMw3G+ujVbbpN01Hnj6O2hyPcdtaTxQPUdMAe4Iv5HS1kLIyFi\nYaVbYD2BZJaYT97PShrZoJVYVIisUdaG9MHx2ufsneQKZNQKFQtAmstsP+dhiuByvStr5UgWaF51\nY5lrR3pGh1lb1EyMGvqZedEZvoFtWRksZwJ3/FDESsc2kp9+P+AjEQL+XH8jL4nzA6M7od2SbiBq\nYKae0saiTbwRgedc3yierJxBydIxyNcLVExGHHNghDtsaxMxcKs5lkXYuqJukJcLPpz8K4Z+XAb/\nCCx33OhBINE/KmLPiWn01GYYQVoXk5rJZQR0yUfUFrLF9zQ5FVVqzsB+Ar4NN3wz2MWx2h9tC+Nw\nQqiyEawHWa6mtbMff2qH0KCtdlOM2UqkzoA1bV6jKfSMZWdMfp0KMtn461zIVjk3GQ5HH7qKh/Td\nbBySS5XIUCtLSWqKBYCEg0VIYC53c1zWF4FQc/BbHZVk2mJxuCFB650P1SCqpVKEVqiLkzKCEWpZ\nmBnAmww+CTPk+M8bzuHi4hWcWPd/OE79kWHO3/LI4Kl8e+QTVIpMbuFu1omRZOgXQZonzE5TCaUE\nKyrPp1J7US0K8WVkUqWqfNscRgjkzQUNpxAkqCsSOGpykO8R3qLI/iVsq8ggZ7CRhzPcD9BUyjpr\nZqtslYdiUNYGysSqwLoqFL8NIeDmfD64ZxCD6wL1JqpdLj5lEGOFnY7vcMMh8tGf1VQ39VLgfBrt\njkAb7pKEioW/OLEbckZVqR/AdE6aRMjCDQSLhZVu8HzWOKbxFDxnuQu3KbEAFWpexGYGU+1yAfif\n2953sYkHPjkP7RIdsuGKrIVxLykQjqRZGF9a3lidm/5CRANQxWQl4ZPrhhIqGNYZqTnwU7bgiC6e\nNj/9Wzhgnv4fzucl0qjloOzC8drnSHl/sptm08oIAV/oz1BLGhNZSsWJA4KKFpm0CwvDtCgaiAUE\n5k8MJ1ARpbFkMuHySFhnfKarWIuZuhPeLIjXV0gK0gd3aP+PTxlIJZlUkgHYYnH4IfiVPoZTfvyA\n4emfUDEyvFjEm5RxejZwbo6FR4dMUV2T0G5GaDaqMJ/LcwUnTl+vckRkpnGE++8s0E6DsW0/R4SU\ngkmbX6GCTGpFmppta3N4IGCqL50fDxbw6dJhHPz8WBYeuCJiec54RxWljGA04A249qG/oc3WA7kz\nuwc/ZA5ob+c3FJB0EBskvxMv45jpo+fmGqTvc5Dr2nyXBFDf4aRCqunKV/ShVnRJdotsWgsJi53V\ndPwIxAcSsUBy/Sg1UzWRw6kmKSEYYQOw9hkzS0doMJSAPwL8wiD6wvu+lWin6MERkMbnt6+Zz5Z3\ns7jxxDuB9lUmUEpBKQOplJlUaS8kuzk2rYZAICEbtDJdZQj/snXEAlJEMCJSgypkkAPbCjKQ2QJO\nJmi05JSKUrz9nMH+DctISu+6KkY61pGn72zNliceKdnEEHbQp31YTTZRIpGyB6uyRvB+yeAGnybS\nfwGpLhgGEsHxt+1kzaTTGjgzqYYXhpwL6SD7C+YUGIlnugHZsOnogZxzxT+DU/C1Eyp/zKT0x9xk\nN8OmtRDqjxx+Nedop3HKqNKEB2qFkvQZSxET9prUqBoiu+5Mp2e/Gn78WGncV64MetdVca9rFpsY\nzMDJZRxX9zVSCDW6gqr+VSvS+OUT26h6KuFfpdWp+9kTcL8d4XlYIGCgPppPi4fBxgI4AmV9Ez7Z\nb6JIumAAUSUt7VFZy0Pbr+RasZBcyniJ83ncdQ0u6njnh9O5ptPjuFy1rNx8EUMGf8wISgC4V95E\nlbOd9vElgSKVNu0bCWXO1ez1uujxRl2rzBsJR1K7JFElKXUD+0GUwwSxnHKZzXRHL95xHOIpprFO\nnkHdr7rzjvYR7/wwAnlSERO12cohSCaD+QSGt9e7sIT3DyW7ETaJRqj0BdIHPYrrwo6VtoZ1YTQl\nKUg5kIAvwqqSbuBU49lcPly9r8+GI7p4VEoyJI1NCBEOyNVHU7ppKPy67cde2By+CAds8L7K0IIy\nVe48jHURSTBCtaXNRnqafgagYRBWDWycnhuYS5INq4bkse7oEfzSexGcmm9sGHl0QPqgVFsDJ9n1\nOWzaLmZNrZc4P1CsKJntiWKdxcA5qPQcJxjLCoBpwL+N97eiqrUD3AJMAXRgOrAmzD6l/BhVbbqc\nhhaGMRKizcxnvb6CTCqpJY23fCOZrvUKs7vGuAm4r5nb2NgkH7NQkXiQBoWWIXUtjL8BY0KWSWAB\nMMh4mGIxALjQeB4DPNboMcwapeECt9Lhf72ncA2P8xTTWMtIZnzyZBTNDcUWC5u2R7FeChLEM4Qt\ntNzazk6TaEZJ3gMywywPp1LjgWVAPVAJ7ABOAhoGQWxAFZLdarw362+U488qtUxczFNMI7/qLo7u\nUYNMmsvFxqYVETDx4RVcfN2KVqnI3hxiGVa9AbgE2ATMAmqBYwkWh11Az3Abr5qex0HSqJmRznUV\ni6Eanh8yjosqVvKTW3Cd6yFcso75ebfBe4XUxdBQG5u2gM/I0SlqgHLjOYx1kUxaKhiPA6Y3cR4w\nH5gaYd2wnsmxjhH+1zcQGPacyCBjI6OzJm2npU37Js93MnO5F/EWym6PJBCtHNUZjpYKxn7L66dQ\nKW4AdgO/sHzWy1jWgIvveJof6MT3dGZOv08ZkS0MC2OFYWEsxCVrWZB3G7xvD4vatF9KtA95lxH4\nvipRV5abyKkpmykanxqPeBGtUyATJQrmKMkxwDfG6xuBX6Omgw5ApeA+CdUVWUsgb5YVKf8MjEON\nK9cQGB0xfRj94ZtRacqHUXknrh411H3WHU6yxcOmfSMc4FtQGBhBtHZLIkR4ttYoSTQWxjIgD1W9\n8V+ABxgBDEQJQQVwlbHuFuBF49kLXEukYInhBH6QagIJQLKNZfthIkupJY15mbfioo7poiWjJDY2\nbQvpg2XTxzNZy0W/tij5tbUsJC/SM4o4DMdMDxv0V8mkkoN0Ya0cyXRHWB9qI8zBTmFn09YQxpXp\n+7AQUigOI3mCcR2BIVUr3YF02Lgwl6ELy5R4DIdVWXlIKbhGPk5VXr/o/BoCkJHDx21sUh1TOGbp\nGvePzA8SjWQIRvJmq64n/FwSgGyUWNQYn3WDMdUl/JQNVV2eN0SgCQTk6mMo23Sy7fewabNIqUTj\nfF5Sc6xWJrc9yZtLYvotQsWiBhUz/yEB82u/Wt6xBoq8/4U+5lySCLNQBczSnUwTT+E6IYU6gDY2\nLSBXH83QyjLl3zO77EkiaYJxqMYwqcINE1k9w+X4s2c9kjWVKkcGC7+8gjzfULbI41god+M6dDV5\nvqGMkzmIjz0s1UuZLR5gAssZeEQpyet5JRoBp7bXqfs2JqWO1YjeHhyXeNi71uWfUmFND9FU4fJ4\nkRoJdKppUjW/yUxjer8n+eEjDYCzXGs5ru5rXnK5GPGzt7mHWziu7mtuHbKDiytWACrj1s2ue6nU\nH2ufiXIFMF6kxCxGm8STq4+mR3FhYGZ3tRKN1pxXkvScnodCxpfDYoSJye6Cjh9KOn4o6V1XRcdy\nyVQWsbLqfHI2VdGhHBasvs1f8r5DDayTZ7bfjFvPedQAt037R0KptlpZ25fB6rV5/ptsVImo4kTS\nRkn+G67iGQQsDTM3hhu4DLYOziSnuEqVtq+xrDMOJRDrjR0fFIg+xqhIN6g/GdYePYJ75c2UaO0r\nEXCuHEOarKXE0b6+l03jCNGd1/UX6SIPMnRwWdgCzCbtJ4FONJgJdLZB/8JKRLkMOEMNi+SDrIFo\n23X/iIqQMsha+cqVwTrOpMTZu9Wbn1CEYAib6M2OZLfEptXZx9mV7zB8xCcNPkm0LyMlBCOoD2bt\nmoyDvTNcyvF5wPKZOdy6Q9Ul2TUxvWGXxnh/UKYxX/OCLE5M45OGJI1a0kSdnTn8MEJM8/CF/jSU\ng/dXTv+oSWt1S1JCMMJyOTx61hS67/xWCcZW47HP8tgCchocM6k2MKJiPORwwaBRGxi6+f/YrS9E\niBOAM5P1beKLEIiPPAxmE4PZRO6sD5PdIptWQi4qZD6zqD8ZxFAJ98Kjn0wBWkc0UsKHYRJUo2QA\nalrbazQcBQj1CocryJwNMl0g3BKGw09dBLe45rFg9a1wdtsO5BIClvjKOI4KDso0xmoj7GDWwwmh\nLtxc32gqf8jk257d0QdqUB6+ervVj9FmfRi7/9tIanQ3gQsgtKsRbgjJjNuwUo1ykAKUQ8eDkgVr\nboMbBA3Vpe0gBBTp33EcFaRRSxdRy0LvrmQ3y6Y1kSoCtFRbQ8YRVciDRf4pFZBYP0bS4zBM0eh5\npFLHzmbB5ZOBm43X5kzWpiLc9tFQC7rBslHjqaYrrJewo21bFwv1XUwQy0mvq6Pa5SKNWg7SBZV6\nxOawwgefOU/C9+wpTJv0ME+NvMEfl9HzyMTUKkmahRFqEPgtjhrY9lYGPE3AL2FiGR1pgGl5hO54\nP1xUsZKnmAaXtOXsXQJu9jBEbCK9TiUsNJ9T2BNlk0CEyMf7tBPK4cmK6a2Syi+pp5rpu7RyqBpy\nsqoaF4fGPoPApDY3qvJ7VgZlq4daqpyfQIZ+YYvbnRwk4r5CMqmkQw3+R4+KOg5KV7IbZ9PKCAH6\nmxqiRoURiCsjO7Hi2QFPiXuTVTh2/xfKv4byf1nmm0SimrDicfrDb7DskfFsnJGL3C6olS5kusD1\n/VU8LHfxgZzHMjHRXw075REwXuaQoV9Ij011gdEg43tnUUmb+B42cUNK0M7Otywwni3d9kT4MlJC\nMEwizSs1hSP00QBj2T+XnkMWlQzdVIb4UnJKYRmsh7rOTzBD68nQTWVkUmn8yBKVXTAVEZDmQQD3\nMJedlcf7s5EB/twImVRhD5McZgjQdxQqgUgnEBVNYodXU0owIFg0mnLaBAmHxcpwXvIitTJNDclW\no4K+asD3ZiHep4tgG/TUZhjjkx5UGtJUQ8D/5pNXsxqAnIoqFelqioVFNHpU1sHOW5PSSpskIcGb\nDj+dK3CM80E2PLp2SnAJUgvx6pYkLQ7jvSZWsH7BaEyrIFXNtry27siaS2B44P0HWQPZxBDWyjNY\n6agl+UkUA1nCHI7Z7NZ70WNNXcMMZcZ3kcPA0cdjGxmHK0Iw1ZfODtmHd50b8d1WCK/BofLgm+4+\n2nAcRlM0x9KAkC6K9XWka39/8HrzmUXJjyMa2aCVmFoAXVTfVAj43HsCPZYYYmGGxJsYVpWowRaL\nwxkpWawdYKRYy0yvU91I7klM1yTpcRiNYQ2r2P3fpi2NQzWWH8k6o3Wf5bU194ax7jDHiyCXkgpX\nnfhSsqFmENfwOMu4mJw1VcFDyxD83aqBbbDH9xDLxQQW+aZQpq1u3UbbJBmBXl6I2CTZMzhNnRcJ\nKimcshZGOKK2NKyjJqF3ZVBdluFAOuzNcpHh1YksFoLEzkE5gVx9DNxsTCB7v5A0aim7ciiVZAaX\njAodFbJ8r2FsYIajJ2VOWywOC8wRvj4eQKrh1fVwzNo6zvtDMfXLE3PYlLYwIHzwZoswLi6ZLdB+\nrrMnqwvLmUAlmVQ5DzSyoSRX70CZFo9GWBACnssn9+IPuclxL9V3d6XyrgwWXHkbx2sSfYGGeEg2\nSC3vx7SUatScmUrthVQwkGxaCwkbfK+QyZ95ifOhQC0WKyQvd5mMeDoxh01Zp6eVUMFoqmsSNInN\nSn8Cjs9s8PUTaCflq+zi2qrwOxPwpu8dXNTxkjyfDKqY7ugFSHjT08RENktNFAHjvP1YqW0P7FrA\nQt8urqpbxDpXHvfIubxTfHZwYadIcSghSYd8lwq0PvlM1buyyNGYANq0F+TOwkBtH/N8Me+w24AD\nDSej9VVP7c/p2Rgxxcibd+wd8PivL+fHg0WUOSOIBQLGecikkiF1KrHwHT8WoW7lgqLRN4GYyFRf\nVxp2WwTc3JmZutN8Rx/xVYMj9GEHzi9hrDaCq8RfAm20Pkf6HtajlUsEkCkqG9nIpj2h9cmHHOON\nNfpxK36xiDdtUjAgDhNrJFy3ZDEdP5IU62Xk6UMbriMkU195lN51Kl9oj011bOg0HHbeihCS34u/\ns0G/kPPFS8C6BgcQ9xUyUqxDZKukJw86vbzhK2Gmz+n3WRwkDfElLPGWcQv38OikKUrQmvnPFv8G\n/cMiprJI+UTsbOLtHp9PWchBROrCxok20SWB8H6MSF2TRnOEmpxsfNYN9o5y8aTvCvJ/dx+sfJnx\nvp+4QL7A5KoXKcq8lduXzPdvpu3X0f9HU87Ibqpg9DXyUVb8biK8WmA5wEQQS5EfFSJrBAIJbqjP\nhh1HZ3L80p2snziIoUv+D/GhxfkQaRJdpB/E6GItKxjPpFtfQd7Ttmfj2kSDQIh8dutd6FHxLeIZ\nqXLGRCjYfNh3SUyitjLCjUePg22TMsANpQwkTRyElUXk6Z2Zy71M+HYl9L6L2zfND5q7cfmNjymT\nrxrYDwLJ4+I6yIFc3xiVLk9Anm8nD+u7VJ7R/YEIzWqXi1pcFE88j1OKy5R32xzJMfN6RBMKYq5j\n3FE2MwR5T1uejWsTPRIpizjmrTrEazKhFkUoTQnGL4C3gS+Az4HpxnI38BbwJbAGSLNscwvq8toG\njIpnY6MhqmAVN6qFArYNyWCs421mOHuRoV/A4+Ja1nEmHculclVss2xXA08WTg9c3NXQY00dx6yp\n5ce5Dh4X1zBz1l0AvPv1GaqrYoqLMaqxnAmcuuwTLi5eEeysgmARCH2EwyIs8zUdpnlgSUEUP4BN\nm0bALJ9l2C5kLkkiaWpYtR6VKK8UOArYjBKKy43n+1BpbuYajwHAhcZzT2AtygryxdrQSMOrjQZ0\nGVXgAX+1KBPpFgy4sgLXwr34vnIgEcyWToSQ3LZkAZTD63oJmvZ39Gt/779ohTl2ae53q9pvpy7/\nAZ6F315N2qG9rO/Yj57/W4MvN6DJcoegLCuX3RPdsISAr8IqFJGwBmuF/jCAvLYAmV2IVqSzQb5K\nBlX0rNyHzLqrkZ3atEUE4JK1OM72kKuP5iZ5L25qGV1QEkhnmaAiR01ZGHtRYgHwHery6ImqBvKM\nsfwZ4LfG6/HAMpTQVAI7gJPi19zwNNo16Q70h40P58Kl+IO2Vg85jaIn5rCl0wBYD2K95IFPDAeS\nYRGcPf0dvAsuiMoB6d1xNL/yDoJXC/niiAGc0OES9AlakJUg1kumyMX01Gao7lC0YmEl3HrGPkS5\nRL9WY2hFGV3ravk8I4eI9Wdt2igC2duDx3kU0qfKKE7SRjPWmQfDUDfGkJtjPKe5N8eHkQkMQlUG\n6U7gVLfe/I8FrAkmd6EEJjkY8RZchipm+wF8MykN7d86oyvexXP+ffRYGMgvITZIFYpt6XKI12Rw\npnIT64XbDZwHvYx0rGWc3pdjN9fi3VikZpea7AO+hFOml+K9fR45N1Q1XyzCHTsEUSOhXCXX+VYc\njevQPuCEZh7AJnWRsKMokAtKAlyCt7wI/g2kw7biDH8kc7yJNtLzKOAlYAbwn5DPJI3HGMYt/jDq\nqE+zKyKBvrBqSB5jHiqBcjjm9Vp0NPgA9BM0sBYNEwQCYcxi0JHorkoZOPt9j/7vTuhuS2RmueGG\nDt2H2a1xy+DlTTmtwv3jawjuntTgr+6tnZ3P694SzhYjgL80sXObtkfwJSXl/WjZHmbqTmbzAJVk\n0vtPVXT4B3S+Nb7dkmgEowNKLJ4DXjWW7QN6oLosxxCY8bAb5Sg16WUsa8Biy+tBxiNumBdSd9g2\nMYMxa0qCnYemtRDqF9iH6mxFOUqRnreLz4/Ige0EvNXRdDOaO1ZuXdcqHqGi4VZT3X1ScA83o4xB\nm8MB6YP5Di8L+CPi43x+6KNBNbzTEd7RoL4DfFsf+3Ga6pIIYBGwBfizZflKlEcA4/lVy/KLgI5A\nFuqe91G4HZ8DTDEe8RALvx/D9BibYeCCYEEIdzFbxWRlyPtQLMsPvNuLBcwMzBqNsJ6fCCkFm0Wk\nbQ2x0IaqvBglmi0W7Y9GwicEKqp45608N/h8+rq2oP32B4Z/DAUuuK1jYIgzFpoSjOHAZOB04FPj\nMQa4BzgLNax6hvEelLC8aDy/CVxLI12SaEMOmoVA2TtDgb6Qs6RKZSC3zs0I7Q6Yj3I49KFKPHKo\nPOQz66MGOAgOKflrxQw2DskNJO2xWi6h2xlESjnYaPpBK6EzVmvAsduH824dfYcZiyEY7+vn/00y\nfG0t6bFNKEJIivTvQhZCpu9Cpupdmf/WHfi+PIKdsjcVSwag39+JDhPi3Ib47i5qZOjs2+bMSI20\nbs8jjTiMbNQ3yzFel6MuLDNjVYS7dKQLNWJsR1/8Hmn33N3U3N1TDWuF5q9oYv+RiCqmxMy61V+A\nG0S6RBSpkRHhACSqFqcQnPL9Buo6P9G8RtikDELAbt9DVPgyGe4cDy4P1BUyy+fk/iX5/sJd38xI\n41r5KK9MmuSPAzInobWbSM+4WhtmBqqDNHQYhhGLpu7qET+XKFvqAFS/14u9BZHT/bfE8RS1tQGI\nfdI/QqI/WORvn76jkP6bq8ipqKLuSFss2jo9Pq7j1A7jeVjfxYaaE8nVx7Bg9W1qRM6wNnua3VGj\nqxxPp2fKWBhWorE2Is0tCZra3h+WFY/n4oIVYe/8Lf0hG8xVGYBy/4Y4M+MdNNOoxWENUusOsq+A\nLkagmSGajks8POTdzfSrnoSnCuLbOJtWQf650N/lPa+gmJcLJvtrk/jpC/wGNg7OZeiJZap7TXws\njJQUDJPGhCNSjoxQwdhb7KLHpDplvlgEIx4Xs/9Y2QSJRSKmFTc4ZighaQf9ocLpgWc5TLD9uF9S\nIbMY68hLXCNt4ooQUKyXMoJ3OKbAqHZXDfKgQEjZ0DTvjlKGS4Fpyi+3+7/xSQKc8hm3ItFoTIbl\nbtujuC44r2cc8QtDqg9I1ADp4E2XSCk4xzkPFbVv0xaQEiY7c9F3rFALDMe72GeMJ4S7QZnzpSL4\n01pKSguGeY03O0XfZagf8VLgCpSs3gOHTg+skohCtU0RjxBdU6QaWBqWlH1+K8MQCtwgfw4d0zxG\nBQNbLNoaPumgyzF7OJh9bPjAwlDf3FZgfcOMW7GSMk7PxojkEA1rNAgCynol6kddYbzGUvQ5CZjH\ntj5aStTdHktKQk61a5e0KYSAj4y5QFJS2/kJ6n8TZr1QsagOhAfE+1xvE4JhEpVouFHquhUVJWLE\nLOzNtcUAABxeSURBVCTix4sH8RCPxtC8OgxXVbKmlDyamIPYJASBZOGQK2GVx5/q8Z+uEN9ThFG/\nxLUpOcjHjBfdYthJ95DXPY+Czo/BoWsCy+N9IUbjColnteyoEx5DsOPzZrjzrJnc4fx//NJ7EbN5\ngBscv8A2MdoQQuCYewf6AA2JAAHiAxkoatXIiFzoeW+et22+8tl+gktvNId9Ia93fwfll0Zau+WE\nm6waz/UbI5r6sn7ME8gYITlRfMLr9SVUOV9gOecTSSxyfaNZ6NsdMH9tUgMpVRa1cpW6gC8JiEUj\nJNKSTrpgmLRUOEJFY5+Mj5kfr4s+Hvtp6ruENUH/AWPeKuGcDnnwTD4lmmVKj0CVSDB4XFxLH8rh\nZDvFX2ogWK+vCLw1utVB6RIsNNUFiecAYcqNkoSKRjRdFusP0p3wP1BoNyEZFVRD29lcrKLRWFdF\n26XzeXFvjv96O194+/EUHVjQL59pv36EqSxicF0ZHV/zIAT8UFNIx01AOuzxlXGsmNGCltnECyHg\ndd87DNtUinyuNFCjxpwFHSHRr0mi/XQpY2FEorlWRyQh2Ed8uwqxEmsbQk+MQ5YTqX5BBxaLKejb\nO1ElMrjr2zugm5d/05VFTKVDDfi6aegfFtLxH6jqaQegp2aLRTIRcz0s1HeRRUXc9hnvcz3lBQOa\n311JFVFoinh0VayYouGY6+O+SR7EVsnoG97lFde5+HxHUCu7sEhTtyaxVSI2oO5e+9WdbUl9aWBn\nz9n+jNZFcNpdq6kVacxnFtuGZKjJk9a5UCFmaWtbF5ACoyQtpbmjK/EcuUgUsbTR2kXpbMZemLiB\nHtBl8R4kgv880g29IjjrtBwumD2yiAULbkPcXMhMr1NlIpf2qEqyEA44+r9XUbvnWHgN1R2x1B85\nFBLFGU4wQm9IbX6UpKXEq6uSSsS1jaE5M/ZCTUlPDt59LN7TnIHlZu7S9ZIHHslH3FTID9WFTGWR\nLRZJRkrI7FQFQqUvmFNQFEgMRZTpD+JMm7UwQmlvFkdL29fA0rBindEa7kDWqNBu6nlvlou/+yYw\nXevVwhbZxIwQCLoB+/CtKlRZ4baiEj4Z1kak7ohtYUSgvfo5mov1xGkw3GZmC7MuD/cjVBMkLGWO\ngaqq23MFcWqlDWnR+4gEs1mv/5VC/b+cN6qY1Q/nKUuDxq2MRJzfKTesGitGydOoiToTeSuTsHaF\nm6QWhp+yBbceXcgCh47kALAKWJ2IFh2ePCzgD9GtWqR7GPJtKafUlOLo4+HlB99F+42OvjLgh+p5\nZOs4PduNhWFlP82zOFJtyNWkpe1p1MqABvlAgw5yLjAcjujiYf7q25Ubw/+wfRqxIgTM830Hfyho\n1nbrXHlQDr5rChEbJfr9mt+x3Zq+jHYpGLGSasIRK1FPRkqHOUOK+GBwLkXe/8BYO/IzvqhuyAQR\nTfooCwIGUsrGs3IbfpaAYkWN0e4FI9a5KqkgHLFaGdC0aEghcN+6m/lLb+clMQGP86goLQoBN9sx\nG9Gw1HceP9QUklNRRZ4+NOrt8rWjeIDZqtaMUazKn1GtP/6M9VaHd6LO23YvGCbxEI5kikdLjt+c\nPq2Qkuq7eiEnFTLfoRtaEUWJRQHCrsQYESFgoW8X83zfcdHmlSqydj2c73jJP2W9KaQULJh/GxeK\nFwKjWNkwZ20RjIVH1k1ttW7JYSMYJi0VDZNkC0csNGVlmJXpBZIpelfgs+h2vA82+FZEfQEcTkjg\na5HBzd/OR2yT/viY6zctQjQ2uGmdOSwl3FTIV/RR0Z/9geFwJutgI1w/aZF/1XgWXg7HYScYELto\nQPKEI6HHrIEfDxaxW/8zi50HottGSphTSJaoYLDYBECGfpgXTRIYggtIwfx+t6vEN+Ykshqoz4Yd\n9EbMDSTHQQi43wMI3hhyhn9nub7R3FjvJL/v/f6iWRtH5TKQUn9QXmtxWAoGNH8kJRLJGGGJ5VgN\nrAxrf9gNHf8h6bH5W+bp30UR3iNAeMjQL6THpjombl6B3FlIlfPFFrauDSMErPIwy+dE0I1FVTew\nx/cQIJFfFjLWcX1QFbyOt0leLZiI3kNTBaceKOBh/V/smeVG7ixgzJp3EA6Y6kvneXEx89fegb5U\nwyXrmJNVRC1p9Cioa3RoPJR4nJ/tJtIzFmLJ+hWO1orriPY4oWZqZ2v5AfAHAZn9YzkMtMt8yPcK\nG93vOL0fK9MvRjgkvofUvUd2EzhG5wPqGrrR62SB5o2ypW0cIRDiDPTyPMQO1b1zXO/jh48cdLhd\nqPohVszyD6cKvhmZxmIxBc/q+9DTNeR6gfaIju7R/BXNSIc5k4u4s85DhxrwdpF0+AilBG8C7weS\n/kZKZ2lUTrQjPWMhHl0UK61lccRsZfT//+2deXhUVZbAf7eqgog0IYGvA0RNsYQBRppVQKBtF0Rg\npkVGW2SZ/loFNxr4BBEaaCowNioKduPXjdKCCwZsRkRQo6LYYAMNTcDAIFtYkhlsAiNZaAYXUnXn\nj/te3kvl1Za8WoLv933vq6qXt9y6effUOeeeew6qWn0uqhZtJpr0FPx0y5vWJ3cybOvRYi2iKg//\nbreR5OWsrHXM467FFPi31rOlKc7goNkhKZFyMx07HFAP1SHwj3HTZC5KWAQ/GPo6ngyJSwSYt+dZ\nVt1+Fzv79ECUS/y3uxE7Za3o3FvZTJNiidguSTumXacrKkP+IPUxns+d46XS0IWG3dpGQ5PmRHv9\nel27AsqmppNZeZ6mrX7NB/6t3H5yK5cyYZ/oCRyFWfPgmfk84G/NBPEy6bzGJ3IIi+V0zvElYvA8\nRPl8I8dkMcixrhoN5rRMZ4THomiSnhH7RQkrwmsyKYeAkf7ObHBdtPzjNLlEvS03ZckKHsX651yY\n0WcBi/J85PtGMeaXG1SJzzLNCZ2FEZUroEK2VJ93Aq9ghPkPArZHv6akvkTSMK4B/gx8ARzAqBif\nB5zCqOg+3HTOr1DuncPAUJvamTDs1jbMJMtRGuohkmUqaU6T9yXbqzfylJyFe72f4y2updSzFvHh\nPBYsfAKxykdPoeXKkJKpnmxKn/8nprqzkdsWqP+0OXu1yVZvc/I8oDvzDASS/D6jYMUC0r9+uHHk\n3xCwwH8BASwTk8j3jw/6u0A8O49flqxQvzy6yWfWKMwbwDZYNN6HOCoZM36DWlRmdmKaHxgJvUQR\nh2/LMa4Hqq83xjdbuE4kW6aNthUBzYE9wJ3APcA/gCVBx3cDVgPXA9mo8lqdgUDQcSnlwwiF3dpG\nMHZrHOGuV8ePoQX7yK4gcoHB8NfbejLIvZYF/pd47pvpeJuWsoYxtKSSticroRi+6y8Y2qKAUuHl\nxO6u8C6I4AVtZh/JHbDaeyd/4h42uo7UHCIyfBwsb095IIMS4WVcq/VQkaf9tTtRT+nGEyFg1zzo\nZ2hAp+VvyTpRhSgH19YAcsZ8OD4bOiwEBIzycXBde657/jhLH5vIpCEr1Yn1Hcxmp3QruDQN0oqB\nV1GrVjXMBYtCKTMQfx9GGUpYAFxAyb9sQt90JLAGuASUAMeAfvVtXLKJp7YB9s+whLtOKC1DnEH9\nQh2EG94oIsdfTSF9OP9FG55mFhlU0HZTZc2S6iZzJFsmD+ekryvieRDH1P5aX6QcZLngzin5CNd3\n/FE8qD0sMxACVssi/IUuumwq5YZP9rFX9CHnq0OqPQJ2yCfhxGytgXpDRdy1EKFpEACcmI0QktPX\nZ5B+8SHVkJl5ZJeeUZnKtoPf4ybw+gICNKUgsJU7/J1hfR6lIofq6zw8mvdKbW3iXIwbGGt9BBye\nn8MVMwKU9Uk3kuiUh69ulswUfV6gF8p6ApgM7ANWAC21fe1QporOKQwB0yiJt9AwkyhzpSYqUHuV\nuQL3GT+HxnlZzVhmsojtffow5ts1ZJeaWlVuetXL9YVwx4tzkvXzx+MPNGXLR8PY4D4Cu6/ivurW\njHf3UIPurDp2sctPaesu2sk++lYV0cO7F1DyIsc/WhX1GfcgCOgRGKbiHHb74OSc6Jfdz8qD3T61\nVB8tCtN/iqWBUzWxEHe71sEqHz3a72VB9QWydp+n6qrlwBjEojxOXdvGqG1aLtUMxleSzdzKxi5j\nYEIex+mI6CRrZ/m2KDikow96y3QEekqCg9Blcin+iW41nZokonV6NgfeAqaiNI1lgL4y6T+AxcAD\nIc5t9EscY10y3xAa5MQ0XSPq87PA06WaVVPu4rq9x/GXuzl8Ww5dCkspy72aNm1OUXZtOm3OVtX+\ntTRnsDZjstsFkjJvC+i4AAb5YCu8snwS9136PbygzhdI/B/Mxz1Mv8DbpBXD3sxBuDkErOZk6T9T\n5m+BEDDE35JKsYESkcNXfVuz9ZubqPr5S9F916fnwyLw+jvzRKCI9pSQISrpWFWKRPDcLgmFAs4K\n/lY1kCbvao/usz7IkvBzaJtXWWvwywxw9/eBuIeC6lsYseRTprT6Iw8eX0maflAM1clC1s7V+t7z\nVDX+7u7g02qI949ONLZMGvAeaqb3txZ/96IyDnYHZmn7ntZeP0Qt0dsVdI4cYfrQWdsaA4kSHGYa\nIjzM5+p+jGaZwGBgBMq7/irGL1kuqpj1q9qK9lYuDk++lu7/fRj/O00NuzmSTa498Ds3qxWWvav2\n4zkKaQPW8s25n5E2t/bhspUAVd+rzjVoRY0TsaxvOkX05EX5EBtMPpFYEQKEkHzp/x1tCjVheMh0\ngClfpuws+LfJ+ayfPLa2wASjgwfAmskjGfPxBtWfZ6ltWlA/p2QtwZFLzVS47CQQN8mQvgv9/QHU\njIXOf6qXuPkwBMrkOEhtYdHW9H4UhodqI3Av0ARoj/qKpgo6Bv9q2hqLsEgWdvk5zHauzBW4RIDM\ndl+qh1r3Qxyjppi1KAbX0QDdSkqoDlwZ+sJW6rZpcAzYtI8mcyWu1yUHqh8nrYI6MwaiWCKs7Pcg\n2u2p4BM5hA3ue6P+3lZIqSqiZ68OukmwAxcQRyVvjx9n+BOCR+YZ6DVlB+1FiZrBOIQtwqLOeeUo\nx2c/EM8r7SdS4t/rgNGmraFEkjSDgc+A/RimxWxgDNBT23cSeMjUztnA/UA1yoSxStPUKGZJwpEM\nTUMnVo0j+Pjs5tBsGcgPBPTTgopMHncEkGGcfPPqAhbK2Qz27MF/k7tOXc86mHM0dMOYJszCeOKs\n1j/oswHB1zHlGBUd8wAJUmkkDUJAy68f4uw37UjbRe2apcED3Dw1GkwmyG4CpsFC72PM6b0kZKEh\nHauBHmrhWDOzprUcFbhQDhePhtcurGjoLIkTGt4Akik0dGIRHlbmSTCWiYPfBuaiBnlxnVOiI1yi\nF/M9s4L2mcLVXZ3y7M/6JUB08hH4yKWEZrDACCcozETQJuqTPq9OQufBQH9gcehp1EhaqCMwkkwq\nCA2IXnBEIzQgSHB0po6KbcZqgFjmZwglNKwymZsFxiAgF17wTmCKKx6TbgKXKwAFPvw73IbACCUs\nIsx4mLEjz6bZ93SxApDW60WiMVkbKjCc0PAGksgZlHBEOzNiPk5/mK0ER60Hf6chAKK1xc3H1QgP\nfaAFCw6rhMTaPpkruNQPmq7ww4w4hZALyQ8uPExFmadu1GowMQhNu6gROmGET6Km5B2BYQPxWocS\nK9GuWwl28Ef1KxhGuESizlShedDpwkMfcMGC4xxccczPtGm/Ycnjsd87GgRjWXblI3DUWBVaa8ZE\na4cVdpkfkQgnEKIVFnbEFDmrVW0kkUFekYhmVqW+af/MWyyEDE6yQguFFkgO9u3AELEZPohPpKeU\nqxnn6smRoTmGADNL3CRoFRDd7Fii1yY5AsNmUkloQPwfqPoKjlhoSSXruAtax8/lJiUM/G4HcpDA\nNdC09ClCgFUwDdEuYl0qEMv/1q7n0jFJ4kCq+DV0IkWP2rEE3zxQYjZbgn0bmeBu4cf/CzcSQTsx\nFfgKtUg6HmQhMh4m/4pbaJ9zEDrNh0ewHJF2CAq71g1Fi50/Yo6GESdSTdOA6M2UhhLN4AlZYKkc\nqICvZ3o45M3B3XGesRAtTghxhqXnJqocmQKQP2TUC/m238fORYbJwhEYcSQVhQYkTmjUWz0vg7T3\n4BHxIpyYzXZvf7XozH+7DS2rixyUx9Q5y2lbWMnJT7pR4F/L23njI5+oEel72pkHJdbr2P0MOiZJ\nnEmVGZRgIpkhdmUK+/L/QpsoF8tDl/nbNPZGtpwczoftf8LAwiJ2+CUD3XHyYWyfj/yxlof0oGTY\ntq2WAWqxzIjYrQUkU6sw4wRuJZBUExo69Qn6ipWYIkszUSHleiKeLvBdf3irxZ2Mc1mUC7QBIeDb\nivkqRHw7xvJ9Uxh8tEFZqSIsrLSLR9WLE7jVGEg1Z6hOrPEb0RwbTChNI6SWcY6apSKyEzTN8MU1\nT4KU8EyL6czptEQlMg5OORgFqSIoIH7msOPDSDB21EJJFeoTwxETWrTnmQ7pvFFdZG9mFSHggzzI\n9dWUM5znac6mDjfWLHT7ff79av0GkbWL74OwAEdgOAQRq4Mu1uNjEhqZKkPGXPkk41uvj+HEKFjk\nQ3wmKThyC/n+Iu4W69h2aQMj3D+BTOgw5Qse3fMKO/PiYwKFI1X8FVY4Powkk4omik59fRbRnFdn\nJSYof0VXai88ywTXY8ocmR7wkC4r2SP68FBgOc8wk63uncSEAE7MgQ6/wRsYzcnCbmr9CFA2XiXn\nSaeKAb/bh9il1RIpNjSMeGoWdlwrknbRUB+Go2EkmVQ2T+o7HRjNOXU0DS1o6838O4ziSrrT8wEf\nBf4tTOBlrheFPMxL9HJ9zlZPjMJipA8B5HvvQZyYTQEjDKfmOcisOs+/lH7EDYVFKh9nqDSEMXzP\naEhW+Yn64AgMh7hQr0HQFUbv2cjOoT3gpxip+W5S9Ti6FJYy7ORWhm3aSptNVeRXawntRffwWcWF\ngDd8LH37QQB68zl0XEjHqlJG3ZZfk2Xr0/Qb2ZYzQGXlhsTVvLSJRPz4OCZJinE5miiRzs++SjNL\nWqOywb4Kl5aBe5eLtK8ucam/h7TOa/Ef+5nKl6lVAes1ZQdPyGe4+/wGpBQcb5nDy0xgibhkeZ/V\nsoib2UKbTVXIs0JpEaZkOTJT4NlfTfVcj6o6JoFF1JgkZq2ooRqB3RpFtMLCMUkcEkY81eaL5ajl\nIs+oG6XNBdfGANU7PIgKuK/alNNPMyP2Lh1EL1FE2i5oUiFpSSUiTEauce6hbEGVbQwWFqBSFVb/\nyIN4VapYDAFk2b8qtbGYH1Y4cRgpRqrGaujEVMLA4lwszq8Vo1GMMkW2AZkgukpc/X3k+G9Vg9gU\nEyGQdMkrhQy4eWgBJdJLiXsL1kNSkF+9iZ7sC90wQByS+uEqQ/Uho40Wh8dMPIRFIv1gjkmSwqSy\n4AD7yh/o1CqDAEbU532o8lnmtHnm3J9aGj+ZK9jp7cFA18igKwuYMA+xcj4j/Z1ZWzWO5S3uZ9Lk\nlUbiX52gIC27TJF4aRWxCgvHJLmMSeUZlIZiNYD0gVljApQDd8DEcUuNMghWF8hU26VMGPFtgVbF\nTMBgn3rN9SEehqXVp1jIHPak92BS3sq6gsJCWDSUeM6AJOP5cDSMRkAqaxp2O0LrxGcIVeSojm9C\nK1IsWwmeeGE+d7GOStmS4e4nORj4dz6XPZktn6LU/SZCQH6giDGFG5CtBJ7p1fjbuQ2NJcJCs4Zo\nGPH0V9RHYDRUw3AERiPichUc4YQGhFigptNNe+0Ph6eoEo+UK/OkrH062Q+W8/flGWSdOI/QSx+W\nw4y8BTw7ZF6dGit2hYCnmqDQcQTG94xUFRrx1DR0QgoOvWbHLzAKMmmrXEU/iSs3gD/frQp6asJB\nlgvDwRnDalS7U+fVh2QKDMeH4WALiYhLCOtTKEaVBC/G8E20ArFKsurwXUzsu1Tt00sznol9JVtj\nFxZ24EyrNjLMD0yqaRsNmXINPj9c4p0agpedmy4gEYgfSt4fezMZopKxhVOiXqau3z8WEhFbkWxh\nAY6G4WAzdg4cq0EbceZCW/8hzkk4pELKvZTULDBrrKSCsABHYDRqUuUhspN6CZxQmkM5PMIfKMGr\nPutxHRZEEkTJrA2SSv/nSAKjKbALKAIOAk9p+zOBj4GjwCagpemcX6EsycPAUDsb61CXVEzI09DY\nA/O5UWsZ5n2Zxuto/kTvqv30Hr9DrYDVN/NxhM4tGqmd3ydhAZEFxjfAzUBP4Efa+8HALJTA6Axs\n1j6DmuQarb0OA/4QxT1SgqPJbkAIom1XIh+sAwm8F0TnT9jyj6AdWm6News30uQ9yfuM4PT4lvSe\nsl2FfGdhFH0OV1m+ATS0n1JNWEB0g/mi9toEcAMVqIwFr2n7XwPu1N6PBNYAl4AS4BjQz6a2xpXG\nLjAgcdrGF1Eel7BFVq1gy4Xan8lEaRLbgaPQNq+SVlVV3CI+paxvek10KE8T0kzRsfoe0Xy3aPvJ\nilQUFhDdLIkL2At0BJah+iELo8/Mzu12qKh/nVNAti0tdYiaVFrA1tCZE53gWZOQyYOz4K95PRns\n2UP1II/KmqU1pMlfJM91/TW0BjldMMr7Bu96jrC9egMDxu2j2baG+TLsIlWFBUQnMAIokyQd+Ahl\nlpiRhE/PGupve6O4d8L4BtoCp5PdjmDq26541kP5OsY2xUtoADXmxPkmtCWL0wjIEBVUF3sQO2Td\nEX4I6ApiiWR97ngokIiPjWs1w77l7LH2EyREWPSO/y0Mfg08jnJottH2tcWYtJqF4c8A+BDob3Gd\nIgxB42zO5myJ27YQR1pjzIBcCXwG3IrKQzRT2z8LZQmCcnYWofwd7YHjJC/83MHBIcF0R5kORcB+\nYIa2PxP4BOtp1dkoZ+dhID7FMB0cHBwcHBwcghmG0j6KMcyaZFCC0po+B/6m7QsXkBYPVqLccv9l\n2pfsoDirNuWhZrw+17bhCW7TNcCfUTN0B4Ap2v5k9lWoNuWR3L66rIIt3ShzxQukob5U1yS15SR1\nZ+AXAU9o72di+GbixY+BXtQenKHaoPuH0lD9d4z4BMVZtckHTLM4NlFtaoOaqQNoDhxBPTfJ7KtQ\nbUp2X4Ga7AE1C7oTFWxpS18lOgqzH6pBJajgrjdRwV7JItghGyogLV78BRUIF00bEhUUZ9UmsHZe\nJ6pNZaiHGuACanI0m+T2Vag2QXL7CuIYbJlogZEN/I/pczIDuyTKcVsITNT2hQpISyThguJOmY5L\ndN9NBvYBKzDU2WS0yYvSgHaROn2lt0kPWkx2X7lQwuwMhtlkS18lWmDIBN8vHINQ/+ThwCSUKm5G\nn7dOJpHakKj2LUNNk/dEBSItDnNsPNvUHFgHTAWCV48kq6+aA29pbbpAavSVHmx5NXAj9gVbJlxg\nfIlyFulcQ23plkj0CLz/Bdaj1LAz1A5IS0aUbqg2BPfd1dq+RHAW4yF7GUNlTWSb0lDCYhXwjrYv\n2X2lt+kNU5tSoa90qoD3gT4kv6/qhQcVzOVF2VfJcno2A36gvb8KtURpKKED0uKJl7pOz2QHxQW3\nqa3p/WPA6gS3SQCvA88H7U9mX4VqU7L76rILthyO8igfQ03nJIP2qE4qQk2J6e0IF5AWD9YAfwe+\nQ/l27ovQhkQExQW36X7UwNiPssvfobZvJxFtGoxSs4swpiuHkdy+smrTcJLfV06wpYODg4ODg4OD\ng4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODw+XE/wOl7RK9GWsMxAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(data7,interpolation='none')" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 54.8 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "data7=mandel4(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that here, all the looping over mandelbrot steps was in Python, but everything below the loop-over-positions happened in C. The code was amazingly quick compared to pure Python." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Can we do better by avoiding a square root?" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel5(position,limit=50):\n", + " value=position\n", + " diverged_at_count=np.zeros(position.shape)\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+position\n", + " diverging=value*np.conj(value)>4\n", + " first_diverged_this_time=np.logical_and(diverging, diverged_at_count==0)\n", + " diverged_at_count[first_diverged_this_time]=limit\n", + " value[diverging]=2\n", + " \n", + " return diverged_at_count" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 52.1 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "data8=mandel5(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Probably not worth the time I spent thinking about it!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NumPy Testing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's look at calculating those residuals, the differences between the different datasets." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data8=mandel5(values)\n", + "data5=mandel2(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.sum((data8-data5)**2)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "For our non-numpy datasets, numpy knows to turn them into arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]\n", + "sum(sum((data1-data7)**2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But this doesn't work for pure non-numpy arrays" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data2=[]\n", + "for y in ys:\n", + " row=[]\n", + " for x in xs:\n", + " row.append(mandel1(complex(x,y)))\n", + " data2.append(row)" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unsupported operand type(s) for -: 'list' and 'list'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdata2\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mdata1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for -: 'list' and 'list'" + ] + } + ], + "source": [ + "data2-data1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So we have to convert to NumPy arrays explicitly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "sum(sum((np.array(data2)-np.array(data1))**2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy provides some convenient assertions to help us write unit tests with NumPy arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x = [1e-5, 1e-3, 1e-1]\n", + "y = np.arccos(np.cos(x))\n", + "np.testing.assert_allclose(x, y, rtol=1e-6, atol=1e-20)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "np.testing.assert_allclose(data7, data1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Arraywise operations are fast" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that we might worry that we carry on calculating the mandelbrot values for points that have already diverged." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel6(position,limit=50):\n", + " value=np.zeros(position.shape)+position\n", + " calculating=np.ones(position.shape,dtype='bool')\n", + " diverged_at_count=np.zeros(position.shape)\n", + " while limit>0:\n", + " limit-=1\n", + " value[calculating]=value[calculating]**2+position[calculating]\n", + " diverging_now=np.zeros(position.shape,dtype='bool')\n", + " diverging_now[calculating]=value[calculating\n", + " ]*np.conj(value[calculating])>4\n", + " calculating=np.logical_and(calculating,\n", + " np.logical_not(diverging_now))\n", + " diverged_at_count[diverging_now]=limit\n", + "\n", + " \n", + " return diverged_at_count" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data8=mandel6(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 52.1 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "data8=mandel6(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(data8,interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This was **not faster** even though it was **doing less work**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This often happens: on modern computers, **branches** (if statements, function calls) and **memory access** is usually the rate-determining step, not maths." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Complicating your logic to avoid calculations sometimes therefore slows you down. The only way to know is to **measure**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Indexing with arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We've been using Boolean arrays a lot to get access to some elements of an array. We can also do this with integers:" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3, 4, 5, 6, 7],\n", + " [ 8, 9, 10, 11, 12, 13, 14, 15],\n", + " [16, 17, 18, 19, 20, 21, 22, 23],\n", + " [24, 25, 26, 27, 28, 29, 30, 31],\n", + " [32, 33, 34, 35, 36, 37, 38, 39],\n", + " [40, 41, 42, 43, 44, 45, 46, 47],\n", + " [48, 49, 50, 51, 52, 53, 54, 55],\n", + " [56, 57, 58, 59, 60, 61, 62, 63]])" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x=np.arange(64)\n", + "y=x.reshape([8,8])\n", + "y" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 1, 2, 3, 4, 5, 6, 7],\n", + " [40, 41, 42, 43, 44, 45, 46, 47],\n", + " [16, 17, 18, 19, 20, 21, 22, 23]])" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y[[0,5,2]]" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1, 18, 47])" + ] + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y[[0,2,5],[1,2,7]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use a : to indicate we want all the values from a particular axis:" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 0, 2],\n", + " [16, 18],\n", + " [32, 34],\n", + " [48, 50]])" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y[0:8:2,[0,2]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can mix array selectors, boolean selectors, :s and ordinary array seqeuencers:" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[[ 0 1 2 3]\n", + " [ 4 5 6 7]\n", + " [ 8 9 10 11]\n", + " [12 13 14 15]]\n", + "\n", + " [[16 17 18 19]\n", + " [20 21 22 23]\n", + " [24 25 26 27]\n", + " [28 29 30 31]]\n", + "\n", + " [[32 33 34 35]\n", + " [36 37 38 39]\n", + " [40 41 42 43]\n", + " [44 45 46 47]]\n", + "\n", + " [[48 49 50 51]\n", + " [52 53 54 55]\n", + " [56 57 58 59]\n", + " [60 61 62 63]]]\n" + ] + } + ], + "source": [ + "z=x.reshape([4,4,4])\n", + "print z" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 4, 5, 6],\n", + " [12, 13, 14]],\n", + "\n", + " [[20, 21, 22],\n", + " [28, 29, 30]],\n", + "\n", + " [[36, 37, 38],\n", + " [44, 45, 46]],\n", + "\n", + " [[52, 53, 54],\n", + " [60, 61, 62]]])" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z[:,[1,3],0:3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can manipulate shapes by adding new indices in selectors with np.newaxis:" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(4, 1, 2)" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z[:,np.newaxis,[1,3],0].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we use basic indexing with integers and : expressions, we get a **view** on the matrix so a copy is avoided:" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[[ 0, 1, -500, 3],\n", + " [ 4, 5, 6, 7],\n", + " [ 8, 9, 10, 11],\n", + " [ 12, 13, 14, 15]],\n", + "\n", + " [[ 16, 17, 18, 19],\n", + " [ 20, 21, 22, 23],\n", + " [ 24, 25, 26, 27],\n", + " [ 28, 29, 30, 31]],\n", + "\n", + " [[ 32, 33, 34, 35],\n", + " [ 36, 37, 38, 39],\n", + " [ 40, 41, 42, 43],\n", + " [ 44, 45, 46, 47]],\n", + "\n", + " [[ 48, 49, 50, 51],\n", + " [ 52, 53, 54, 55],\n", + " [ 56, 57, 58, 59],\n", + " [ 60, 61, 62, 63]]])" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a=z[:,:,2]\n", + "a[0,0]=-500\n", + "z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also use ... to specify \": for as many as possible intervening axes\":" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[16, 17, 18, 19],\n", + " [20, 21, 22, 23],\n", + " [24, 25, 26, 27],\n", + " [28, 29, 30, 31]])" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[-500, 6, 10, 14],\n", + " [ 18, 22, 26, 30],\n", + " [ 34, 38, 42, 46],\n", + " [ 50, 54, 58, 62]])" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z[...,2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, boolean mask indexing and array filter indexing always causes a copy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try again at avoiding doing unnecessary work by using new arrays containing the reduced data instead of a mask:" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel7(position,limit=50):\n", + " positions=np.zeros(position.shape)+position\n", + " value=np.zeros(position.shape)+position\n", + " indices=np.mgrid[0:values.shape[0],0:values.shape[1]]\n", + " diverged_at_count=np.zeros(position.shape)\n", + " while limit>0:\n", + " limit-=1\n", + " value=value**2+positions\n", + " diverging_now=value*np.conj(value)>4\n", + " diverging_now_indices=indices[:,diverging_now]\n", + " carry_on=np.logical_not(diverging_now)\n", + "\n", + " value=value[carry_on]\n", + " indices=indices[:,carry_on]\n", + " positions=positions[carry_on]\n", + " diverged_at_count[diverging_now_indices[0,:],\n", + " diverging_now_indices[1,:]]=limit\n", + "\n", + " return diverged_at_count" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data9=mandel7(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(data9,interpolation='none')" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 71.3 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "\n", + "data9=mandel7(values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Still slower. Probably due to lots of copies." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "NumPy" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/030scaling.ipynb b/session08/030scaling.ipynb new file mode 100644 index 00000000..41d4c2d8 --- /dev/null +++ b/session08/030scaling.ipynb @@ -0,0 +1,856 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scaling for containers and algorithms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We've seen that NumPy arrays are really useful. Why wouldn't we always want to use them for data which is all the same type?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from timeit import repeat\n", + "from matplotlib import pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_append_to_ndarray(count):\n", + " return repeat('np.append(before,[0])',\n", + " 'import numpy as np; before=np.ndarray('+str(count)+')',\n", + " number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_append_to_list(count):\n", + " return repeat('before.append(0)',\n", + " 'before=[0]*'+str(count),\n", + " number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "counts=np.arange(1,100000,10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.0018)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_append_to_list,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.60000000000000009)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_append_to_ndarray,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Both scale well for accessing the middle element:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_lookup_middle_element_in_list(count):\n", + " before=[0]*count\n", + " def totime():\n", + " x=before[count/2]\n", + " return repeat(totime,number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_lookup_middle_element_in_ndarray(count):\n", + " before=np.ndarray(count)\n", + " def totime():\n", + " x=before[count/2]\n", + " return repeat(totime,number=10000)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.0060000000000000001)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_lookup_middle_element_in_list,counts))\n", + "plt.ylim(ymin=0)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.0065000000000000006)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_lookup_middle_element_in_ndarray,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But a list performs badly for insertions at the beginning:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x=range(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 3, 4]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x[0:0]=[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[-1, 0, 1, 2, 3, 4]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_insert_to_list(count):\n", + " return repeat('before[0:0]=[0]','before=[0]*'+str(count),number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.40000000000000002)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYgAAAEACAYAAACpoOGTAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xd8VuX9//HXPbIDJIQwRQIIAooMZYkoomKAugAH7g2K\n1gECYZ72a6tf2zpbK47WVb/ar9VWrcWvC+XnQpFRFBAQZE9ZgRgyrt8f51BuQuBOIMk5yfV+Ph7n\n4VlX8vkcyee673OdASIiIiIiIiIiIiIiIiIiIiIiIiIiIrVSLrAYWAqMP8x+PYBiYNgRtBURkVom\nAiwDcoAEYB7Q8RD7fQC8xf4OoqJtRUQkgMJxtvfELfIrgSLgZeCCcva7HXgV2HwEbUVEJIDidRAt\ngNUxy2u8dWX3uQD4o7dsKtFWREQCKl4HYeJsB3gYmODtG/KmirYVEZGAisbZvhZoGbPcEvebQKyT\ncU8fATQCBuGeUqpIW3BPQ7WtYLwiIuJaDhznZwBRL4gcIJH4A81/BoZWsq2+aezn+B1AgDh+BxAg\njt8BBIjjdwABUu21M943iGLgNuAd3KuSngEWASO97dOPoK2IiNQC8ToIgH95U6xDdQzXVaCtiIjU\nAvEGqaVmzfQ7gACZ6XcAATLT7wACZKbfAUjN0hiEiEjlVXvt1DcIEREplzoIEREplzoIEREplzoI\nEREplzoIEREplzoIEZGKcmiFw6s41Pc7lJqgDkJEpCIcBgCfA58Cu3yOpkZU5E5qERF7OYSAu4B7\ngCtw+MDniGqMOggRkUNxSAOeBtoBvXH4weeIapROMYmIlMehLe7ppEKgn22dA6iDEBE5mEMubufw\nJHAdDgU+R+QLnWISEdnHHW/IA0YDw3GY5XNEvlIHISIC4FAPeBZoDvTEYa2/AflPp5hERByOB74A\nNgP91TkEhx73LSL+cTgfh0043Oh3KJXk+ytHRUTqJocwMA24HjgPhy98jihw1EGIiH0cMoAXgAbA\nKThs9DmiQKrIGEQusBhYCowvZ/sFwHxgLjAHGBCzbSWwwNs2+2gCFRGpEg4n4NajFcBZlescTCSJ\ngtxqiqzWiQDLgBwgAZgHdCyzT1rMfGdv/31WAA3j/A6NQYhIzXAY7o03XFWZZnPolvIIt9/3Chdv\n20h24WR+2bi6QqwE38cgeuIW/JXe8su43xgWxeyzO2Y+HdhS5meEjiI+EZGj5xABfgVcBuTi8HW8\nJgbqlRIavJKc0dksPbWYaMF2Ml6sz8777mXqpmqPOQDidRAtgNUxy2uAXuXsdyFwH9AMGBiz3gDv\nASXAdOCpI45URORIOGQBL+GeETkF56APsf9hIAs4HxhaTGTAZ/Qp/BvDCr6kx42f0vc5COmMR4xh\nHFjUrwQeO8z+/YAlMcvNvP9m456e6ldOGx1wEakeDl1x+B6H3+CU/4HYQAsDow28b2DHWpp9MJrH\nFmaydTmYy8FEajrsCvL9FNNaoGXMckvcbxGHMsv7mVnAVmC9t34z8DruKavybl13YuZnepOIyJFz\nuAJ4GLgNh1diNxloC1yE+yH4eOCttxjy1uW8VLiL+icAvwSeh1BRTYd9GP29KTCiwHLcQepEyh+k\nbsv+cYbu3v4AqUA9bz4N+IQDTz/to28QIlJ1HBJweAiHZTh0BjDuuaETDUw1MM/ARgPTDQxszfKT\nwPwvmHVgbgOT5HcKFRSI2jkI97TRMtyHWAGM9CaAccBC3EtZZwE9vPVtcDuUed72fW3LCkSSIlIH\nODTGYSYOb7e4m4YGehq438B3Bn4w8JCBfgYiYNqCeR7MJjD3gEn1O/xKsqJ2WpGkiFQzhx7RKfxw\n43m8UBziMQOrDSw28CsDJ5v/nOkwLcFMB7MFzFQwtfX90lbUTiuSFJHqYSDpv/rx4LNdKCiIsN3A\nHAOTzEGnw00TMA+D2QrmfjBZ/kRcZayonVYkKSJVx0C6geFFIV7OT+CnL5qzZ2E29xl3vLTs3g3B\n3Od1DI+AaVrjAVcPK2qnFUmKyNHxOoWrDfzdwM7dUT4cdzbLWt/Bv3Ao5zSRqe+dQtoC5kn31FKd\nYkXttCJJETlyBhoZ+NrAvwxcddp1nIvDGhymeE9ljd071Rt03gTmBTDH+RN1tbOidlqRpIgcGQNN\nDSw08OtpZxDCYZT3PKUhZfZM8i5TXQfmVTCd/Im4xlhRO61IUkQqz0BL7xLVyTgk4/AMDgtxaBez\nVxTMDWB+APNPMN39i7hG+X4ntYiILwy0Bt4H/hBy+BvufVYrgN445HuPwLgU+AXuM+NGQOhT3wKW\naqFvECJyAAPtDawycCsOuThswOEuHEJgQmAuArMQzGdgBsT/iXWSFbXTiiRFpGK8R2KsLQpxPQ6O\nNxjdz+sYcsF8BWYumCHuOmtZUTutSFJE4jPQ3cD6FQ24CYd/eY/NaAqmDZh3wCwCczGYirwNs66z\nonZakaSIHJ6BPgY2vt6BsTisxOEB+k9NAjPWu5dhHJgEv+MMECtqpxVJisihGehfCpum9OdR7xLW\noe7VSGYOmPfcB+tJGVbUTiuSFJHyGTi3FDZdPpR3cPg3o046CcxvwGwEc63l4wyHY0XttCJJETmY\ngfOLQmw9+yqW4vACDb/7GZjlYF4C09jv+ALOitppRZIiciADlxRE2NbrRn5kTNOxUPIcmJVgBvsd\nWy1hRe20IkkR2e+nCNduT2JX95tYR++HpoDZAOYhMOl+x1aLWFE7rUhSRFwrGzB2Qxo/db8++Uvq\nrX4PzAIwPf2OqxayonZakaSIwP9rySOr6lPcedDpHxMq2gImT5euHjEraqcVSYpYzSH0vx15c1kG\nJW3aP7wczIdg2vsdVi0XiNqZCywGlgLjy9l+ATAfmAvMAWKfixKvLQQkSRGpHlnjSH+qa+jbxZnR\n4hYNZv3oPXlVl64ePd9rZwRYhvsavwRgHge955W0mPnO3v4VbQsBSFJEqkfiZDo+1j1hx/z6GSWt\not+8UYde9xkEvtfOPsCMmOUJ3nS4/T+vZFvfkxSRqpdw+7HXP94tofir1JZFJ/PlCL/jqYN8fx9E\nC9znrO+zBuhVzn4XAvcBzYCBlWwrInXJz9smRndn/n36+4tyeyxrtuWtvSO6zqHHOr/DksqL10FU\ntIf6uzf1A14AOlQyDidmfqY3iUht039aj4SfXn3nxfdW1TtjdeK8JqWr+nXmv3f7HVYd0d+bAqM3\nB54myuPQg837LAeyKtFWp5hEaj0ToffvHk26o3HJB40abywm/LaBZL+jquN8r51R3IKfAyRS/kBz\nW2DfFQndvf0r2hYCkKSIHIVw4YmcNWFVyl0NC5enpSww8Kpx/+alegWidg4CluBekZTnrRvpTQDj\ngIW4l7nOAnrEaVtWIJIUkcoyydRb/RsuH1JYb3TT73dH+cTAX4zedV9TrKidViQpUreY02k+eyV3\nN89vcluDF0rgUwNPG/fydqkZVtROK5IUqRtMBpgn6f7kj0xO3NFlFDcb+MrAYwb0GtCaZUXttCJJ\nkdrNhMAMI1KwlssHLWRqeOnlQzndwAIDD5j945BSc6yonVYkKVJ7mWwwr9Hwu6VMqL8Ih1df68Dx\nBhYZcNQ5+MaK2mlFkiK1kzkLzBp6PvoK09iAw90FEVoZWGbiX/Iu1cuK2mlFkiK1i0kAcz9J29Zx\n2/Fv4LAGh9MNHGfgBwO3+x2h+P+oDRGxjmkLvETnF+Gia0oIl24ETjQOzXGfcuCE4GlfQxRr6BuE\nSGCYK0jZvIWbT/6CaazA4WwAA10NrDdwhd8Ryn9YUTutSFIk2Ew9MM9x0gtrmJKwCYdHcEg3EDZw\ns4HNBob5HaUcwIraaUWSIsFlTqHemu+5/tTvmRpegkNfAAMdDcwy8JmBE/2OUg5iRe20IkmR4DFh\nQsX30PVPO5iUvB2H+3BINpDkXb662cBo3R0dWFbUTiuSFAkW05SsxR9xXb8fmZz4LQ4nAxjo593f\n8HcDx/gdpRyWFbXTiiRFAiNUNIiej25nYupupkSn4ZBoIMPAdANrDFzkd4hSIVbUTiuSFPGfSeKY\nT5/hur4/MaHBtzicaCBk4GIDaw08bqCB31FKhVlRO61IUsRXKVs6coazhrx6hdzTaDIOEQPHGnjT\nwDcGd2BaahUraqcVSYr4w4To9MpEbupRxJ3HLmFKwnEGIgbuMLDFwGS93KfWsqJ2WpGkSI3L+SCL\nwbfOZ3xmMSO7/RKHsIEuBmYb+MjA8X6HKEfFitppRZIiNerMSddw6wmFjOryAzf1aGcg1cD9BjYZ\nuEHvbqgTrKidViQpUiOuOiuVy86fyT2NShl+6cM4hAycY2C5gZcNNPU7RKkyVtROK5IUqXZXnnMe\nd+Ts5uoBmzn9v7oZaGTgee/pq0P8Dk+qXCBqZy6wGFhK+c9/vwKYDywAPgFOitm20ls/F5h9iJ8f\niCRFai2HNEZ2+wdjG5dw5pT/OaPJQxEDVxnYYOBBA+l+hyjVwvfaGQGWATlAAjAP6Fhmnz7sv3Y6\nF/g8ZtsKoGGc3+F7kiK11tgm53JP9g4uHp5P63fPNdDWwLsG5ho4xe/wpFr5Xjv7ADNilid406Fk\nAmtillcAWXF+h+9JitQ6Dg0Y0+wVxjTdS7enP+rHR40MjPMuXb3HuB/opG7z/YVBLYDVMctrgF6H\n2f8G4O2YZQO8B5QA04GnjiBGEYk1NTyYwtQXWZabwscT7yra1mF2lJL3gE1AzxB873eIUjfE6yAq\n00OdCVzPgXdk9gXWA9nAu7hjGbPKaevEzM/0JhGJ5ZBFUcrj7G4whDefXD/gu9RL36fd1cAUYAzw\nUkjfyOuy/t4UGL058BRTHuUPVJ+EO1Zx3GF+1jTcf8Rl6R+0SDwOw5mcuIUho3aSsuWxbTS4wLs6\n6VkDjfwOT3zhe+2MAstxB6kTKX+Q+ljczqF3mfWpQD1vPg33CqeB5fwO35MUCSyHJkwL/Y2xjTdz\n7EdbzuLdKwy8YmCZgbP8Dk98FYjaOQhYgtsJ5HnrRnoTuC8v34p7KWvs5axtcDuUecDCmLZlBSJJ\nkcBx6M/UyAaGjFwdju54fzmtx3ov8bnPQIrf4YnvrKidViQpUmEOYRwmMin5R9q9tW0Q//xNKXxs\n4Atz4H1GYjcraqcVSYpUiEMjpoZncFu7tZH6y1d/Rfc/epeu3q5Xf0oZVtROK5IUicvhVKZE1zJk\n1PpO4Tn/V0jCpwZmGnecT6QsK2qnFUmKHJJDCIcxTE7aTofXtj/Inc+XumMN4/StQQ7DitppRZIi\n5XLIZGrkDX7eZkNmgy9XraXZDAOLDHT3OzQJPCtqpxVJihzE4RSmJKzi/Os3DQ+/+FEx4VUGfm/c\nS8RF4rGidlqRpMh/uKeURjMpeUdix+d3vMeAd0phnYHBfocmtYoVtdOKJEUAcKjPlOir3NlqS+eM\n19bkk7rYwBsGGvsdmtQ6VtROK5IUwaELkxNXhC68YuuvwmO+KiG01cDNBkJ+hya1khW104okxWLu\nKaUbmZS8s1mnh3cto82CUvjSQHu/Q5NazYraaUWSYimHNCYnvsRdLbcNb/DA+r1ENxu4V+9rkCpg\nRe20IkmxkEMnJiUvT7tw6PZ/hHO/KyG00sBpfocldYYVtdOKJMUy00JXMSl5V88OE/K30WC9gefM\n/lfzilQFK2qnFUmKJRxSmJT8bOTO5jvuTb92SxGRrQYu8TssqZOsqJ1WJCkWcGjPxNQlbX529q5v\nQ+02FhGZaaCl32FJnWVF7bQiSanjpoUuCU1M3nVN6ysKCkjaaWCsgbDfYUmdZkXttCJJqaMckpiY\nOj1zdOP8fyT33VlA0lIDXf0OS6xgRe20Ikmpgxxak5f+zdln9ijYQkZ+IQl/0JvepAZZUTutSFLq\nmKnhC5LGJe/6XbPT9u4mZauBXL9DEutYUTutSFLqCIcE8ur9/oRrG+5ZmNCqYDv1/89Att9hiZUC\nUTtzgcXAUmB8OduvAOYDC4BPOPCdufHaQkCSFInLoWVofP0Fd5zSfu9O0gr2kDxSz1ESH/leOyPA\nMiAH99EA84COZfbpw/4bgHKBzyvRFgKQpEhcU6KDm9+esmtGZtviTWR9Z6Cd3yGJ9XyvnX2AGTHL\nE7zpUDKBNZVs63uSIofkEGV85oMXXlCvcFO4ftEamj+i5yhJQFR77YzG2d4CWB2zvAbodZj9bwDe\nPsK2IsHi0Kz+towZD76deML5y9idYEp/1ph1s/wOS6SmxOsgKtNDnQlcD/Q9grZOzPxMbxLxz5Ro\n997fJ378/P+GUksK6r+fzdJhIdjpd1hitf7eFBi9OfA0UR7lDzafhDvecNwRtNUpJgmWcVlDLhya\nvHdLJK14Hifd7Hc4Iofge+2MAstxB5oTKX+g+VjczqH3EbSFACQp8h8/b3Pb5T9LK94aTi9cQatT\n/Q5H5DACUTsHAUtwO4E8b91IbwJ4GtgKzPWm2XHalhWIJMVyDiFGd3zo+nMySn4Mp+XvJdrZ75BE\n4rCidlqRpASYQ4RbTnzz9tOaluwIpW7Vq0CllrCidlqRpATUmKYp3HrCvLzurYrzQ8nrjHtKVKQ2\nsKJ2WpGkBNCtJ2SHRrdf9+uOx/1UQMIyA839DkmkEqyonVYkKQFz6UUnhu44Nv/xnPY7iojM1/OU\npBayonZakaQEyAXXDgrf3aTo5aZt15cQ+ty4TwAQqW2sqJ1WJCkBMWTk7dExDUvea9hymYGPDNTz\nOySRI2RF7bQiSQmAIbf8PvHurNL56Y0WGJhhINXvkESOghW104okxU8mzHk3vJvy86bFPySnzzHw\nuoEkv6MSOUpW1E4rkhSfZC9MY9iI79JvabVnWzRhtoG/6GmsUkdYUTutSFJ8cMynrbh88NbMG9pv\nLgiHvzTwlHHfUyJSF1hRO61IUmpYqw9P4/pTf2pydadFxTDXwCMGwn6HJVKFrKidViQpNajtv67l\n1k7FrUd0+qAUvjHwa70aVOogK2qnFUlKTTAh2v/jAe5qUdxl2PHPlsJSAxP9jkqkmlhRO61IUqqb\nSeCEl//BuKyic85rda+BlQbu9DsqkWpkRe20IkmpTiaTbk8tYEL9whvPanibgTUGbvI7KpFqZkXt\ntCJJqS6mHac+sJEJDXY90CvhCgPrDVzhd1QiNcCK2mlFklINwkVnMHBMPuMzNr7WgWEGNhoY6ndY\nIjXEitppRZJSxRJ33siwEQWMz/z266ZcYGCTcd9gKGILK2qnFUlKVTFh0tY/xLVn7GF8xocb0rjQ\n6xz6+x2ZSA2zonZakaRUBZNGxvK3ue34XUxMe25PlOHeaaXefkcm4oNA1M5cYDGwFBhfzvYOwGfA\nT8CYMttWAguAucDsQ/z8QCQpQWeOofH8bxjbeBeTE39RAld6A9Ld/I5MxCe+184IsAz3Pb0JwDyg\nY5l9soFTgHs5uINYATSM8zt8T1KCzpzMsR9vYmLaLqaFbjRws3cpaye/IxPxUbXXznjPpumJ20Gs\nBIqAl4ELyuyzGfjK214ePeJAjoIZSofXPuDqc5JI3D3C/MKk494d3T8E3/odnUhdFq+DaAGsjlle\n462rKAO8h9uB6MYlqQQTAjOBno89zcWX7SVaONA4dAFGA6eH3A8uIlKNonG2H+1XmL7AetzTUO/i\njmXMKmc/J2Z+pjeJtUwilE7n3LHn0vOxnYmmeGChw7XA+bidw3qfAxTxQ38CdrVeb2BGzHIe5Q9U\nA0zj4DGIimzXGITEMMcQLvyMEeetYGp4bqN7aOo9qnuOgUZ+RycSIL7XziiwHHeQOpHyB6n3cTiw\nA0hl/wvh04BPgIHltPM9SQkKcxYZ32/kjpzvmca/uo6kgYGnDXxqIMPv6EQCJhC1cxCwBPecb563\nbqQ3ATTFHafYAWwDVgHpQBvcDmUesDCmbVmBSFL8ZMJgJnLiS9uYnLgNh7xnu5DkvR70A+P+exKR\nA1lRO61IUg7FZJK0/Z8Mv2QDU6IrcehlIMnA6wb+aSDF7whFAsqK2mlFklIe05Xms1czptlWpkZe\nwqG+gRON+4rQl417WlNEymdF7bQiSSkjVHwtfX6bz+SknThcZSBiYJyBzQau1ytCReKyonZakaTs\nY5LJ+P55rh6Qz8TUf+NwnIG2Bv6fgQ+Ne0GEiMRnRe20IkkBMDl0eO07xjUsYGLqg8fdTqKBW7xv\nDXeY+Dduish+VtROK5K0Xsrm8zj3jt3k1dvO1PBZBo4x8I6B2cZ94KOIVI4VtdOKJO1lIrSa+Sgj\nu+5lbJNP0iaSbeAK7x0OU0z8u/lFpHxW1E4rkrRS4s4s+vxuAeMz9nJXy7z8BLINvGpgoYHufocn\nUstZUTutSNI6LT7rzyXD8hnTdAvjM7oZON/AOgMPGEj2OzyROsCK2mlFkvYwIU55/H7uyClmVOd3\nnu5GUwN/NrDcQD+/oxOpQ6yonVYkaYWWs9IZMuprxmUVcV2/0QYGGPjBwBN6XIZIlbOidlqRZJ13\n6gN9uaH3Lm7pvLHXoJM7G3jUe+tbrt+hidRRVtROK5Ks04aM+hVjG5dw2flv5EfCfQws8R60l+l3\naCJ1mBW104ok66QRP0vnyoFzuKtlcf2zRo40cK+BDQaG+x2aiAWsqJ1WJFnnXD74dH7eJp8rz93w\nTHavQd4D9t407uPfRaT6WVE7rUiyznAIcW2/3zCuYUl0wJg3ighP0AP2RHxhRe20Isk6YWJaFqM7\nLmBUl6LBre+aZOAT7wF7rfwOTcRCVtROK5Ks9e48dhDjsnaHhty0cX6kvWNgix6wJ+IrK2qnFUnW\nWg4J3JHzBGOzi9p1/PXMIiLv6gF7IoFgRe20IslayaEN92R/F7ry7MIHUy9/0XvA3mQ9YE8kEAJR\nO3OBxcBSYHw52zsAnwE/AWMq2RYCkqSUMSn5KvLS9zTuOfHHDWS/b+DfBrr5HZaI/IfvtTMCLMN9\ny1cCMA/oWGafbOAU4F4O7CAq0hYCkKTEcKhHXr2/cmerPVdn5X1TQmiDgf82kOR3aCJyAN9rZx9g\nRszyBG8qzzQO7CAq2tb3JMXjMJhJKWsbDLl096xwz/ml7gP2TvM7LBEpV7XXznjnklsAq2OW1wC9\nKvizj6at1CSHHIpSHmdXg77nvjwy9NraB/JTKfgUuCcE+X6HJyL+iNdBHE0PVZm2Tsz8TG+S6uaQ\nTEl0HEUJ4zq9c134uTmfru9m/isSoXRkCN7xOzwROUB/b6ox8TqItUDLmOWWuN8EKqIybZ0K/kyp\nKg6D2Jv6dPNFPes99M/k/KF7pydEKfkD8IcQFPodnogcZCYHfnie5k8Y+0WB5bgDzYkceqAZ3CIf\nOwZR0bYag6hJDjnk1ZtR77Ymu+5rODT/JxJ3lbqD0HryqkjtEojaOQhYgntFUp63bqQ3gftwttXA\nDmAbsIr9L4cpr21ZgUiyznNIZlKykzA+Zfftx+Xu3UH6rkISXjRwrN+hicgRsaJ2WpGkr6aGB4Um\npK27uG/XglXhJrt3kv6xga5+hyUiR8WK2mlFkr5wyGF8xrv9LmlU8GVy28JtNFhm4By/wxKRKmFF\n7bQiyRrlkExevV92vDml4O9Z7Yu3UX9bPqnX6MF6InWKFbXTiiRrzNRwbvPR6Zumt21ZvD2UVriG\n5r8wkOx3WCJS5ayonVYkWe0cchrc3vCjX/SqX/RjOLXkWzq8ZKCh32GJSLWxonZakWS1cUhKuSvz\n17cMTNq7LjG9dE6o8xebyWrjd1giUu2sqJ1WJFkdIhMTB198fur2JfXSSudG263/hD79/Y5JRGqM\nFbXTiiSrlEOrc4dmfv1Js8TSRYnNCl5l6Ci9D1rEOlbUTiuSrBIOSWdc0vCJ19pHS1Yl1St9OjLi\nyQ85I+J3WCLiCytqpxVJHq0zLk+98smTkgo2JSWYP6Ze+NkdPNTI75hExFdW1E4rkjxSg66g44Pd\n0n/Ykhw2T2WesWEEf+npd0wiEghW1E4rkqysqy4k9bfd672zNi1kXmncofDC5GdGgdE4g4jsY0Xt\ntCLJijIQ+mOXlHsXZ0aKP2jcsPSiRvc+D0Y3uolIWVbUTiuSrIg3jwtf8FV24s4FWQnmspybFySz\np2X8ViJiKStqpxVJHoqB0NttQ9fNbpy8bnV6yNzcceCO1HqLcv2OS0QCz4raaUWSZb3RnvQXj097\neklGtHB+o4i56YQB+Rkt35oGJt5b/kREwJLaaUWS+/z61EiX59tlfr0hNWTeaplReuHxV38TSfvh\nHA1Ai0glWVE7636SDqE7+za+7dXW9bf+mISZ3qpd4emtnD+ByfY7NBGptep+7aQOJ9nphvTMu3vk\nvP5x88SiVelRc29OnzUnZz1/mb4tiEgVCETtzAUWA0uB8YfY51Fv+3ygW8z6lcACYC4w+xBtA5Fk\nVTpxaOdzpnQ/ZtnSjJD5smGDkrwW573bLTqrmd9xiUid4nvtjADLgBwgAZgHdCyzz2DgbW++F/B5\nzLYVxH8nge9JVok7WyX1PLfP7x4+KXP35uSw+Wd2yx15WVdNuphX9BY3EakOvtfOPsCMmOUJ3hTr\nCeDSmOXFQBNvfgWQFed3+J7kUbl8cOfcAd0+/+vxiaVbEqPm5ewTvpmedLEehyEi1a3aa2e8Sypb\nAKtjltfgfkuIt08LYCNuAu8BJcB04KmjCTYwLh4eTi4K3Xnxtq8m3vrhO1nNtycVzU464aX8vVtH\nX7b5mx3wjd8RiogctXgdREV7qEMNup4GrAOygXdxv13MquDPDJ4ho1o3Dq959Nr17w4aPW9PZAf1\nt+wpbjPy2J+WPtOqYG6J3+GJiFSleB3EWiD2cQ8tcb8hHG6fY7x14HYOAJuB14GelN9BODHzM70p\nGPpPC5Hy43WdorOn3rL2mVZX/tuUfp/U6KvGu/eMbmW2fgVb/Y5QROzQ35sCIwosxx2kTiT+IHVv\n9g9SpwL1vPk04BNgYDm/I5hjEH1+2zR03vUvDLjg2J/eaJ1Sui2aUPh9UsYTBpr7HZqICAGpnYOA\nJbhXM+V560Z60z6/97bPB7p769rgdijzgIUxbcsKRJIuE6Lv/cOSh5+/8JrBqaULMlOLNiYmb9ob\nCo8yboceMyztAAAGt0lEQVQnIhIUAaqd1cf/JI/5JJOzxz3R+PJTdk09NbVkc2JC4ZaEpM8MDDKg\ny1RFJIj8r501wL8kO73ShfNumnXi1Y1Lnj0+46fdkUhBYSj0JwMn+BaTiEjFqIOohl8XotvTF0cv\nunjpRRelln6SXS+/IBz+0cAU415tJSJSG6iDqMJfk0Cf303JHnbmtgmnJ5euT04syI+EvzYwwrgD\n8CIitYk6iKOWtTiL/lOf73ZRp71/PiGlOD8SLtwTCb1o4ORq/b0iItVLHcQRa/3eSYkDb/v4stxG\npZ83SS7anhDdtjfMJJ1GEpE6Qh1EJX9UiI6vDmsx8JLlU/umlG5IiRatT05cYGCYiX9ToIhIbaIO\nooI/IjHc9cmJfQecvuPFTgmlO6PhorWpCX810Pnof7aISCCpgzisxJ1Zqb3uff6a09rv/bJJtHRD\ncsLOTSnhSQYyqzA+EZEgUgdRrsYLOrftPeqze0/OKt2YEi5d1CBl6Y5ELjTu+ytERGygDiJmt1Ck\n1btDz+lx3qq/tk802xLDJXOyUt8uDHN89YYnIhJI6iDAJDVq96cpo7r22DUvO2xWpCfumd046X4D\n9WsmPBGRQLK4g0jYld2tY94rv+3YqnhzSsh8lp22Zk6T0CV6NpKICGBjB5GQ/v1JwztcNvcfreqX\nbkkOlb7Vsv6nc5sc9IhxERHb2dJBmNBx2a+PuKfdGRu+bRg1izIT9v6lXf1nljQk3e/gREQCyo4O\n4g+tOhZsSQ6ZGcekb/1j57RbzKFfYSoiIi47Oog/HZ/57X2nRnr6HYiISC1iRwfhdwAiIrVQtddO\nXREkIiLlUgchIiLlqkgHkQssBpYC4w+xz6Pe9vlAt0q2FRGRWigCLANygARgHhx0T8Jg4G1vvhfw\neSXagsYgYvX3O4AA6e93AAHS3+8AAqS/3wEEiO9jED1xi/xKoAh4GbigzD7nA895818AGUDTCraV\nA/X3O4AA6e93AAHS3+8AAqS/3wHYJF4H0QJYHbO8xltXkX2aV6CtiIgEVLwOoqJfYXRjm4hIHRPv\nNZxrgZYxyy1xvwkcbp9jvH0SKtAWYDkah4g1ze8AAkTHYj8di/10LFzL/Q4g6gWRAyQSf5C6N/sH\nqSvSVkREarFBwBLcAec8b91Ib9rn9972+UD3OG1FRERERESOTF28ka4l8CHwDbAQ+Lm3viHwLvAd\n8H+4lwPvk4d7DBYDA2PWnwz829v2SMz6JOAVb/3nQKuqTqKKRYC5wJvesq3HIgN4FVgEfIt735Ct\nxyIP92/k38BLuLHbciz+BGzEjXufmsr9Gu93fAdcffSpVJ+K3khX2zQFunrz6bin2DoCDwDjvPXj\ngfu9+U64uSfgHotl7L8qbDbu/STgjvPkevO3Ao9785fi3mMSZHcDfwHe8JZtPRbPAdd781GgAXYe\nixzge9xCBm4xuwZ7jkU/3CdOxHYQNZF7Q9xx4Qxv2jcfSH2AGTHLE7yprvk7cDZu79/EW9fUWwb3\n00Hst6cZuIP9zXA/ae5zGfBEzD69vPkosLnKo646xwDvAWey/xuEjceiAW5RLMvGY9EQ94NTJm6c\nbwLnYNexyOHADqImch8B/DGmzRNeu0Py82F9FbkJr7bLwf2k8AXu//yN3vqN7P/H0JwDL/+NvdEw\ndv1a9h+f2GNXDOzA/aMLooeAe4DSmHU2HovWuH+ofwa+Bp4C0rDzWPwI/A5YBawDtuOeXrHxWOxT\n3blnHeZnHZKfHURdv/chHfgbcAewq8w2Q93PH+BnwCbc8YdD3Uxpy7GI4l7h97j3390c/I3ZlmPR\nFrgT9wNUc9y/lSvL7GPLsShPYHL3s4OoyE14tVUCbufwAu4pJnA/FTT15pvhFk449I2Ga735suv3\ntTnWm993LvvHqgu/ypyK+6yuFcD/AANwj4mNx2KNN33pLb+K21FswL5jcQrwKbAV9xPua7innG08\nFvtU99/E1nJ+VqBrbl29kS4EPI97aiXWA+w/lziBgwehEnFPQyxn/6ftL3DPJYY4eBBq37nEywjW\nANyhnMH+MQhbj8XHQHtv3sE9DjYeiy64V/il4ObwHDAau45FDgcPUld37g1xx8EycMd/9s0HVl28\nke403PPt83BPrczF/R/XEHewtrzL2CbiHoPFwLkx6/ddxrYM950b+yQBf2X/ZWw5VZ9GlTuD/Vcx\n2XosuuB+g5iP+6m5AfYei3Hsv8z1Odxv3bYci//BHXvZiztWcB01l/t13vqluFeOiYiIiIiIiIiI\niIiIiIiIiIiIiIiIiIiIiIiI1F3/H2051/Gnd6GrAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_insert_to_list,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are containers in Python that work well for insertion at the start:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from collections import deque" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_insert_to_deque(count):\n", + " return repeat('before.appendleft(0)','from collections import deque; before=deque([0]*'+str(count)+')',number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.0016000000000000001)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\nuBMRERERERERERERERERERERERERERERERERERHJHv8fL5MOraCnC/gAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_insert_to_deque,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But looking up in the middle scales badly:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_lookup_middle_element_in_deque(count):\n", + " before=deque([0]*count)\n", + " def totime():\n", + " x=before[count/2]\n", + " return repeat(totime,number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.040000000000000001)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n/23QgaYRCqQ5AAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_lookup_middle_element_in_deque,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What is going on here?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Arrays are stored as contiguous memory. Anything which changes the length of the array requires the whole array to be copied elsewhere in memory." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This copy takes time proportional to the array size." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Python `list` type is **also** an array, but it is allocated with **extra memory**. Only when that memory is exhausted is a copy needed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the extra memory is typically the size of the current array, a copy is needed every 1/N appends, and costs N to make, so **on average** copies are cheap. We call this **amortized constant time**. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The deque type works differently: each element contains a pointer to the next. Inserting elements is therefore very cheap, but looking up the Nth element requires traversing N such pointers." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dictionary performance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For another example, let's consider the performance of a dictionary versus a couple of other ways in which we could implement an associative array." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class evildict(object):\n", + " def __init__(self,data):\n", + " self.data=data\n", + " def __getitem__(self,akey):\n", + " for key,value in self.data:\n", + " if key==akey:\n", + " return value\n", + " raise KeyError()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "me=[[\"Name\",\"James\"],[\"Job\",\"Programmer\"],[\"Home\",\"London\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "me_evil=evildict(me)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Programmer'" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "me_evil[\"Job\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "me_dict=dict(me)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Programmer'" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "me_evil[\"Job\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "class sorteddict(object):\n", + " def __init__(self,data):\n", + " self.data=sorted(data,key=lambda x:x[0])\n", + " self.keys=map(lambda x:x[0],self.data)\n", + " def __getitem__(self,akey):\n", + " from bisect import bisect_left\n", + " loc=bisect_left(self.keys,akey)\n", + " if loc!=len(self.data):\n", + " return self.data[loc][1]\n", + " raise KeyError()" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "me_sorted=sorteddict(me)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Programmer'" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "me_sorted[\"Job\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def time_dict_generic(ttype,count,number=10000):\n", + " from random import randrange\n", + " keys=range(count)\n", + " values=[0]*count\n", + " data=ttype(zip(keys,values))\n", + " def totime():\n", + " x=data[keys[count/2]]\n", + " return repeat(totime,number=10000)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "time_dict=lambda count: time_dict_generic(dict,count)\n", + "time_sorted=lambda count: time_dict_generic(sorteddict,count)\n", + "time_evil=lambda count: time_dict_generic(evildict,count)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.0065000000000000006)" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "counts=np.arange(1,1000,100)\n", + "plt.plot(counts,map(time_dict,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.045999999999999999)" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\nAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_sorted,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 0.35000000000000003)" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(counts,map(time_evil,counts))\n", + "plt.ylim(ymin=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can't really see what's going on here for the sorted example as there's too much noise, but theoretically we should get **logarithmic** asymptotic performance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We write this down as $O(\\ln N)$. This doesn't mean there isn't also a constant term, or a term proportional to something that grows slower (such as $\\ln(\\ln N)$): we always write down just the term that is dominant for large $N$. Similarly, the hash-table based solution used by `dict` is $O(1)$ and the simple check-each-in-turn solution is $O(N)$. We saw before that `list` is $O(1)$ for appends, $O(N)$ for inserts. Numpy's `array` is $O(N)$ for appends." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Exercise: determine what the asymptotic peformance for the Boids model in terms of the number of Boids. Make graphs to support this. Bonus: how would the performance scale with the number of dimensions?" + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Scaling" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/040cython.ipynb b/session08/040cython.ipynb new file mode 100644 index 00000000..7be6cfaa --- /dev/null +++ b/session08/040cython.ipynb @@ -0,0 +1,540 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xmin=-1.5\n", + "ymin=-1.0\n", + "xmax=0.5\n", + "ymax=1.0\n", + "resolution=300\n", + "xstep=(xmax-xmin)/resolution\n", + "ystep=(ymax-ymin)/resolution\n", + "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", + "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The Cython extension is already loaded. To reload it, use:\n", + " %reload_ext Cython\n" + ] + } + ], + "source": [ + "%load_ext Cython" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def mandel(position,limit=50):\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%cython\n", + "def mandel_cython(position,limit=50):\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "data_cy=[[mandel_cython(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 414 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data_cy=[[mandel_cython(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is about a factor of 2 faster than the pure python." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But we can do much better, by telling Cython what C types we would use for the code:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%cython\n", + "def typed_mandel_cython(position,limit=50):\n", + " cdef double complex value\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 350 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "data_cy=[[typed_mandel_cython(complex(x,y)) for x in xs] for y in ys]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We might not really seeing how well Cython is really doing, as our loop could now be the limit:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100000 loops, best of 3: 9.22 µs per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "mandel(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100000 loops, best of 3: 4.19 µs per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "mandel_cython(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100000 loops, best of 3: 7.88 µs per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "typed_mandel_cython(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%cython\n", + "cpdef call_typed_mandel_cython(double complex position,int limit=50):\n", + " cdef double complex value\n", + " value=position\n", + " while abs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100000 loops, best of 3: 2.94 µs per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "call_typed_mandel_cython(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note we're not actually writing C, we're writing Python with C types." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can call C functions instead of Python functions with appropriate imports. E.g., let's get complex abs from the C standard library." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%cython\n", + "from libc.math cimport abs as cabs\n", + "cpdef mandel_cython_2(double complex position,int limit=50):\n", + " cdef double complex value\n", + " value=position\n", + " while cabs(value)<2:\n", + " limit-=1\n", + " value=value**2+position\n", + " if limit<0:\n", + " return 0\n", + " return limit" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100000 loops, best of 3: 3.02 µs per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "mandel_cython_2(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cython integrates nicely with NumPy:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]\n", + "values=xmatrix+1j*ymatrix" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "numpy_cython_1=np.vectorize(mandel_cython_2)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 loops, best of 3: 135 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit \n", + "numpy_cython_1(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "%matplotlib inline\n", + "plt.imshow(numpy_cython_1(values),interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fast, but slower than our pure NumPy. Maybe explicit C-style looping is faster?" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%cython\n", + "import numpy as np\n", + "cimport numpy as np\n", + "cpdef numpy_cython_2(np.ndarray[double complex,ndim=2] position, int limit=50):\n", + " cdef np.ndarray[long,ndim=2] diverged_at\n", + " cdef double complex value\n", + " cdef int xlim\n", + " cdef int ylim\n", + " cdef double complex pos\n", + " cdef steps\n", + "\n", + " \n", + " xlim=position.shape[1]\n", + " ylim=position.shape[0]\n", + " diverged_at=np.zeros([ylim, xlim], dtype=int)\n", + " for y in range(ylim):\n", + " for x in range(xlim):\n", + " steps=limit\n", + " value=position[y,x]\n", + " pos=position[y,x]\n", + " while abs(value)<2 and steps>=0:\n", + " steps-=1\n", + " value=value**2+pos\n", + " diverged_at[y,x]=steps\n", + " \n", + " return diverged_at" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loops, best of 3: 219 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "numpy_cython_2(values)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "%matplotlib inline\n", + "plt.imshow(numpy_cython_2(values),interpolation='none')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Not yet faster." + ] + } + ], + "metadata": { + "jekyll": { + "display_name": "Cython" + }, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/session08/Cython.ipynb b/session08/Cython.ipynb deleted file mode 100644 index d7350013..00000000 --- a/session08/Cython.ipynb +++ /dev/null @@ -1,440 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:a4003e28aa6d0cd5c284caa895edb2c1ad1636d4d35317dfeec6858bd481081c" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xmin=-1.5\n", - "ymin=-1.0\n", - "xmax=0.5\n", - "ymax=1.0\n", - "resolution=300\n", - "xstep=(xmax-xmin)/resolution\n", - "ystep=(ymax-ymin)/resolution\n", - "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", - "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load_ext cythonmagic\n" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel(position,limit=50):\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "def mandel_cython(position,limit=50):\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data_cy=[[mandel_cython(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data_cy=[[mandel_cython(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is about a factor of 2 faster than the pure python." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But we can do much better, by telling Cython what C types we would use for the code:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "def typed_mandel_cython(position,limit=50):\n", - " cdef double complex value\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data_cy=[[typed_mandel_cython(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We might not really seeing how well Cython is really doing, as our loop could now be the limit:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "mandel(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "mandel_cython(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "typed_mandel_cython(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "cpdef call_typed_mandel_cython(double complex position,int limit=50):\n", - " cdef double complex value\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "call_typed_mandel_cython(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note we're not actually writing C, we're writing Python with C types." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can call C functions instead of Python functions with appropriate imports. E.g., let's get complex abs from the C standard library." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "from libc.math cimport abs as cabs\n", - "cpdef mandel_cython_2(double complex position,int limit=50):\n", - " cdef double complex value\n", - " value=position\n", - " while cabs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "mandel_cython_2(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Cython integrates nicely with NumPy:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]\n", - "values=xmatrix+1j*ymatrix" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "numpy_cython_1=np.vectorize(mandel_cython_2)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit \n", - "numpy_cython_1(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from matplotlib import pyplot as plt\n", - "%matplotlib inline\n", - "plt.imshow(numpy_cython_1(values),interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Fast, but slower than our pure NumPy. Maybe explicit C-style looping is faster?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "import numpy as np\n", - "cimport numpy as np\n", - "cpdef numpy_cython_2(np.ndarray[double complex,ndim=2] position, int limit=50):\n", - " cdef np.ndarray[long,ndim=2] diverged_at\n", - " cdef double complex value\n", - " cdef int xlim\n", - " cdef int ylim\n", - " cdef double complex pos\n", - " cdef steps\n", - "\n", - " \n", - " xlim=position.shape[1]\n", - " ylim=position.shape[0]\n", - " diverged_at=np.zeros([ylim, xlim], dtype=int)\n", - " for y in range(ylim):\n", - " for x in range(xlim):\n", - " steps=limit\n", - " value=position[y,x]\n", - " pos=position[y,x]\n", - " while abs(value)<2 and steps>=0:\n", - " steps-=1\n", - " value=value**2+pos\n", - " diverged_at[y,x]=steps\n", - " \n", - " return diverged_at" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "numpy_cython_2(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from matplotlib import pyplot as plt\n", - "%matplotlib inline\n", - "plt.imshow(numpy_cython_2(values),interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "import numpy as np\n", - "cimport numpy as np\n", - "cpdef numpy_cython_3(np.ndarray[double complex,ndim=2] position, int limit=50):\n", - " cdef np.ndarray[long,ndim=2] diverged_at_count\n", - " cdef np.ndarray[double complex,ndim=2] value\n", - "\n", - " cdef np.ndarray[long, ndim=2] diverging\n", - " \n", - " value=position\n", - " diverged_at_count=np.zeros([position.shape[0], position.shape[1]], dtype=int)\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=np.abs(value)>4\n", - " \n", - "\n", - " value[diverging]=2\n", - " \n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "from libc.math cimport abs as cabs\n", - "cpdef wrap_c_abs(double x):\n", - " ertur" - ], - "language": "python", - "metadata": {}, - "outputs": [] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session08/Intro.md.ipynb b/session08/Intro.md.ipynb deleted file mode 100644 index 7d76e18b..00000000 --- a/session08/Intro.md.ipynb +++ /dev/null @@ -1,232 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:7149595657e9c4ebff8f5a298e927595e8cdf9031504840300734f1e9df30e59" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Performance programming" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We've spent most of this course looking at how to make code readable and reliable. For research work, it is often also important that code is efficient: that it does what it needs to do *quickly*." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is very hard to work out beforehand whether code will be efficient or not: it is essential to *Profile* code, to measure its performance, to determine what aspects of it are slow." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we looked at Functional programming, we claimed that code which is conceptualised in terms of actions on whole data-sets rather than individual elements is more efficient. Let's measure the performance of some different ways of implementing some code and see how they perform." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Two Mandelbrots" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You're probably familiar with a famous fractal called the [Mandelbrot Set](https://www.youtube.com/watch?v=AGUlJus5kpY)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For a complex number $c$, $c$ is in the Mandelbrot set if the series $z_{i+1}=z_{i}^2+c$ (With $z_0=c$) stays close to $0$.\n", - "Traditionally, we plot a color showing how many steps are needed for $\\left|z_i\\right|>2$, whereupon we are sure the series will diverge." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here's a trivial python implementation:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel1(position,limit=50):\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xmin=-1.5\n", - "ymin=-1.0\n", - "xmax=0.5\n", - "ymax=1.0\n", - "resolution=300\n", - "xstep=(xmax-xmin)/resolution\n", - "ystep=(ymax-ymin)/resolution\n", - "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", - "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data=[[mandel1(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "plt.imshow(data1,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will learn this lesson how to make a version of this code which works Ten Times faster:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel_numpy(position,limit=50):\n", - " value=position\n", - " diverged_at_count=np.zeros(position.shape)\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=value*np.conj(value)>4\n", - " first_diverged_this_time=np.logical_and(diverging, diverged_at_count==0)\n", - " diverged_at_count[first_diverged_this_time]=limit\n", - " value[diverging]=2\n", - " \n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]\n", - "values=xmatrix+1j*ymatrix\n", - "data_numpy=mandel_numpy(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "plt.imshow(data_numpy,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data_numpy=mandel_numpy(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note we get the same answer:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "sum(sum(abs(data_numpy-data1)))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session08/Mandels.ipynb b/session08/Mandels.ipynb deleted file mode 100644 index 1140399d..00000000 --- a/session08/Mandels.ipynb +++ /dev/null @@ -1,261 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:05d84d11096a588e5e9199fd1eef16298c170325bd1631ed5f0337f3c70cd51c" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xmin=-1.5\n", - "ymin=-1.0\n", - "xmax=0.5\n", - "ymax=1.0\n", - "resolution=300\n", - "xstep=(xmax-xmin)/resolution\n", - "ystep=(ymax-ymin)/resolution\n", - "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", - "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel1(position,limit=50):\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Many Mandelbrots" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's compare our naive python implementation which used a list comprehension, taking 662ms, with the following:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data2=[]\n", - "for y in ys:\n", - " row=[]\n", - " for x in xs:\n", - " row.append(mandel1(complex(x,y)))\n", - " data2.append(row)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data2=[]\n", - "for y in ys:\n", - " row=[]\n", - " for x in xs:\n", - " row.append(mandel1(complex(x,y)))\n", - " data2.append(row)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Interestingly, not much difference. I would have expected this to be slower, due to the normally high cost of **appending** to data." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from matplotlib import pyplot as plt\n", - "plt.imshow(data2,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We ought to be checking if these results are the same by comparing the values in a test, rather than re-plotting. This is cumbersome in pure Python, but easy with NumPy, so we'll do this later." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try a pre-allocated data structure:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data3=[[0 for i in range(resolution)] for j in range(resolution)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "for j,y in enumerate(ys):\n", - " for i,x in enumerate(xs):\n", - " data3[j][i]=mandel1(complex(x,y))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for j,y in enumerate(ys):\n", - " for i,x in enumerate(xs):\n", - " data3[j][i]=mandel1(complex(x,y))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.imshow(data3,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Nope, no gain there. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try using functional programming approaches:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data4=[]\n", - "for y in ys:\n", - " bind_mandel=lambda x: mandel1(complex(x,y))\n", - " data4.append(map(bind_mandel,xs))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data4=[]\n", - "for y in ys:\n", - " bind_mandel=lambda x: mandel1(complex(x,y))\n", - " data4.append(map(bind_mandel,xs))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.imshow(data4,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "That was a tiny bit slower." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So, what do we learn from this? Our mental image of what code should be faster or slower is often wrong, or doesn't make much difference. The only way to really improve code performance is empirically, through measurements." - ] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session08/Numpy.ipynb b/session08/Numpy.ipynb deleted file mode 100644 index f77663e5..00000000 --- a/session08/Numpy.ipynb +++ /dev/null @@ -1,1371 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:ecbe4310c701b675c0b38c0bd0d2cc8165e5fa87eebcf10407466150c114b217" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xmin=-1.5\n", - "ymin=-1.0\n", - "xmax=0.5\n", - "ymax=1.0\n", - "resolution=300\n", - "xstep=(xmax-xmin)/resolution\n", - "ystep=(ymax-ymin)/resolution\n", - "xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)]\n", - "ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try using **numpy**" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "NumPy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Numerical Python, NumPy, is a library that enables us to do much faster work with floating point data than ordinary python." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "NumPy constructors" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "NumPy's core type is the `ndarray`, or N-Dimensional Array:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.zeros([3,4,2])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We rarely construct an ndarray directly:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.ndarray([2,2,2],dtype='int')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can convert any Python iterable into an `ndarray` with the `array` magic constructor:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=np.array(xrange(5))\n", - "print x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But NumPy arrays can only contain one type of data, unlike Python lists, which is one source of their speed:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.array([1,1.0,'one'])" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "NumPy decided to make them all strings." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The real magic of numpy arrays is that most python operations are applied, quickly, on an elementwise basis:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x*2" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Ndarray multiplication is element wise, not matrix multiplication or vector dot product:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x*x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Numpy's mathematical functions also happen this way, and are said to be \"vectorized\" functions." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.sqrt(x)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Numpy contains many useful functions for creating matrices. In our earlier lectures we've seen `linspace` and `arange` for evenly spaced numbers." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.linspace(0,10,21)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.arange(0,10,0.5)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " Here's one for creating matrices like coordinates in a grid:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "xmin=-1.5\n", - "ymin=-1.0\n", - "xmax=0.5\n", - "ymax=1.0\n", - "resolution=300\n", - "xstep=(xmax-xmin)/resolution\n", - "ystep=(ymax-ymin)/resolution\n", - "\n", - "ymatrix, xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print ymatrix" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can add these together to make a grid containing the complex numbers we want to test for membership in the Mandelbrot set." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "values=xmatrix+1j*ymatrix" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print values" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Arraywise Algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use this to apply the mandelbrot algorithm to whole *ARRAYS*" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z0=values\n", - "z1=z0*z0+values\n", - "z2=z1*z1+values\n", - "z3=z2*z2+values" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print z3" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So can we just apply our `mandel1` function to the whole matrix?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel1(position,limit=50):\n", - " value=position\n", - " while abs(value)<2:\n", - " limit-=1\n", - " value=value**2+position\n", - " if limit<0:\n", - " return 0\n", - " return limit" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "mandel1(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "No. The *logic* of our current routine would require stopping for some elements and not for others. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can ask numpy to **vectorise** our method for us:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "mandel2=np.vectorize(mandel1)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data5=mandel2(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from matplotlib import pyplot as plt\n", - "%matplotlib inline\n", - "plt.imshow(data5,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Is that any faster?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "data5=mandel2(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is not significantly faster. When we use *vectorize* it's just hiding an plain old python for loop under the hood. We want to make the loop over matrix elements take place in the \"**C Layer**\"." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What if we just apply the Mandelbrot algorithm without checking for divergence until the end:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel_numpy_explode(position,limit=50):\n", - " value=position\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=abs(value)>2\n", - "\n", - " \n", - " return abs(value)<2" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data6=mandel_numpy_explode(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OK, we need to prevent it from running off to $\\infty$" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel_numpy(position,limit=50):\n", - " value=position\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=abs(value)>2\n", - " # Avoid overflow\n", - " value[diverging]=2\n", - " \n", - " return abs(value)<2" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data6=mandel_numpy(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "data6=mandel_numpy(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from matplotlib import pyplot as plt\n", - "%matplotlib inline\n", - "plt.imshow(data6,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Wow, that was TEN TIMES faster." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There's quite a few NumPy tricks there, let's look at them:" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Logical Arrays" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "diverging=abs(z3)>2\n", - "print diverging[30]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we apply a logical condition to a NumPy array, we get a logical array." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=np.arange(10)*1.0\n", - "y=np.ones([10])*5\n", - "z=x>y\n", - "print z" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Logical arrays can be used to index into arrays:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x[x>80]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x[np.logical_not(z)]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And you can use such an index as the target of an assignment:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x[z]=5\n", - "print x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Broadcasting" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In our example above, we didn't compare two arrays to get our logical array, but an array to a scalar integer." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we apply an operation to things of different shapes, NumPy will **broadcast** the smaller index:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print x>5" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This can be used quite creatively:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "row=np.array([[1,2,3]])\n", - "column=np.array([[0],[2],[4]])\n", - "print row.shape" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print column.shape" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "row*column" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=np.ones([4,1,2])\n", - "y=np.ones([1,4,1])\n", - "print (x+y).shape\n", - "print x+y" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "More Mandelbrot" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Of course, we didn't calculate the number-of-iterations-to-diverge, just whether the point was in the set." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's correct our code to do that:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel4(position,limit=50):\n", - " value=position\n", - " diverged_at_count=np.zeros(position.shape)\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=abs(value)>2\n", - " first_diverged_this_time=np.logical_and(diverging, \n", - " diverged_at_count==0)\n", - " diverged_at_count[first_diverged_this_time]=limit\n", - " value[diverging]=2\n", - " \n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data7=mandel4(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.imshow(data7,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "data7=mandel4(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that here, all the looping over mandelbrot steps was in Python, but everything below the loop-over-positions happened in C. The code was amazingly quick compared to pure Python." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Can we do better by avoiding a square root?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel5(position,limit=50):\n", - " value=position\n", - " diverged_at_count=np.zeros(position.shape)\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+position\n", - " diverging=value*np.conj(value)>4\n", - " first_diverged_this_time=np.logical_and(diverging, diverged_at_count==0)\n", - " diverged_at_count[first_diverged_this_time]=limit\n", - " value[diverging]=2\n", - " \n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "data8=mandel5(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Probably not worth the time I spent thinking about it!" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "NumPy Testing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's look at calculating those residuals, the differences between the different datasets." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data8=mandel5(values)\n", - "data5=mandel2(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.sum((data8-data5)**2)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "For our non-numpy datasets, numpy knows to turn them into arrays:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data1=[[mandel1(complex(x,y)) for x in xs] for y in ys]\n", - "sum(sum((data1-data7)**2))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But this doesn't work for pure non-numpy arrays" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data2=[]\n", - "for y in ys:\n", - " row=[]\n", - " for x in xs:\n", - " row.append(mandel1(complex(x,y)))\n", - " data2.append(row)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data2-data1" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So we have to convert to NumPy arrays explicitly:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "sum(sum((np.array(data2)-np.array(data1))**2))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "NumPy provides some convenient assertions to help us write unit tests with NumPy arrays:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x = [1e-5, 1e-3, 1e-1]\n", - "y = np.arccos(np.cos(x))\n", - "np.testing.assert_allclose(x, y, rtol=1e-6, atol=1e-20)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "np.testing.assert_allclose(data7, data1)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Arraywise operations are fast" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that we might worry that we carry on calculating the mandelbrot values for points that have already diverged." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel6(position,limit=50):\n", - " value=np.zeros(position.shape)+position\n", - " calculating=np.ones(position.shape,dtype='bool')\n", - " diverged_at_count=np.zeros(position.shape)\n", - " while limit>0:\n", - " limit-=1\n", - " value[calculating]=value[calculating]**2+position[calculating]\n", - " diverging_now=np.zeros(position.shape,dtype='bool')\n", - " diverging_now[calculating]=value[calculating\n", - " ]*np.conj(value[calculating])>4\n", - " calculating=np.logical_and(calculating,\n", - " np.logical_not(diverging_now))\n", - " diverged_at_count[diverging_now]=limit\n", - "\n", - " \n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data8=mandel6(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "data8=mandel6(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.imshow(data8,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This was **not faster** even though it was **doing less work**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This often happens: on modern computers, **branches** (if statements, function calls) and **memory access** is usually the rate-determining step, not maths." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Complicating your logic to avoid calculations sometimes therefore slows you down. The only way to know is to **measure**" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Indexing with arrays" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We've been using Boolean arrays a lot to get access to some elements of an array. We can also do this with integers:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=np.arange(64)\n", - "y=x.reshape([8,8])\n", - "y" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y[[0,5,2]]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y[[0,2,5],[1,2,7]]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use a : to indicate we want all the values from a particular axis:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y[0:8:2,[0,2]]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can mix array selectors, boolean selectors, :s and ordinary array seqeuencers:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z=x.reshape([4,4,4])\n", - "print z" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z[:,[1,3],0:3]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can manipulate shapes by adding new indices in selectors with np.newaxis:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z[:,np.newaxis,[1,3],0].shape" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we use basic indexing with integers and : expressions, we get a **view** on the matrix so a copy is avoided:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a=z[:,b,2]\n", - "a[0,0]=-500\n", - "z" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also use ... to specify \": for as many as possible intervening axes\":" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z[1]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "z[...,2]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, boolean mask indexing and array filter indexing always causes a copy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try again at avoiding doing unnecessary work by using new arrays containing the reduced data instead of a mask:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def mandel7(position,limit=50):\n", - " positions=np.zeros(position.shape)+position\n", - " value=np.zeros(position.shape)+position\n", - " indices=np.mgrid[0:values.shape[0],0:values.shape[1]]\n", - " diverged_at_count=np.zeros(position.shape)\n", - " while limit>0:\n", - " limit-=1\n", - " value=value**2+positions\n", - " diverging_now=value*np.conj(value)>4\n", - " diverging_now_indices=indices[:,diverging_now]\n", - " carry_on=np.logical_not(diverging_now)\n", - "\n", - " value=value[carry_on]\n", - " indices=indices[:,carry_on]\n", - " positions=positions[carry_on]\n", - " diverged_at_count[diverging_now_indices[0,:],\n", - " diverging_now_indices[1,:]]=limit\n", - "\n", - " return diverged_at_count" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "data9=mandel7(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.imshow(data9,interpolation='none')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "\n", - "data9=mandel7(values)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Still slower. Probably due to lots of copies." - ] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session08/Scaling.ipynb b/session08/Scaling.ipynb deleted file mode 100644 index 59be7df9..00000000 --- a/session08/Scaling.ipynb +++ /dev/null @@ -1,588 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:7a55063244b13c79775f7d6e1e0abd04a19ac2e0705966450b3d82203dfa5053" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Scaling for containers and algorithms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We've seen that NumPy arrays are really useful. Why wouldn't we always want to use them for data which is all the same type?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "from timeit import repeat\n", - "from matplotlib import pyplot as plt\n", - "%matplotlib inline" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_append_to_ndarray(count):\n", - " return repeat('np.append(before,[0])',\n", - " 'import numpy as np; before=np.ndarray('+str(count)+')',\n", - " number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_append_to_list(count):\n", - " return repeat('before.append(0)',\n", - " 'before=[0]*'+str(count),\n", - " number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "counts=np.arange(1,100000,10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_append_to_list,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_append_to_ndarray,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Both scale well for accessing the middle element:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_lookup_middle_element_in_list(count):\n", - " before=[0]*count\n", - " def totime():\n", - " x=before[count/2]\n", - " return repeat(totime,number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_lookup_middle_element_in_ndarray(count):\n", - " before=np.ndarray(count)\n", - " def totime():\n", - " x=before[count/2]\n", - " return repeat(totime,number=10000)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_lookup_middle_element_in_list,counts))\n", - "plt.ylim(ymin=0)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_lookup_middle_element_in_ndarray,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But a list performs badly for insertions at the beginning:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x=range(5)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x[0:0]=[-1]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_insert_to_list(count):\n", - " return repeat('before[0:0]=[0]','before=[0]*'+str(count),number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_insert_to_list,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are containers in Python that work well for insertion at the start:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from collections import deque" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_insert_to_deque(count):\n", - " return repeat('before.appendleft(0)','from collections import deque; before=deque([0]*'+str(count)+')',number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_insert_to_deque,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But looking up in the middle scales badly:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_lookup_middle_element_in_deque(count):\n", - " before=deque([0]*count)\n", - " def totime():\n", - " x=before[count/2]\n", - " return repeat(totime,number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_lookup_middle_element_in_deque,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What is going on here?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Arrays are stored as contiguous memory. Anything which changes the length of the array requires the whole array to be copied elsewhere in memory." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This copy takes time proportional to the array size." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Python `list` type is **also** an array, but it is allocated with **extra memory**. Only when that memory is exhausted is a copy needed." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the extra memory is typically the size of the current array, a copy is needed every 1/N appends, and costs N to make, so **on average** copies are cheap. We call this **amortized constant time**. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The deque type works differently: each element contains a pointer to the next. Inserting elements is therefore very cheap, but looking up the Nth element requires traversing N such pointers." - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Dictionary performance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For another example, let's consider the performance of a dictionary versus a couple of other ways in which we could implement an associative array." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class evildict(object):\n", - " def __init__(self,data):\n", - " self.data=data\n", - " def __getitem__(self,akey):\n", - " for key,value in self.data:\n", - " if key==akey:\n", - " return value\n", - " raise KeyError()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me=[[\"Name\",\"James\"],[\"Job\",\"Programmer\"],[\"Home\",\"London\"]]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_evil=evildict(me)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_evil[\"Job\"]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_dict=dict(me)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_evil[\"Job\"]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class sorteddict(object):\n", - " def __init__(self,data):\n", - " self.data=sorted(data,key=lambda x:x[0])\n", - " self.keys=map(lambda x:x[0],self.data)\n", - " def __getitem__(self,akey):\n", - " from bisect import bisect_left\n", - " loc=bisect_left(self.keys,akey)\n", - " if loc!=len(self.data):\n", - " return self.data[loc][1]\n", - " raise KeyError()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_sorted=sorteddict(me)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "me_sorted[\"Job\"]" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def time_dict_generic(ttype,count,number=10000):\n", - " from random import randrange\n", - " keys=range(count)\n", - " values=[0]*count\n", - " data=ttype(zip(keys,values))\n", - " def totime():\n", - " x=data[keys[count/2]]\n", - " return repeat(totime,number=10000)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "time_dict=lambda count: time_dict_generic(dict,count)\n", - "time_sorted=lambda count: time_dict_generic(sorteddict,count)\n", - "time_evil=lambda count: time_dict_generic(evildict,count)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "counts=np.arange(1,1000,100)\n", - "plt.plot(counts,map(time_dict,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_sorted,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.plot(counts,map(time_evil,counts))\n", - "plt.ylim(ymin=0)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": "" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can't really see what's going on here for the sorted example as there's too much noise, but theoretically we should get **logarithmic** asymptotic performance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We write this down as $O(\\ln N)$. This doesn't mean there isn't also a constant term, or a term proportional to something that grows slower (such as $\\ln(\\ln N)$): we always write down just the term that is dominant for large $N$. Similarly, the hash-table based solution used by `dict` is $O(1)$ and the simple check-each-in-turn solution is $O(N)$. We saw before that `list` is $O(1)$ for appends, $O(N)$ for inserts. Numpy's `array` is $O(N)$ for appends." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Exercise: determine what the asymptotic peformance for the Boids model in terms of the number of Boids. Make graphs to support this. Bonus: how would the performance scale with the number of dimensions?" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/session08/index.md b/session08/index.md index 5877f3c4..d5a58970 100644 --- a/session08/index.md +++ b/session08/index.md @@ -2,71 +2,4 @@ title: Performance Programming --- -## Performance Programming - -### Performance Programming - -We've spent most of this course looking at how to make code readable and -reliable. For research work, it is often also important that code is efficient: -that it does what it needs to do quickly. - -## Measure First - -It is very hard to work out beforehand whether code will be efficient or not: -it is essential to Profile code, to measure its performance, to determine what -aspects of it are slow. - -## Two Mandelbrots - -### The Mandelbrot Set - -You're probably familiar with a famous fractal called the [Mandelbrot Set](https://www.youtube.com/watch?v=AGUlJus5kpY) - -For a complex number $c$, $c$ is in the Mandelbrot set if the series $z_{i+1}=z_{i}^2+c$ (With $z_0=c$) stays close to $0$. - -Traditionally, we plot a color showing how many steps are needed for -$\left|z_i\right|>2$, whereupon we are sure the series will diverge. - -### Simple Implementation - -{{pyfrag('08','mandel','TrivialMandel')}} - -![](session08/python/mandel.png) - -### How does it perform? - -{{pyfrag('08','mandel','ResolutionDefine')}} - -{{pyfrag('08','mandel','GenerateTrivial',timed="687ms")}} - -### We can go faster! - -We will learn this lesson to make a version of this code which works on **arrays** of inputs: - -{{pyfrag('08','mandel','NumpyMandel')}} -{{pyfrag('08','mandel','NumpyGrid')}} - -### Numpy Goes Faster! - -{{pyfrag('08','mandel','GenerateNumpy', timed="55ms")}} - -And it gives the same answer: - -{{pyfrag('08','mandel','NumpyResidual')}} - -## The rest is notebooks - -The remainder of this lecture is presented as IPython Notebooks - -[MandelbrotSets](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session08/Mandels.ipynb) -[NumPy](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session08/Numpy.ipynb) -[Scaling](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session08/Scaling.ipynb) -[Cython](http://nbviewer.ipython.org/url/development.rc.ucl.ac.uk/training/engineering/session08/Cython.ipynb) - -These will be integrated into the rest of the lectures in due course. - -## Exercise: Numpy and the Boids - -Reimplement the boids using NumPy. -Make it as fast as you can: measure the performance versus the default bad_boids and your best object oriented solution. -**Don't** base your NumPy solution on your object oriented solution: I recommend you go back to the original bad boids and start again as you numpify. +Some content diff --git a/session08/python/AppendToArray.png b/session08/python/AppendToArray.png deleted file mode 100644 index ac308fb3..00000000 Binary files a/session08/python/AppendToArray.png and /dev/null differ diff --git a/session08/python/AppendToList.png b/session08/python/AppendToList.png deleted file mode 100644 index bfdfcc30..00000000 Binary files a/session08/python/AppendToList.png and /dev/null differ diff --git a/session08/python/InsertDeque.png b/session08/python/InsertDeque.png deleted file mode 100644 index 6a6f9c1b..00000000 Binary files a/session08/python/InsertDeque.png and /dev/null differ diff --git a/session08/python/InsertList.png b/session08/python/InsertList.png deleted file mode 100644 index ef0a70b1..00000000 Binary files a/session08/python/InsertList.png and /dev/null differ diff --git a/session08/python/LookupArray.png b/session08/python/LookupArray.png deleted file mode 100644 index ff90ae4b..00000000 Binary files a/session08/python/LookupArray.png and /dev/null differ diff --git a/session08/python/LookupDeque.png b/session08/python/LookupDeque.png deleted file mode 100644 index 50021cee..00000000 Binary files a/session08/python/LookupDeque.png and /dev/null differ diff --git a/session08/python/LookupList.png b/session08/python/LookupList.png deleted file mode 100644 index 2a0c334b..00000000 Binary files a/session08/python/LookupList.png and /dev/null differ diff --git a/session08/python/TimeDict.png b/session08/python/TimeDict.png deleted file mode 100644 index 831a09c8..00000000 Binary files a/session08/python/TimeDict.png and /dev/null differ diff --git a/session08/python/TimeEvil.png b/session08/python/TimeEvil.png deleted file mode 100644 index 280f675e..00000000 Binary files a/session08/python/TimeEvil.png and /dev/null differ diff --git a/session08/python/TimeSorted.png b/session08/python/TimeSorted.png deleted file mode 100644 index 4f63890f..00000000 Binary files a/session08/python/TimeSorted.png and /dev/null differ diff --git a/session08/python/mandel.png b/session08/python/mandel.png deleted file mode 100644 index 859c9717..00000000 Binary files a/session08/python/mandel.png and /dev/null differ diff --git a/session08/python/mandel.py b/session08/python/mandel.py deleted file mode 100644 index 46bc0d36..00000000 --- a/session08/python/mandel.py +++ /dev/null @@ -1,260 +0,0 @@ -### "TrivialMandel" - -def trivial_mandel(position,limit=50): - value=position - while abs(value)<2: - limit-=1 - value=value**2+position - if limit<0: - return 0 - return limit - -### "ResolutionDefine" - -xmin=-1.5 -ymin=-1.0 -xmax=0.5 -ymax=1.0 -resolution=300 -xstep=(xmax-xmin)/resolution -ystep=(ymax-ymin)/resolution -xs=[(xmin+(xmax-xmin)*i/resolution) for i in range(resolution)] -ys=[(ymin+(ymax-ymin)*i/resolution) for i in range(resolution)] - -### "Imports" - -import matplotlib.pyplot as plt -import numpy as np -from nose.tools import assert_raises -import time -### "GenerateTrivial" - -trivial_data=[[ - trivial_mandel(complex(x,y)) - for x in xs] - for y in ys] - -### "PlotTrivial" - -plt.imshow(trivial_data,interpolation='none') - -### "SaveTrivial" - -plt.savefig('mandel.png') -plt.figure() - -### "NumpyMandel" - -def numpy_mandel(position,limit=50): - value=position - diverged_at_count=np.zeros(position.shape) - while limit>0: - limit-=1 - value=value**2+position - diverging=value*np.conj(value)>4 - first_diverged_this_time=np.logical_and(diverging, - diverged_at_count==0) - diverged_at_count[first_diverged_this_time]=limit - value[diverging]=2 - return diverged_at_count - -### "NumpyGrid" - -ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep] -values=xmatrix+1j*ymatrix - -### "GenerateNumpy" - -numpy_data=numpy_mandel(values) - -### "PlotNumpy" - -plt.imshow(numpy_data,interpolation='none') - -### "NumpyResidual" - -sum(sum(abs(numpy_data-trivial_data))) - -### "AppendMandel" - -append_data=[] -for y in ys: - row=[] - for x in xs: - row.append(trivial_mandel(complex(x,y))) - append_data.append(row) - -### "PreAllocMandel" - -pre_alloc_data=[[0 for i in range(resolution)] - for j in range(resolution)] -for j,y in enumerate(ys): - for i,x in enumerate(xs): - pre_alloc_data[j][i]=trivial_mandel(complex(x,y)) - -### "FuncMandel" - -func_data=[] -for y in ys: - bind_mandel=lambda x: trivial_mandel(complex(x,y)) - func_data.append(map(bind_mandel,xs)) - -### "Zeros" - -np.zeros([3,4,2]) -### "NDArray" -np.ndarray([2,2,2],dtype='int') -### "FromList" -x=np.array(xrange(5)) -print x -### "SingleType" -np.array([1,1.0,'one']) -### "Elementwise" -x*2 -### "ElementwiseMultiply" -x*x -### "VectorizedFunction" -np.sqrt(x) -### "Linspace" -np.linspace(0,10,21) -### "Arange" -np.arange(0,10,0.5) -### "Mgrid" -ymatrix,xmatrix=np.mgrid[ymin:ymax:ystep,xmin:xmax:xstep] -print xmatrix -print ymatrix -### "ComplexCoordinate" -values=xmatrix+1j*ymatrix -print values -### "ArraywiseAlgorithms" -z0=values -z1=z0*z0+values -z2=z1*z1+values -z3=z2*z2+values -print z3 -### "ArraywiseBranching" -with assert_raises(ValueError): - trivial_mandel(values) -### "VectorizeMetafunction" -vectorized_mandel=np.vectorize(trivial_mandel) -### "GenerateVectorized" -vectorized_data=vectorized_mandel(values) -### "Exploding" -def exploding_mandel(position,limit=50): - value=position - while limit>0: - limit-=1 - value=value**2+position - diverging=abs(value)>2 - return abs(value)<2 -### "GenerateExploding" -exploding_data=exploding_mandel(values) -### "Flat" -def flat_mandel(position,limit=50): - value=position - while limit>0: - limit-=1 - value=value**2+position - diverging=abs(value)>2 - # Avoid overflow - value[diverging]=2 - return abs(value)<2 -### "GenerateFlat" -flat_data=flat_mandel(values) -### "LogicalArray" -diverging=abs(z3)>2 -print diverging[30] -### "ArraywiseCompare" -x=np.arange(10) -y=np.ones([10])*5 -z=x>y -print z -### "LogicalIndexing" -x[z] -### "ArraywiseBooleanOperators" -x[np.logical_not(z)] -### "LogicalAssignment" -x[z]=5 -print x -### "Broadcasting" -print x>5 -### "BroadcastOuterProduct" -row=np.array([[1,2,3]]) -column=np.array([[0],[2],[4]]) -print row.shape -print column.shape -print row*column -### "Residual" -sum(sum(abs(vectorized_data-numpy_data))) -### "ResidualList" -with assert_raises(TypeError): - trivial_data-append_data - -sum(sum(abs(np.array(trivial_data)-np.array(append_data))**2)) -### "NumpyTesting" -x = [1e-5, 1e-3, 1e-1] -y = np.arccos(np.cos(x)) -np.testing.assert_allclose(x, y, rtol=1e-5, atol=0) -### "VectorisedResidual" -np.testing.assert_allclose(vectorized_data, numpy_data) -### "Mask" -def mask_mandel(position,limit=50): - value=np.zeros(position.shape)+position - calculating=np.ones(position.shape,dtype='bool') - diverged_at_count=np.zeros(position.shape) - while limit>0: - limit-=1 - value[calculating]=value[calculating - ]**2+position[calculating] - diverging_now=np.zeros(position.shape,dtype='bool') - diverging_now[calculating]=value[calculating - ]*np.conj(value[calculating])>4 - calculating=np.logical_and(calculating, - np.logical_not(diverging_now)) - diverged_at_count[diverging_now]=limit - return diverged_at_count -### "GenerateMask" -mask_data=mask_mandel(values) -### "Reshape" -x=np.arange(64) -y=x.reshape([8,8]) -print y -### "ArrayIndexing" -y[[0,2]] -### "ArrayIndexing2" -y[[0,2],[1,2]] -### "ColonIndexing" -y[:,[0,2]] -### "Reshape2" -z=x.reshape([4,4,4]) -print z -### "MixedIndexing" -z[:,[1,3],0:3] -### "Newaxis" -z[:,np.newaxis,[1,3],0].shape -### "View" -a=z[:,1:3,2] -a[0,0]=-100 -print z -### "Ellipsis" -z[...,1] -### "Filter" -def filter_mandel(position,limit=50): - positions=np.zeros(position.shape)+position - value=np.zeros(position.shape)+position - indices=np.mgrid[0:values.shape[0],0:values.shape[1]] - diverged_at_count=np.zeros(position.shape) - while limit>0: - limit-=1 - value=value**2+positions - diverging_now=value*np.conj(value)>4 - diverging_now_indices=indices[:,diverging_now] - carry_on=np.logical_not(diverging_now) - value=value[carry_on] - indices=indices[:,carry_on] - positions=positions[carry_on] - diverged_at_count[diverging_now_indices[0,:], - diverging_now_indices[1,:]]=limit - return diverged_at_count -### "GenerateFilter" -filter_data=filter_mandel(values) diff --git a/session08/python/scaling.py b/session08/python/scaling.py deleted file mode 100644 index 971fd3f9..00000000 --- a/session08/python/scaling.py +++ /dev/null @@ -1,131 +0,0 @@ -### "Imports" -import numpy as np -from timeit import repeat -from matplotlib import pyplot as plt -### "TimeAppendToList" -counts=np.arange(1,100000,10000) -def time_append_to_list(count): - return repeat('before.append(0)','before=[0]*'+str(count),number=10000) -plt.plot(counts,map(time_append_to_list,counts)) -plt.ylim(ymin=0) -### "SaveAppendToList" -plt.savefig('AppendToList.png') -plt.figure() -### "TimeAppendToArray" -def time_append_to_ndarray(count): - return repeat('np.append(before,[0])','import numpy as np; before=np.ndarray('+str(count)+')',number=10000) -plt.plot(counts,map(time_append_to_ndarray,counts)) -plt.ylim(ymin=0) -### "SaveAppendToArray" -plt.savefig('AppendToArray.png') -plt.figure() -### "TimeLookupInArray" -def time_lookup_middle_element_in_ndarray(count): - before=np.ndarray(count) - def totime(): - x=before[count/2] - return repeat(totime,number=10000) -plt.plot(counts,map(time_lookup_middle_element_in_ndarray,counts)) -plt.ylim(ymin=0) -### "SaveLookupInArray" -plt.savefig('LookupArray.png') -plt.figure() -### "TimeLookupInList" -def time_lookup_middle_element_in_list(count): - before=[0]*count - def totime(): - x=before[count/2] - return repeat(totime,number=10000) -plt.plot(counts,map(time_lookup_middle_element_in_list,counts)) -plt.ylim(ymin=0) -### "SaveLookupInArray" -plt.savefig('LookupList.png') -plt.figure() -### "TimeInsertToList" -def time_insert_to_list(count): - return repeat('before[0:0]=[0]','before=[0]*'+str(count),number=10000) -plt.plot(counts,map(time_insert_to_list,counts)) -plt.ylim(ymin=0) -### "SaveLookupInArray" -plt.savefig('InsertList.png') -plt.figure() -### "TimeInsertDeque" -from collections import deque -def time_insert_to_deque(count): - return repeat('before.appendleft(0)','from collections import deque; before=deque([0]*'+str(count)+')',number=10000) -plt.plot(counts,map(time_insert_to_deque,counts)) -plt.ylim(ymin=0) -### "SaveInsertDeque" -plt.savefig('InsertDeque.png') -plt.figure() -### "TimeLookupDeque" -def time_lookup_middle_element_in_deque(count): - before=deque([0]*count) - def totime(): - x=before[count/2] - return repeat(totime,number=10000) -plt.plot(counts,map(time_lookup_middle_element_in_deque,counts)) -plt.ylim(ymin=0) -### "SaveLookupDeque" -plt.savefig('LookupDeque.png') -plt.figure() -### "Dict" -me=[["Name","James"],["Job","Programmer"],["Home","London"]] -me_dict=dict(me) -print me_dict["Job"] -### "EvilDict" -class evildict(object): - def __init__(self,data): - self.data=data - def __getitem__(self,akey): - for key,value in self.data: - if key==akey: - return value - raise KeyError() -me_evil=evildict(me) -print me_evil["Job"] -### "SortedDict" -class sorteddict(object): - def __init__(self,data): - self.data=sorted(data,key=lambda x:x[0]) - self.keys=map(lambda x:x[0],self.data) - def __getitem__(self,akey): - from bisect import bisect_left - loc=bisect_left(self.keys,akey) - if loc!=len(self.data): - return self.data[loc][1] - raise KeyError() -me_sorted=sorteddict(me) -print me_sorted["Job"] -### "SetupTimeDicts" -def time_dict_generic(ttype,count,number=10000): - from random import randrange - keys=range(count) - values=[0]*count - data=ttype(zip(keys,values)) - def totime(): - x=data[keys[count/2]] - return repeat(totime,number=10000) -time_dict=lambda count: time_dict_generic(dict,count) -time_sorted=lambda count: time_dict_generic(sorteddict,count) -time_evil=lambda count: time_dict_generic(evildict,count) -counts=np.arange(1,1000,100) -### "TimeDict" -plt.plot(counts,map(time_dict,counts)) -plt.ylim(ymin=0) -### "SaveTimeDict" -plt.savefig('TimeDict.png') -plt.figure() -### "TimeEvil" -plt.plot(counts,map(time_evil,counts)) -plt.ylim(ymin=0) -### "SaveTimeEvil" -plt.savefig('TimeEvil.png') -plt.figure() -### "TimeSorted" -plt.plot(counts,map(time_sorted,counts)) -plt.ylim(ymin=0) -### "SaveTimeSorted" -plt.savefig('TimeSorted.png') -plt.figure() - diff --git a/session09/slides.md b/session09/slides.md deleted file mode 100644 index d875b563..00000000 --- a/session09/slides.md +++ /dev/null @@ -1,2 +0,0 @@ -{{d['session09/index.md']}} -{{d['session09/01advanced.md']}}