├── binder ├── requirements.txt └── info.org ├── book ├── logo.png ├── dev.md ├── static │ ├── data │ │ ├── octave_a.mat │ │ ├── create_octave_data.py │ │ ├── io_savemat.py │ │ └── io_savemat.py.octave │ ├── images │ │ ├── sympy.png │ │ ├── titlepage.png │ │ ├── mayavi │ │ │ ├── mayavi-samp.png │ │ │ └── vector_field_combination.png │ │ ├── odeintsolution8to10.pdf │ │ ├── odeintsolution8to10.png │ │ ├── pylab │ │ │ └── contour_demo.png │ │ └── visual │ │ │ ├── vpythondemo1.png │ │ │ ├── vpythondemo3.png │ │ │ ├── vpythondemo4.png │ │ │ └── vpythondemo3_3d.png │ ├── nbval_sanitize.cfg │ ├── odeintexercisesolution.py │ └── latex_template.tplx ├── _static │ └── myfile.css ├── todo.org ├── _toc.yml ├── 99-changes.ipynb ├── info.org ├── _config.yml ├── opening.md ├── 10-matlab-to-python.ipynb ├── index.ipynb ├── 19-next-steps.ipynb ├── 11-python-shells.ipynb ├── 09-common-tasks.ipynb ├── 13-numeric-computation.ipynb ├── 01-introduction.ipynb ├── 07-functions-modules.ipynb ├── 02-powerful-calculator.ipynb └── 04-introspection.ipynb ├── requirements.txt ├── static └── images │ └── logo.png ├── .dockerignore ├── LICENSE.TXT ├── .gitignore ├── todo.org ├── .github └── workflows │ ├── binder-nbval.yml │ ├── nbval.yml │ ├── deploy.yml │ └── stage.yml ├── Dockerfile ├── Makefile └── Readme.md /binder/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | sympy 4 | matplotlib 5 | pandas 6 | pyarrow 7 | ipython 8 | pytest 9 | nbval 10 | -------------------------------------------------------------------------------- /book/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | sympy 4 | matplotlib 5 | pandas 6 | pyarrow 7 | ipython 8 | pytest 9 | jupyter-book 10 | nbval 11 | -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/static/images/logo.png -------------------------------------------------------------------------------- /book/dev.md: -------------------------------------------------------------------------------- 1 | # Dev 2 | 3 | ## TODO 4 | 5 | - [ ] Add f-string to section 5 6 | 7 | ## Execution Status 8 | 9 | ```{nb-exec-table} 10 | ``` 11 | -------------------------------------------------------------------------------- /book/static/data/octave_a.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/data/octave_a.mat -------------------------------------------------------------------------------- /book/static/images/sympy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/sympy.png -------------------------------------------------------------------------------- /book/static/images/titlepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/titlepage.png -------------------------------------------------------------------------------- /book/static/images/mayavi/mayavi-samp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/mayavi/mayavi-samp.png -------------------------------------------------------------------------------- /book/static/images/odeintsolution8to10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/odeintsolution8to10.pdf -------------------------------------------------------------------------------- /book/static/images/odeintsolution8to10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/odeintsolution8to10.png -------------------------------------------------------------------------------- /book/static/images/pylab/contour_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/pylab/contour_demo.png -------------------------------------------------------------------------------- /book/static/images/visual/vpythondemo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/visual/vpythondemo1.png -------------------------------------------------------------------------------- /book/static/images/visual/vpythondemo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/visual/vpythondemo3.png -------------------------------------------------------------------------------- /book/static/images/visual/vpythondemo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/visual/vpythondemo4.png -------------------------------------------------------------------------------- /book/static/images/visual/vpythondemo3_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/visual/vpythondemo3_3d.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | .Python 6 | venv 7 | .venv 8 | .tox 9 | .coverage 10 | .coverage.* 11 | .cache 12 | *,cover 13 | *.log 14 | .git 15 | .github 16 | -------------------------------------------------------------------------------- /book/static/images/mayavi/vector_field_combination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fangohr/introduction-to-python-for-computational-science-and-engineering/HEAD/book/static/images/mayavi/vector_field_combination.png -------------------------------------------------------------------------------- /book/static/data/create_octave_data.py: -------------------------------------------------------------------------------- 1 | # create octave data without octave 2 | import scipy.io 3 | import numpy as np 4 | 5 | #create two numpy arrays 6 | a = np.linspace(-1, 4, 11) 7 | 8 | tmp_a = {'a': a} 9 | scipy.io.savemat('octave_a.mat', tmp_a) 10 | 11 | 12 | -------------------------------------------------------------------------------- /book/_static/myfile.css: -------------------------------------------------------------------------------- 1 | /* suppress table of contents on the right in 2 | pdf created from html */ 3 | 4 | @media print { 5 | .bd-toc { 6 | visibility: hidden; 7 | } 8 | } 9 | 10 | 11 | /* 12 | p { 13 | text-align: justify; 14 | } 15 | */ 16 | -------------------------------------------------------------------------------- /book/static/data/io_savemat.py: -------------------------------------------------------------------------------- 1 | import scipy.io 2 | import numpy as np 3 | 4 | #create two numpy arrays 5 | a = np.linspace(0, 50, 11) 6 | b = np.ones((4, 4)) 7 | 8 | #save as mat-file 9 | #create dictionary for savemat 10 | tmp_d = {'a': a, 11 | 'b': b} 12 | scipy.io.savemat('data.mat', tmp_d) 13 | -------------------------------------------------------------------------------- /book/todo.org: -------------------------------------------------------------------------------- 1 | - [ ] check that saving notebooks without output (15, 16, 17) works fine with binder 2 | - [ ] check binder once merged 3 | - [ ] move footnotes from end of text into the right places 4 | 5 | - [ ] html output on https://fangohr.github.io/introduction-to-python-for-computational-science-and-engineering 6 | - [ ] add links in the right places 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Introduction to Python for Computational Science and Engineering 2 | 3 | Hans Fangohr (2002-2021) 4 | 5 | This work is licensed under the Creative Commons 6 | Attribution-NonCommercial 4.0 International License. To view a copy of 7 | this license, visit https://creativecommons.org/licenses/by-nc/4.0/ or 8 | send a letter to Creative Commons, PO Box 1866, Mountain View, CA 9 | 94042, USA. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | OB.cache 2 | .ipynb_checkpoints 3 | __pycache__ 4 | html 5 | pdf 6 | data.mat 7 | hello.py 8 | hello.txt 9 | module1.py 10 | myfile.txt 11 | myplot.eps 12 | myplot.pdf 13 | myplot.png 14 | pylabimshow1a.pdf 15 | pylabimshow1b.pdf 16 | pylabimshowcm.pdf 17 | test.txt 18 | vectools.py 19 | .pytest_cache/ 20 | eu-pop-2017.csv 21 | stock.csv 22 | .venv 23 | .venv/ 24 | book/_build 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * TODO 2 | - [X] building natively on OSX works (make html, make pdf) 3 | - [X] Binder link is included 4 | - [X] nbval shows some failures 5 | - [X] building through docker fails for pdf 6 | - [ ] check github actions 7 | - [ ] make pdf available? 8 | - [ ] JupyterLite version 9 | - [X] check CI 10 | - [ ] run black over notebooks 11 | - [ ] run spell checker over notebooks 12 | - see https://stackoverflow.com/questions/39324039/highlight-typos-in-the-jupyter-notebook-markdown 13 | - [X] review content 14 | - [X] pandas 15 | 16 | -------------------------------------------------------------------------------- /binder/info.org: -------------------------------------------------------------------------------- 1 | * Overall strategy: separate Docker containers for development and mybinder 2 | 3 | This repository uses a Dockerfile in the root directory for development 4 | purposes. This includes translation of the source files (in book/*ipynb) to html 5 | and pdf (using jupyterbook), which in turns needs a full latex installation. 6 | 7 | That makes the container quite big. 8 | 9 | For myBinder, we do not need latex, so in this directory, we attempt to have a 10 | leaner definition of dependencies. 11 | 12 | Binder should look in this directory (as it is called 'binder') and ignore the 13 | global Dockerfile. 14 | 15 | -------------------------------------------------------------------------------- /book/_toc.yml: -------------------------------------------------------------------------------- 1 | format: jb-book 2 | root: opening 3 | options: 4 | numbered: true 5 | chapters: 6 | - file: 01-introduction 7 | - file: 02-powerful-calculator 8 | - file: 03-data-types-structures 9 | - file: 04-introspection 10 | - file: 05-input-output 11 | - file: 06-control-flow 12 | - file: 07-functions-modules 13 | - file: 08-functional-tools 14 | - file: 09-common-tasks 15 | - file: 10-matlab-to-python 16 | - file: 11-python-shells 17 | - file: 12-symbolic-computation 18 | - file: 13-numeric-computation 19 | - file: 14-numpy 20 | - file: 15-visualising-data 21 | - file: 16-scipy 22 | - file: 17-pandas 23 | - file: 18-environments 24 | - file: 19-next-steps 25 | - file: 99-changes 26 | -------------------------------------------------------------------------------- /.github/workflows/binder-nbval.yml: -------------------------------------------------------------------------------- 1 | name: NBVAL on binder container 2 | 3 | # We use `repo2docker` to build the docker image. Same as would happen on myBinder (or other Binder instances) 4 | 5 | on: 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | repo2docker-build-and-test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Build Docker Container with repo2docker 17 | uses: jupyterhub/repo2docker-action@master 18 | with: 19 | NO_PUSH: 'true' 20 | IMAGE_NAME: "python4compscience" 21 | 22 | - name: List Available Images 23 | run: docker images 24 | 25 | - name: Test with nbval 26 | run: make docker-binder-nbval 27 | 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | 3 | #RUN apt-get update -y && apt-get install -y texlive-xetex latexmk texlive-xetex \ 4 | # texlive-fonts-extra fonts-freefont-otf python3 python3-pip git zile wget 5 | 6 | RUN apt-get update -y && apt-get install -y python3 python3-pip texlive-xetex \ 7 | latexmk git zile wget texlive-fonts-extra python3-venv 8 | 9 | COPY requirements.txt /opt/ 10 | 11 | WORKDIR /opt/ 12 | RUN python3 -m venv venv 13 | RUN . venv/bin/activate && pip install -r requirements.txt 14 | RUN . venv/bin/activate && pip list 15 | # activate venv 16 | ENV PATH="/opt/venv/bin:$PATH" 17 | 18 | RUN mkdir -p /io 19 | WORKDIR /io 20 | 21 | # Need this for one nbval and chapter 1 22 | RUN ln -s /usr/bin/python3 /usr/local/bin/python 23 | 24 | # supress warning when running jupyter-book in container 25 | ENV PYDEVD_DISABLE_FILE_VALIDATION=1 26 | CMD ["/bin/bash"] 27 | -------------------------------------------------------------------------------- /book/static/nbval_sanitize.cfg: -------------------------------------------------------------------------------- 1 | [Memory addresses] 2 | regex: 0x[0-9a-fA-F]+ 3 | replace: MEMORY-ADDRESS 4 | 5 | [file-magic-new-file] 6 | regex: Overwriting 7 | replace: Writing 8 | 9 | [timeit-output] 10 | regex: [\s\S]* per loop \(mean [\s\S]* 11 | replace: TIMEIT-OUTPUT 12 | 13 | [dictionary-keys-values-items] 14 | regex: dict_[\s\S]*\(\[[\s\S]*\]\) 15 | replace: DICTIONARY-DATA 16 | 17 | # The following rules are helpful if we attempt to include the svg+xml output from matplotlib in the notebooks. 18 | # 19 | #[matplotlib-path-id] 20 | #regex: id="[0-9a-zA-Z]+" 21 | #replace: PATH-ID 22 | # 23 | #[matplotlib-clip-path-url] 24 | #regex: clip-path="url\(#[0-9a-zA-Z]+\)" 25 | #replace: CLIPPATH-URL 26 | # 27 | #[matplotlib-xlink-href] 28 | #regex: xlink:href="#[0-9a-zA-Z]+" 29 | #replace: XLINK-HREF 30 | # 31 | #[date-stamp] 32 | #regex: \d{2,4}-\d{1,2}-\d{2,4} 33 | #replace: DATE-STAMP 34 | # 35 | #[time-stamp] 36 | #regex: \d{2}:\d{2}:\d{2}.\d{6} 37 | #replace: TIME-STAMP -------------------------------------------------------------------------------- /book/static/data/io_savemat.py.octave: -------------------------------------------------------------------------------- 1 | 2 | HAL47:code fangohr$ octave 3 | GNU Octave, version 3.2.4 4 | Copyright (C) 2009 John W. Eaton and others. 5 | 6 | 7 | octave:1> whos 8 | Variables in the current scope: 9 | 10 | Attr Name Size Bytes Class 11 | ==== ==== ==== ===== ===== 12 | ans 1x11 92 cell 13 | 14 | Total is 11 elements using 92 bytes 15 | 16 | octave:2> load data.mat 17 | octave:3> whos 18 | Variables in the current scope: 19 | 20 | Attr Name Size Bytes Class 21 | ==== ==== ==== ===== ===== 22 | a 11x1 88 double 23 | ans 1x11 92 cell 24 | b 4x4 128 double 25 | 26 | Total is 38 elements using 308 bytes 27 | 28 | octave:4> a 29 | a = 30 | 31 | 0 32 | 5 33 | 10 34 | 15 35 | 20 36 | 25 37 | 30 38 | 35 39 | 40 40 | 45 41 | 50 42 | 43 | octave:5> b 44 | b = 45 | 46 | 1 1 1 1 47 | 1 1 1 1 48 | 1 1 1 1 49 | 1 1 1 1 50 | 51 | -------------------------------------------------------------------------------- /book/99-changes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "american-settle", 6 | "metadata": {}, 7 | "source": [ 8 | "# Change history\n", 9 | "\n", 10 | "Since 2022" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "naughty-affairs", 16 | "metadata": {}, 17 | "source": [ 18 | "- 3 Jan 2022: review [visualisation chapter](15-visualising-data.ipynb)\n", 19 | "- 4 Jan 2022: change from `odeint` to `solve_ivp` in [scipy chapter](16-scipy.ipynb)\n", 20 | "- 5 Jan 2022: adding new section [virtual environments and pip](18-environments.ipynb)" 21 | ] 22 | } 23 | ], 24 | "metadata": { 25 | "kernelspec": { 26 | "display_name": "Python 3 (ipykernel)", 27 | "language": "python", 28 | "name": "python3" 29 | }, 30 | "language_info": { 31 | "codemirror_mode": { 32 | "name": "ipython", 33 | "version": 3 34 | }, 35 | "file_extension": ".py", 36 | "mimetype": "text/x-python", 37 | "name": "python", 38 | "nbconvert_exporter": "python", 39 | "pygments_lexer": "ipython3", 40 | "version": "3.9.7" 41 | } 42 | }, 43 | "nbformat": 4, 44 | "nbformat_minor": 5 45 | } 46 | -------------------------------------------------------------------------------- /book/static/odeintexercisesolution.py: -------------------------------------------------------------------------------- 1 | from scipy.integrate import odeint 2 | import math 3 | 4 | 5 | def rhs(y, t): 6 | return math.exp(-t) * (-math.sin(10 * t) * 10 - math.cos(10 * t)) 7 | 8 | 9 | def test(): 10 | import numpy as N 11 | 12 | a = 0 13 | b = 10 14 | y0 = 1.0 15 | 16 | import pylab 17 | 18 | #correct solution 19 | t = N.arange(a, b, 0.01) 20 | #y = N.exp(-t) * N.cos(10 * t) 21 | #pylab.plot(t, y, label='correct solution') 22 | 23 | #approximate solution odeint 24 | yscipy = odeint(rhs, y0, t) 25 | 26 | pylab.figure(figsize=(8, 3.5)) 27 | pylab.plot(t, yscipy, label="odeint's approximation") 28 | 29 | #make student plot 30 | pylab.axis([8, 10, -4e-4, 4e-4]) 31 | pylab.xlabel('t') 32 | pylab.ylabel('y(t)') 33 | pylab.legend() 34 | 35 | pylab.savefig('solution8to10.png') 36 | pylab.savefig('odeintsolution8to10.png') 37 | pylab.savefig('odeintsolution8to10.pdf') 38 | pylab.axis([0, 10, -1, 1]) 39 | 40 | #pylab.show() 41 | 42 | 43 | if __name__ == "__main__": 44 | """This will only be executed if the file is run 'on its own'. 45 | 46 | If we import a method from this file, then this section will 47 | not be reached.""" 48 | 49 | test() 50 | -------------------------------------------------------------------------------- /.github/workflows/nbval.yml: -------------------------------------------------------------------------------- 1 | name: nbval 2 | 3 | on: 4 | push: 5 | # all 6 | workflow_dispatch: 7 | 8 | jobs: 9 | docker-build-and-nbval: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Set up Docker Buildx 15 | uses: docker/setup-buildx-action@v3 16 | 17 | - name: Cache Docker layers 18 | uses: actions/cache@v4 19 | with: 20 | path: /tmp/.buildx-cache 21 | key: ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 22 | restore-keys: | 23 | ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 24 | 25 | # This build and push step does the same thing as `make docker-build`, but 26 | # makes it easier to store and load caches, which is why we use it instead 27 | - name: Build and Save Layers Locally 28 | uses: docker/build-push-action@v5 29 | with: 30 | context: ./ 31 | file: ./Dockerfile 32 | load: true 33 | cache-from: type=local,src=/tmp/.buildx-cache 34 | cache-to: type=local,dest=/tmp/.buildx-cache 35 | tags: python4compscience 36 | 37 | - name: List Available Images 38 | run: docker images 39 | 40 | - name: Run NBVAL over all chapters 41 | run: make docker-nbval 42 | -------------------------------------------------------------------------------- /book/info.org: -------------------------------------------------------------------------------- 1 | * <2022-01-05 Wed 07:42> Notes on nbval: 2 | 3 | ** Chapters with matplotlib output 4 | - for chapter 15 (matplotlib) [and other with matplotlib], we run into fails if svg+xml output is saved 5 | - For some of the fails, I have created entries in [file:static/nbvas_sanitize.cfg] to ignore the changes (and commented out) 6 | - Update on closer inspection: in the past, we didn't save the output, and thus the problem never occurred. 7 | - But what does nbval do if the cells are empty, and it computes output? 8 | - if we do not save output, then nbval will not complain about the empty cells 9 | - if an exception occurs, it will report a fail. 10 | - So --nbval on empty output cells behaves like --nbval-lax on saved output 11 | - So not saving the output might be the right way, as jupyter-book executes the 12 | notebooks anyway when computing pdf and html 13 | (And that's what we did before my recent changes, I just had to remind myself ...) 14 | 15 | ** Extra treatment of Chapter 18 Environments 16 | - should not execute automatically (because the description of creating 17 | virtual envs is tailored to running it on a mac with Anaconda). 18 | - achieved via instruction in [file:_config.yml] 19 | - should be skipped by nbval for same reasons (use --ignore switch for that) 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | make html 3 | make pdf 4 | 5 | install: 6 | pip install -r requirements.txt 7 | 8 | clean: 9 | cd book; rm -rf \ 10 | *.aux *.out *.log pylabimshow* myplot* myfile* \ 11 | combined_files hello.txt hello.py data.mat test.txt vectools.py \ 12 | module1.py pylabhistogram.pdf eu-pop-2017.csv \ 13 | _build .ipynb_checkpoints 14 | 15 | html: 16 | jupyter-book build book --builder html 17 | 18 | linkcheck: 19 | jupyter-book build book --builder linkcheck 20 | 21 | pdf: book/*-*.ipynb 22 | jupyter-book build book --builder pdflatex 23 | 24 | nbval: 25 | @echo "Testing all chapters (apart from 18) with --nbval" 26 | pytest -v --nbval book --sanitize-with book/static/nbval_sanitize.cfg \ 27 | --ignore=book/18-environments.ipynb --ignore=book/_build 28 | @echo "Testing chapter 18 with --nbval-lax" 29 | pytest -v --nbval-lax book/18-environments.ipynb 30 | 31 | 32 | nbval-native: 33 | @echo "Testing all chapters (apart from 18) with --nbval" 34 | pytest -v --nbval book --sanitize-with book/static/nbval_sanitize.cfg \ 35 | --ignore=book/18-environments.ipynb --ignore=book/_build 36 | @echo "Testing chapter 18 with --nbval-lax" 37 | pytest -v --nbval-lax book/18-environments.ipynb 38 | 39 | 40 | docker-all: 41 | make docker-build 42 | make docker-html 43 | make docker-linkcheck 44 | make docker-pdf 45 | make docker-nbval 46 | 47 | docker-build: 48 | docker build -t python4compscience . 49 | 50 | # Occasionally (after changing branch) we need to force rebuild: 51 | docker-build-nocache: 52 | docker build -t python4compscience2 --no-cache . 53 | 54 | define DOCKER_RUN 55 | docker run --rm -v $(CURDIR):/io python4compscience 56 | endef 57 | 58 | docker-bash: 59 | docker run --workdir=/io -t -i -v $(CURDIR):/io python4compscience bash 60 | 61 | docker-html: 62 | $(DOCKER_RUN) make html 63 | 64 | docker-linkcheck: 65 | $(DOCKER_RUN) make linkcheck 66 | 67 | docker-pdf: 68 | $(DOCKER_RUN) make pdf 69 | 70 | docker-nbval: 71 | $(DOCKER_RUN) make nbval 72 | 73 | docker-clean: 74 | $(DOCKER_RUN) make clean 75 | 76 | docker-binder-nbval: 77 | $(DOCKER_RUN) make nbval-native 78 | 79 | # to update the title page: 80 | # - screenshot first page of pdf 81 | # - (edit out date if desired) 82 | # - add border around bitmap, for example using 83 | # "convert titlepage.png -shave 1x1 -bordercolor black -border 1 titlepage-with-border.png" 84 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Create and deploy html and pdf on gh-pages 2 | 3 | on: 4 | release: 5 | types: [created] 6 | push: 7 | # all 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker-build-and-deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v3 18 | 19 | - name: Cache Docker layers 20 | uses: actions/cache@v4 21 | with: 22 | path: /tmp/.buildx-cache 23 | key: ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 24 | restore-keys: | 25 | ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 26 | 27 | # This build and push step does the same thing as `make docker-build`, but 28 | # makes it easier to store and load caches, which is why we use it instead 29 | - name: Build and Save Layers Locally 30 | uses: docker/build-push-action@v5 31 | with: 32 | context: ./ 33 | file: ./Dockerfile 34 | load: true 35 | cache-from: type=local,src=/tmp/.buildx-cache 36 | cache-to: type=local,dest=/tmp/.buildx-cache 37 | tags: python4compscience 38 | 39 | - name: List Available Images 40 | run: docker images 41 | 42 | - name: Make Docker HTML (JBook---create html) 43 | run: make docker-html 44 | 45 | - name: Check links - Markdown 46 | uses: gaurav-nelson/github-action-markdown-link-check@v1 47 | 48 | - name: Make Docker Linkcheck 49 | run: make docker-linkcheck 50 | 51 | - name: Make Docker PDF (JBook---create pdf) 52 | run: | 53 | make docker-pdf 54 | sudo cp -v book/_build/latex/book.pdf book/_build/html/book.pdf 55 | 56 | - name: Gather outputs in pages 57 | run: | 58 | mkdir -p pages 59 | cp -av book/_build/html/* pages 60 | cp -av book/_build/latex/book.pdf pages 61 | 62 | # Deploy output to gh-pages branch 63 | - name: GitHub Pages action 64 | uses: peaceiris/actions-gh-pages@v3 65 | with: 66 | github_token: ${{ secrets.GITHUB_TOKEN }} 67 | publish_dir: ./pages 68 | 69 | - uses: marvinpinto/action-automatic-releases@latest 70 | with: 71 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 72 | automatic_release_tag: "latest" 73 | title: "Development Build" 74 | files: book/_build/latex/book.pdf 75 | -------------------------------------------------------------------------------- /.github/workflows/stage.yml: -------------------------------------------------------------------------------- 1 | name: Book Staging 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | docker-build-and-stage: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Set up Docker Buildx 15 | uses: docker/setup-buildx-action@v3 16 | 17 | - name: Cache Docker layers 18 | uses: actions/cache@v4 19 | with: 20 | path: /tmp/.buildx-cache 21 | key: ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 22 | restore-keys: | 23 | ${{ runner.os }}-buildx-${{ hashFiles('poetry.lock') }} 24 | 25 | # This build and push step does the same thing as `make docker-build`, but 26 | # makes it easier to store and load caches, which is why we use it instead 27 | - name: Build and Save Layers Locally 28 | uses: docker/build-push-action@v5 29 | with: 30 | context: ./ 31 | file: ./Dockerfile 32 | load: true 33 | cache-from: type=local,src=/tmp/.buildx-cache 34 | cache-to: type=local,dest=/tmp/.buildx-cache 35 | tags: python4compscience 36 | 37 | - name: List Available Images 38 | run: docker images 39 | 40 | - name: Make Docker HTML 41 | run: make docker-html 42 | 43 | - name: Check links - Markdown 44 | uses: gaurav-nelson/github-action-markdown-link-check@v1 45 | 46 | - name: Make Docker Linkcheck 47 | run: make docker-linkcheck 48 | 49 | - name: Make Docker PDF 50 | run: | 51 | make docker-pdf 52 | sudo cp book/_build/latex/book.pdf book/_build/html/book.pdf 53 | 54 | # - name: Publish 55 | # uses: netlify/actions/build@master 56 | # env: 57 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | # NETLIFY_DIR: ./book/_build/html 59 | # 60 | # - name: Publish Staging Site 61 | # id: netlify-deploy 62 | # uses: netlify/actions/cli@6c34c3fcafc69ac2e1d6dbf226560329c6dfc51b 63 | # with: 64 | # args: deploy --dir=./book/_build/html 65 | # env: 66 | # NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY }} 67 | # 68 | # - uses: mshick/add-pr-comment@07f690343c25a94e24a8acb70d03c86b701ae322 69 | # with: 70 | # message: ${{ steps.netlify-deploy.outputs.NETLIFY_OUTPUT }} 71 | # repo-token: ${{ secrets.GITHUB_TOKEN }} 72 | # repo-token-user-login: 'github-actions[bot]' 73 | # allow-repeats: true 74 | -------------------------------------------------------------------------------- /book/_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: Introduction to Python for Computational Science and Engineering 5 | author: Hans Fangohr 6 | logo: logo.png 7 | 8 | # Force re-execution of notebooks on each build. 9 | # See https://jupyterbook.org/content/execute.html 10 | execute: 11 | execute_notebooks: auto 12 | exclude_patterns: [18-environments.ipynb] 13 | 14 | # Define the name of the latex output file for PDF builds 15 | latex: 16 | latex_engine: xelatex 17 | latex_documents: 18 | targetname: book.tex 19 | 20 | # Information about where the book exists on the web 21 | repository: 22 | url: https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering # Online location of your book 23 | path_to_book: book # Optional path to your book, relative to the repository root 24 | branch: master # Which branch of the repository should be used when creating links (optional) 25 | 26 | # Add GitHub buttons to your book 27 | # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository 28 | html: 29 | use_issues_button: true 30 | use_repository_button: true 31 | comments: 32 | utterances: 33 | repo: "fangohr/introduction-to-python-for-computational-science-and-engineering" 34 | issue-term: "pathname" 35 | label: "💬 utterances" 36 | theme: "github-light" 37 | crossorigin: "anonymous" 38 | 39 | 40 | launch_buttons: 41 | binderhub_url: "https://mybinder.org" # The URL for your BinderHub (e.g., https://mybinder.org) 42 | 43 | # Allow parsing of "3d magnetisation vector field, created with MayaVi2 38 | 39 | -------------------------------------------------------------------------------- /book/10-matlab-to-python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# From Matlab to Python\n", 8 | "\n", 9 | "## Important commands\n", 10 | "\n", 11 | "### The for-loop\n", 12 | "\n", 13 | "Matlab:\n", 14 | "\n", 15 | "```octave\n", 16 | "for i = 1:10\n", 17 | " disp(i)\n", 18 | "end\n", 19 | "```" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "Matlab requires the `end` key-word at the end of the block belonging to the for-loop.\n", 27 | "\n", 28 | "Python:" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 1, 34 | "metadata": { 35 | "collapsed": false, 36 | "scrolled": true 37 | }, 38 | "outputs": [ 39 | { 40 | "name": "stdout", 41 | "output_type": "stream", 42 | "text": [ 43 | "1\n", 44 | "2\n", 45 | "3\n", 46 | "4\n", 47 | "5\n", 48 | "6\n", 49 | "7\n", 50 | "8\n", 51 | "9\n", 52 | "10\n" 53 | ] 54 | } 55 | ], 56 | "source": [ 57 | "for i in range(1,11):\n", 58 | " print(i)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "Python requires a colon (“:”) at the of the `for`-line. (This is important and often forgotten when you have programmed in Matlab before.) Python requires the commands to be executed within the for-loop to be indented.\n", 66 | "\n", 67 | "### The if-then statement\n", 68 | "\n", 69 | "Matlab:\n", 70 | "\n", 71 | "```octave\n", 72 | "if a==0\n", 73 | " disp('a is zero')\n", 74 | "elseif a<0\n", 75 | " disp('a is negative')\n", 76 | "elseif a==42\n", 77 | " disp('a is 42')\n", 78 | "else\n", 79 | " disp('a is positive')\n", 80 | "end\n", 81 | "```" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Matlab requires the `end` key-word at the very end of the block belonging to the for-loop.\n", 89 | "\n", 90 | "Python:" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 2, 96 | "metadata": { 97 | "collapsed": false 98 | }, 99 | "outputs": [ 100 | { 101 | "name": "stdout", 102 | "output_type": "stream", 103 | "text": [ 104 | "a is negative\n" 105 | ] 106 | } 107 | ], 108 | "source": [ 109 | "a = -5\n", 110 | "\n", 111 | "if a==0:\n", 112 | " print('a is zero')\n", 113 | "elif a<0:\n", 114 | " print('a is negative')\n", 115 | "elif a==42:\n", 116 | " print('a is 42')\n", 117 | "else:\n", 118 | " print('a is positive')" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "Python requires a colon (“:”) after every condition (i.e. at the of the lines starting with `if`, `elif`, `else`. Python requires the commands to be executed within each part of the if-then-else statement to be indented.\n", 126 | "\n", 127 | "### Indexing\n", 128 | "\n", 129 | "Matlab’s indexing of matrices and vectors starts a 1 (similar to Fortran), whereas Python’s indexing starts at 0 (similar to C).\n", 130 | "\n", 131 | "### Matrices\n", 132 | "\n", 133 | "In Matlab, every object is a matrix. In Python, there is a specialised extension library called `numpy` (see Sec. \\[cha:numer-pyth-numpy\\]) which provides the `array` object which in turns provides the corresponding functionality. Similar to Matlab, the `numpy` object is actually based on binary libraries and execution there very fast.\n", 134 | "\n", 135 | "There is a dedicated introduction to numpy for Matlab users available at ." 136 | ] 137 | } 138 | ], 139 | "metadata": { 140 | "kernelspec": { 141 | "display_name": "Python 3", 142 | "language": "python", 143 | "name": "python3" 144 | }, 145 | "language_info": { 146 | "codemirror_mode": { 147 | "name": "ipython", 148 | "version": 3 149 | }, 150 | "file_extension": ".py", 151 | "mimetype": "text/x-python", 152 | "name": "python", 153 | "nbconvert_exporter": "python", 154 | "pygments_lexer": "ipython3", 155 | "version": "3.8.6-final" 156 | } 157 | }, 158 | "nbformat": 4, 159 | "nbformat_minor": 1 160 | } -------------------------------------------------------------------------------- /book/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "*Python for Computational Science and Engineering*\n", 8 | "\n", 9 | "The content of [this book](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/blob/master/Readme.md) is distributed into chapters, using one Jupyter Notebook for each chapter.\n", 10 | "\n", 11 | "You can read the book in different formats: [html](https://fangohr.github.io/introduction-to-python-for-computational-science-and-engineering/), [pdf](https://fangohr.github.io/introduction-to-python-for-computational-science-and-engineering/book.pdf), or you can use the [myBinder](https://mybinder.org/v2/gh/fangohr/introduction-to-python-for-computational-science-and-engineering/master?urlpath=tree/book/index.ipynb)\n", 12 | " environment, in which you can read the text and execute the examples in a browser (without having to install Python locally) using one Jupyter notebook per chapter, as mentioned above.\n", 13 | "\n", 14 | "If you have not used the Jupyter Notebook before, please read the section \"First steps with Jupyter Notebook\" below before you proceed.\n", 15 | "\n", 16 | "If you are reading this online using the interactive [myBinder](https://mybinder.org/v2/gh/fangohr/introduction-to-python-for-computational-science-and-engineering/master?urlpath=tree/book/index.ipynb) environment, then you can use the menu `File` -> `Open ...` to open the different chapters as an executable notebook (in the classic Notebook), or by selecting the chapter in the panel on the left (in JupyterLab). In either case, you can execute, change, and re-execute all examples as you like. I believe this interactive exploration can be of great value as part of the learning process.\n", 17 | "\n", 18 | "*First steps with Jupyter Notebook*\n", 19 | "\n", 20 | "1. Navigating the notebook:\n", 21 | " - When you open a notebook, you will find that you can move a highlighted block (with a blue line at the left) with the cursor keys to move up and down. This block highlights a *cell*. (You can also use the mouse to select a cell.) This is called the [Command mode](https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Notebook%20Basics.html#Command-mode).\n", 22 | "\n", 23 | "2. Executing code:\n", 24 | " - If you want to execute a cell (for example one that contains some Python code), you can press `Shift+ENTER`. If the cell creates some output, it will be displayed below the cell. (You may not notice if it just updates output that was displayed before, in particular if the new output is the same as the old output.)\n", 25 | "\n", 26 | "3. Editing code:\n", 27 | " - If you want to *change* the code in the currently highlighted cell, you need to press `ENTER`. You have now entered the [Editing mode](https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Notebook%20Basics.html#Edit-mode), and the content of the cell can be edited. If you have completed your changes, and you want to execute them, use the `Shift+ENTER` short cut.\n", 28 | "\n", 29 | "Note that you can also edit blocks of text (or go into edit mode for a text cell unintentionally). Just press `Shift+ENTER` to render the text again, and go back into command mode.\n", 30 | "\n", 31 | "*Warning: Changes on mybinder are temporary*\n", 32 | "\n", 33 | "If you use this text book interactively on the mybinder service, then you have been given a temporary resource in the cloud to execute the code examples. The changes you have made to the notebook *will be lost* when your session ends (which is when you close the window, or the patience of the service has expired). Thus, the interactive exploration of the notebooks is good to help learn Python, computing and data science, but you should not attempt to write any code in these notebooks that you want to re-use the next day or later.\n", 34 | "\n", 35 | "*Comments? Questions?*\n", 36 | "\n", 37 | "For feedback, corrections, and questions please refer to the [home page](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/blob/master/Readme.md) of the book.\n", 38 | "\n", 39 | "Enjoy!" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "![Logo, created with MayaVi](static/images/mayavi/mayavi-samp.png \"3d magnetisation vector field\")\n" 47 | ] 48 | } 49 | ], 50 | "metadata": { 51 | "anaconda-cloud": {}, 52 | "kernelspec": { 53 | "display_name": "Python 3", 54 | "language": "python", 55 | "name": "python3" 56 | }, 57 | "language_info": { 58 | "codemirror_mode": { 59 | "name": "ipython", 60 | "version": 3 61 | }, 62 | "file_extension": ".py", 63 | "mimetype": "text/x-python", 64 | "name": "python", 65 | "nbconvert_exporter": "python", 66 | "pygments_lexer": "ipython3", 67 | "version": "3.9.1" 68 | } 69 | }, 70 | "nbformat": 4, 71 | "nbformat_minor": 2 72 | } 73 | -------------------------------------------------------------------------------- /book/19-next-steps.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Where to go from here?\n", 8 | "\n", 9 | "Learning a programming language is the first step towards becoming a computationalist who advances science and engineering through computational modelling and simulation.\n", 10 | "\n", 11 | "We list some additional skills that can be very beneficial for day-to-day computational science work, but is of course not exhaustive.\n", 12 | "\n", 13 | "## Advanced programming\n", 14 | "\n", 15 | "This text has put emphasis on providing a robust foundation in terms of programming, covering control flow, data structures and elements from function and procedural programming. We have not touch Object Orientation in great detail, nor have we discussed some of Python’s more advanced features such as iterators, and decorators, type hinting, nor many of the fantastic (standard) libraries available.\n", 16 | "\n", 17 | "## Compiled programming language\n", 18 | "\n", 19 | "When performance starts to be the highest priority, we may need to use compiled code, and likely embed this in a Python code to carry out the computational that are the performance bottle neck.\n", 20 | "\n", 21 | "Fortran, C and C++ are sensible choices here; maybe Rust in the near future.\n", 22 | "\n", 23 | "We may also need to learn how to integrate the compiled code with Python using tools such as Cython, Boost, Ctypes and Swig.\n", 24 | "\n", 25 | "With the rise of GPUs as cheap and powerful compute resources, it is likely we want to drive computation carried out on the GPU. This can be done through GPU-specific libraries and languages (CUDA and OpenCL, for example). For some use cases, it may be sufficient to use frameworks that translate computational work from a higher level language (ideally as high as Python) to the GPUs.\n", 26 | "\n", 27 | "## Testing\n", 28 | "\n", 29 | "Good software development is supported by a range of unit and system tests that can be run routinely to check that the code works correctly. Tools such as pytest, doctest and others are invaluable, and we should at least learn at least how to use pytest for automated tests.\n", 30 | "\n", 31 | "## Simulation models\n", 32 | "\n", 33 | "A number of standard simulation tools such as Monte Carlo, Molecular Dynamics, lattice based models, agents, finite difference and finite element models are commonly used to solve particular simulation challenges – it is useful to have at least a broad overview of these.\n", 34 | "\n", 35 | "## Software engineering for research codes\n", 36 | "\n", 37 | "Research codes bring particular challenges: the requirements may change during the run time of the project, we need great flexibility yet reproducibility. A number of techniques are available to support effectively, including version control (see below), automatic tests and continous integration.\n", 38 | "\n", 39 | "## Data and visualisation\n", 40 | "\n", 41 | "Dealing with large amounts of data, processing and visualising it can be a challenge. Fundamental knowledge of database design, 3d visualisation and modern data processing tools such as the Pandas and xarray Python package help with this. For interactive 3d visualisation VTK remains an important tool, although WebGL starts to be an interesting alternative.\n", 42 | "\n", 43 | "## Version control\n", 44 | "\n", 45 | "Using a version control tool, such as git, should be a standard approach and improves code writing effectiveness significantly, helps with working in teams, and - maybe most importantly - supports reproducibility of computational results.\n", 46 | "\n", 47 | "## Parallel execution\n", 48 | "\n", 49 | "Parallel execution of code is a way to make it run orders of magnitude faster. This could be using MPI for inter-node communication or OpenMP for intra-node parallelisation or a hybrid mode bringing both together.\n", 50 | "\n", 51 | "The recent rise of GPU computing provides yet another avenue of parallelisation. \n", 52 | "\n", 53 | "## Acknowledgements\n", 54 | "\n", 55 | "Big thanks go to\n", 56 | "\n", 57 | "- Marc Molinari for carefully proof reading this manuscript around 2007.\n", 58 | "\n", 59 | "- Neil O’Brien for contributing to the SymPy section.\n", 60 | "\n", 61 | "- Jacek Generowicz for introducing me to Python in the last millennium, and for kindly sharing countless ideas from his excellent Python course.\n", 62 | "\n", 63 | "- EPSRC (GR/T09156/01 and EP/G03690X/1) and the European Union (OpenDreamKit Horizon 2020 European Research Infrastructures project, #676541) for support.\n", 64 | "\n", 65 | "- Students and other readers who have provided feedback and pointed out typos and errors etc.\n", 66 | "\n", 67 | "- Thomas Kluyver who helped to translate the Python 2 LaTeX based document into Python 3 Jupyter Notebooks and provided the machinery to create html and pdf versions. automatically (via his [bookbook package](https://github.com/takluyver/bookbook)).\n", 68 | "\n", 69 | "- Robert Rosca who helped to create html and pdf files after using [jupyterbook](https://jupyterbook.org) was released (2020).\n", 70 | "\n", 71 | "[1] the vertical line is to show the division between the original components only; mathematically, the augmented matrix behaves like any other 2 × 3 matrix, and we code it in SymPy as we would any other.\n", 72 | "\n", 73 | "[2] from the `help(preview)` documentation: “Currently this depends on pexpect, which is not available for windows.”\n", 74 | "\n", 75 | "[3] The exact value for the upper limit is availabe in `sys.maxint`.\n", 76 | "\n", 77 | "[4] We add for completeness, that a C-program (or C++ of Fortran) that executes the same loop will be about 100 times faster than the python float loop, and thus about 100\\*200 = 20000 faster than the symbolic loop.\n", 78 | "\n", 79 | "[5] In this text, we usually import `numpy` under the name `N` like this: `import numpy as N`. If you don’t have `numpy` on your machine, you can substitute this line by `import Numeric as N` or `import numarray as N`.\n", 80 | "\n", 81 | "[6] Historical note: this has changed from scipy version 0.7 to 0.8. Before 0.8, the return value was a float if a one-dimensional problem was to solve." 82 | ] 83 | } 84 | ], 85 | "metadata": { 86 | "anaconda-cloud": {}, 87 | "kernelspec": { 88 | "display_name": "Python 3 (ipykernel)", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "codemirror_mode": { 94 | "name": "ipython", 95 | "version": 3 96 | }, 97 | "file_extension": ".py", 98 | "mimetype": "text/x-python", 99 | "name": "python", 100 | "nbconvert_exporter": "python", 101 | "pygments_lexer": "ipython3", 102 | "version": "3.8.8" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 1 107 | } 108 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/fangohr/introduction-to-python-for-computational-science-and-engineering/master?urlpath=tree/book/index.ipynb) 2 | 3 | 4 | ![Book Deploy](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/workflows/Book%20Deploy/badge.svg) 5 | [![nbval in repo2docker container](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/actions/workflows/binder-nbval.yml/badge.svg)](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/actions/workflows/binder-nbval.yml) [![nbval](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/actions/workflows/nbval.yml/badge.svg)](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/actions/workflows/nbval.yml) 6 | 7 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1411868.svg)](https://doi.org/10.5281/zenodo.1411868) 8 | [![License: CC BY-NC 4.0](https://img.shields.io/badge/License-CC%20BY--NC%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc/4.0/) 9 | 10 | # Introduction to Python for Computational Science and Engineering 11 | 12 | 13 | 14 | 15 | 16 | An Introduction to Python for Computational Science and Engineering, developed 17 | by Hans Fangohr since 2003.(2003-2024). 18 | 19 | The content and methods taught are intended for a target audience of scientists 20 | and engineers who need to use computational methods and data processing in their 21 | work, but typically have no prior programming experience or formal computer 22 | science training. 23 | 24 | The book is available: 25 | 26 | - [As a web page (html)](https://fangohr.github.io/introduction-to-python-for-computational-science-and-engineering/) 27 | 28 | 29 | - [As a PDF](https://fangohr.github.io/introduction-to-python-for-computational-science-and-engineering/book.pdf) 30 | 31 | - Interactively [as Jupyter Notebooks on MyBinder](https://mybinder.org/v2/gh/fangohr/introduction-to-python-for-computational-science-and-engineering/master?urlpath=tree/book/index.ipynb) (or [use JupyterLab](https://mybinder.org/v2/gh/fangohr/introduction-to-python-for-computational-science-and-engineering/master?urlpath=lab/tree/book/index.ipynb) instead of the traditional Notebook.) 32 | 33 | The book is based on Python 3. 34 | 35 | (A Python 2.7 version is [available 36 | online](https://www.southampton.ac.uk/~fangohr/training/python/pdfs/Python2-for-Computational-Science-and-Engineering.pdf)) 37 | 38 | # Why use this book? (Feedback from user) 39 | 40 | > *Readers looking for a beginner's guide to Python are faced with a 41 | > bafflingarray of choices. However, Introduction to Python for Computational 42 | > Science and Engineering, by Hans Fangohr is uniquely valuable because it is 43 | > specifically aimed at those of us who are engaged in applied science or 44 | > scientific research. The book is concise, well organized and full of practical 45 | > examples that the reader can implement as they are going along. The key 46 | > concepts of programming are introduced in the first half of the book, while 47 | > the second half focuses on science/engineering applications: numerical 48 | > methods, optimization, scientific plotting, and data science. This book is a 49 | > must-have companion for anyone learning to use Python to enable their work in 50 | > applied science or scientific research." 51 | - Simon Box, Head of Virtual Development at Aurora Innovation.* 52 | 53 | 54 | # Translation 55 | 56 | The [book is available in 57 | Portuguese](https://github.com/gcpeixoto/lecture-ipynb/blob/master/README.md) 58 | ([pdf](https://github.com/gcpeixoto/lecture-ipynb/raw/master/pdf/Introducao-Python-para-Ciencias-Computacionais-Engenharia.pdf)). 59 | 60 | # Acknowledgments 61 | 62 | Thanks go to Robert Rosca, Thomas Kluyver, Neil O'Brien, Jacek Generowicz, and Mark Molinari 63 | for various contributions (see last chapter for details). Special thanks to all 64 | readers, users and students who have provided feedback and corrections. 65 | 66 | We acknowledge support from EPSRC (GR/T09156/01 and EP/G03690X/1) and from the 67 | OpenDreamKit Horizon 2020 European Research Infrastructures project (#676541). 68 | 69 | # Feedback? 70 | 71 | If you have used these materials and have some feedback (positive or negative), 72 | please get in touch with [Hans Fangohr](mailto:fangohr@soton.ac.uk). 73 | 74 | # Favour request and citation 75 | 76 | If you are using the book (be it as a teacher in your lecture course, as a 77 | student to support your learning, or in any other role), please send a short 78 | message to Hans (mailto:fangohr@soton.ac.uk) . Ideally, this would contain at which 79 | university/institution/company you are and how you use the book (in one 80 | sentence). This kind of data is useful to support further maintenance and 81 | extensions of the materials. 82 | 83 | Please use this citation: 84 | 85 | * Hans Fangohr, *Python for Computational Science and Engineering*, 2018, DOI: 86 | [10.5281/zenodo.1411868](https://doi.org/10.5281/zenodo.1411868), 87 | [github.com/fangohr/introduction-to-python-for-computational-science-and-engineering](https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering/blob/master/Readme.md) 88 | 89 | For BibTeX: 90 | ``` 91 | @misc{fangohr-python-book, 92 | doi = {10.5281/ZENODO.1411868}, 93 | url = {https://github.com/fangohr/introduction-to-python-for-computational-science-and-engineering}, 94 | author = {Fangohr, Hans}, 95 | keywords = {Python, Education, Textbook, Computational Science, Data Science, Jupyter}, 96 | language = {en}, 97 | title = {Introduction To {Python} For Computational Science And Engineering}, 98 | publisher = {Zenodo}, 99 | year = {2018} 100 | } 101 | ``` 102 | 103 | # License 104 | 105 | Creative Commons License
This work 108 | is licensed 110 | under a Creative Commons 112 | Attribution-NonCommercial 4.0 International License. 113 | 114 | The book can be downloaded, used and re-distributed for non-commercial purposes, 115 | i.e in particular for education purposes at universities, research institutes 116 | and schools. Please send a message to the author if you do so. 117 | 118 | 119 | # Author 120 | 121 | Hans Fangohr is a researcher and teacher (see 122 | [homepage](https://fangohr.github.io), [blog](https://fangohr.github.io/blog), 123 | [mastodon](https://fosstodon.org/@ProfCompMod)). His interests include effective 124 | software engineering for computational science and data science, researching 125 | computational modelling and data analysis methods, and education. He is a 126 | 127 | Professor at the [University of Southampton (UK)](https://www.southampton.ac.uk) 128 | 129 | and Head of the Scientific Support Unit for Computational Science at the [Max Planck Institute for Structure and Dynamics of Matter (Germany)](https://www.mpsd.mpg.de/research/ssus/comput-science). 130 | 131 | ---- 132 | 133 | Historical note: CI was done on [Circle 134 | CI](https://app.circleci.com/pipelines/github/fangohr/introduction-to-python-for-computational-science-and-engineering) 135 | until 23 August 2018, then switched to [Travis 136 | CI](https://travis-ci.org/fangohr/introduction-to-python-for-computational-science-and-engineering). A further switch in December 2020 to GitHub workflows. 137 | -------------------------------------------------------------------------------- /book/11-python-shells.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python shells\n", 8 | "\n", 9 | "## IDLE\n", 10 | "\n", 11 | "IDLE comes with every Python distribution and is a useful tool for everyday programming. Its editor provides syntax highlighting.\n", 12 | "\n", 13 | "There are two reasons why you might want to use another Python shell, for example:\n", 14 | "\n", 15 | "- While working with the Python prompt, you like auto-completion of variable names, filenames and commands. In that case *IPython* is your tool of choice (see below). IPython does not provide an editor, but you can carry on using the IDLE editor to edit files, or any other editor you like.\n", 16 | "\n", 17 | "IPython provides a number of nice features for the more experienced Python programmer, including convenient profiling of code (see ).\n", 18 | "\n", 19 | "Recently, some auto-completion has been added to Idle as well (press tab after having typed the first few letters of object names and keywords)." 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "## Python (command line)\n", 27 | "\n", 28 | "This is the most basic face of the Python shell. It is very similar to the Python prompt in IDLE but there are no menus to click on and no facilities to edit files." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "## Interactive Python (IPython)\n", 36 | "\n", 37 | "### IPython console\n", 38 | "\n", 39 | "IPython is an improved version of the Python command line. It is a valuable tool and worth exploring it’s capabilities (see )\n", 40 | "\n", 41 | "You will find the following features very useful:\n", 42 | "\n", 43 | "- auto completion\n", 44 | " Suppose you want to type `a = range(10)`. Instead of typing all the letters, just type `a = ra` and the press the “Tab” key. Ipython will now show all the possible commands (and variable names) that start with `ra`. If you type a third letter, here `n` and press “Tab” again, Ipython will auto complete and append `ge` automatically.\n", 45 | " This works also for variable names and modules.\n", 46 | "\n", 47 | "- To obtain help on a command, we can use Python’s help command. For example: `help(range)`. Ipython provides a shortcut. To achieve the same, it is sufficient to just type the command followed by a question mark: `range?`\n", 48 | "\n", 49 | "- You can relatively easily navigate directories on your computer. For example,\n", 50 | "\n", 51 | " - `!dir` lists the content of the current directory (same as `ls`)\n", 52 | "\n", 53 | " - `pwd` shows the current working directory\n", 54 | "\n", 55 | " - `cd` allows to change directories" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "- In general, using an exclamation mark before the command will pass the command to the shell (not to the Python interpreter).\n", 63 | "\n", 64 | "- You can execute Python programs from ipython using ``%run. Suppose you have a file `hello.py` in the current directory. You can then execute it by typing: ``%run hello\n", 65 | "\n", 66 | " Note that this differs from executing a python program in IDLE: IDLE restarts the Python interpreter session and thus deletes all existing objects before the execution starts. This is not the case with the `run` command in ipython (and neither when executing chunks of Python code from Emacs using the Emacs Python mode). In particular this can be very useful if one needs to setup a few objects which are needed to test the code one is working on. Using ipython’s `run` or Emacs instead of IDLE allows to keep these objects in the interpreter session and to only update the function/classes/... etc that are being developed.\n", 67 | "\n", 68 | "- allows multi-line editing of command history\n", 69 | "\n", 70 | "- provides on-the-fly syntax highlighting\n", 71 | "\n", 72 | "- displays doc-strings on-the-fly\n", 73 | "\n", 74 | "- can inline matplotlib figures (activate mode with if started with `%matplotlib inline`)\n", 75 | "\n", 76 | "- `%load` loads file from disk or form URL for editing\n", 77 | "\n", 78 | "- `%timeit` measures execution time for a given statement\n", 79 | "\n", 80 | "- …and a lot more.\n", 81 | "\n", 82 | "- Read more at \n", 83 | "\n", 84 | "If you have access to this shell, you may want to consider it as your default Python prompt." 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "### Jupyter Notebook\n", 92 | "\n", 93 | "The Jupyter Notebook (formerly IPython Notebook) allows you to execute, store, load, re-execute a sequence of Python commands, and to include explanatory text, images and other media in between.\n", 94 | "\n", 95 | "This is a recent and exciting development that has the potential to develop into a tool of great significance, for example for\n", 96 | "\n", 97 | "- documenting calculations and data processing\n", 98 | "\n", 99 | "- support learning and teaching of\n", 100 | "\n", 101 | " - Python itself\n", 102 | "\n", 103 | " - statistical methods\n", 104 | "\n", 105 | " - general data post-processing\n", 106 | "\n", 107 | " - …\n", 108 | "\n", 109 | "- documentation new code\n", 110 | "\n", 111 | "- automatic regression testing by re-running ipython notebook and comparing stored output with computed output\n", 112 | "\n", 113 | "**Further reading**\n", 114 | "\n", 115 | "- Jupyter Notebook ().\n", 116 | "\n", 117 | "- IPython ()." 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "## Spyder\n", 125 | "\n", 126 | "Spyder is the Scientific PYthon Development EnviRonment: a powerful interactive development environment for the Python language with advanced editing, interactive testing, debugging and introspection features and a numerical computing environment thanks to the support of IPython (enhanced interactive Python interpreter) and popular Python libraries such as NumPy (linear algebra), SciPy (signal and image processing) or matplotlib (interactive 2D/3D plotting). See for more.\n", 127 | "\n", 128 | "Some important features of Spyder:\n", 129 | "\n", 130 | "- Within Spyder, the IPython console is the default Python interpreter, and\n", 131 | "\n", 132 | "- code in the editor can be fully or partially be executed in this buffer.\n", 133 | "\n", 134 | "- The editor supports automatic checking for Python erros using pyflakes, and\n", 135 | "\n", 136 | "- the editor warns (if desired) if the code formatting deviates from the PEP8 style guide.\n", 137 | "\n", 138 | "- The Ipython Debugger can be activated, and\n", 139 | "\n", 140 | "- a profiler is provided.\n", 141 | "\n", 142 | "- An object explorer shows documentation for functions, methods etc on the fly and a\n", 143 | "\n", 144 | "- variable explorer displays names, size and values for numerical variables.\n", 145 | "\n", 146 | "Spyder is currently (as of 2014) on the way to develop into a powerful and robust multi-platform integrated environment for Python development, with particular emphasis on Python for scientific computing and engineering.\n", 147 | "\n", 148 | "## Editors\n", 149 | "\n", 150 | "All major editors that are used for programming, provide Python modes (such as Emacs, Vim, Sublime Text), some Integrated Development Enviroments (IDEs) come with their own editor (Spyder, Eclipse). Which of these is best, is partly a matter of choice.\n", 151 | "\n", 152 | "For beginners, Spyder seems a sensible choice as it provides an IDE, allows execution of chunks of code in an interpreter session and is easy to pick up." 153 | ] 154 | } 155 | ], 156 | "metadata": { 157 | "kernelspec": { 158 | "display_name": "Python 3", 159 | "language": "python", 160 | "name": "python3" 161 | }, 162 | "language_info": { 163 | "codemirror_mode": { 164 | "name": "ipython", 165 | "version": 3 166 | }, 167 | "file_extension": ".py", 168 | "mimetype": "text/x-python", 169 | "name": "python", 170 | "nbconvert_exporter": "python", 171 | "pygments_lexer": "ipython3", 172 | "version": "3.8.6-final" 173 | } 174 | }, 175 | "nbformat": 4, 176 | "nbformat_minor": 1 177 | } -------------------------------------------------------------------------------- /book/09-common-tasks.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Common tasks\n", 8 | "\n", 9 | "Here we provide a selection of small example programs addressing some common tasks and just providing some more Python code that can be read if seeking inspiration how to address a given problem.\n", 10 | "\n", 11 | "## Many ways to compute a series\n", 12 | "\n", 13 | "As an example, we compute the sum of odd numbers in different ways." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "this result is 12, expected to be 12\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "def compute_sum1(n):\n", 31 | " \"\"\"computes and returns the sum of 2,4,6, ..., m\n", 32 | " where m is the largest even number smaller than n.\n", 33 | "\n", 34 | " For example, with n = 7, we compute 0+2+4+6 = 12.\n", 35 | "\n", 36 | " This implementation uses a variable 'mysum' that is\n", 37 | " increased in every iteration of the for-loop.\"\"\"\n", 38 | "\n", 39 | " mysum = 0\n", 40 | " for i in range(0, n, 2):\n", 41 | " mysum = mysum + i\n", 42 | " return mysum\n", 43 | "\n", 44 | "\n", 45 | "def compute_sum2(n):\n", 46 | " \"\"\"computes and returns ...\n", 47 | "\n", 48 | " This implementation uses a while-loop:\n", 49 | " \"\"\"\n", 50 | "\n", 51 | " counter = 0\n", 52 | " mysum = 0\n", 53 | " while counter < n:\n", 54 | " mysum = mysum + counter\n", 55 | " counter = counter + 2\n", 56 | "\n", 57 | " return mysum\n", 58 | "\n", 59 | "\n", 60 | "def compute_sum3(n, startfrom=0):\n", 61 | " \"\"\"computes and returns ...\n", 62 | "\n", 63 | " This is a recursive implementation:\"\"\"\n", 64 | "\n", 65 | " if n <= startfrom:\n", 66 | " return 0\n", 67 | " else:\n", 68 | " return startfrom + compute_sum3(n, startfrom + 2)\n", 69 | "\n", 70 | "\n", 71 | "def compute_sum4a(n):\n", 72 | " \"\"\"A functional approach ... this seems to be\n", 73 | " the shortest and most concise code.\n", 74 | " \"\"\"\n", 75 | " return sum(range(0, n, 2))\n", 76 | "\n", 77 | "\n", 78 | "from functools import reduce\n", 79 | "def compute_sum4b(n):\n", 80 | " \"\"\"A functional approach ... not making use of 'sum' which\n", 81 | " happens to exist and is of course convenient here.\n", 82 | " \"\"\"\n", 83 | " return reduce(lambda a, b: a + b, range(0, n, 2))\n", 84 | "\n", 85 | "\n", 86 | "def compute_sum4c(n):\n", 87 | " \"\"\"A functional approach ... a bit faster than compute_sum4b\n", 88 | " as we avoid using lambda.\n", 89 | " \"\"\"\n", 90 | " import operator\n", 91 | " return reduce(operator.__add__, range(0, n, 2))\n", 92 | "\n", 93 | "\n", 94 | "def compute_sum4d(n):\n", 95 | " \"\"\"Using list comprehension.\"\"\"\n", 96 | " return sum([k for k in range(0, n, 2)])\n", 97 | "\n", 98 | "\n", 99 | "def compute_sum4e(n):\n", 100 | " \"\"\"Using another variation of list comprehension.\"\"\"\n", 101 | " return sum([k for k in range(0, n) if k % 2 == 0])\n", 102 | "\n", 103 | "\n", 104 | "def compute_sum5(n):\n", 105 | " \"\"\"Using numerical python (numpy). This is very fast\n", 106 | " (but would only pay off if n >> 10).\"\"\"\n", 107 | " import numpy\n", 108 | " return numpy.sum(2 * numpy.arange(0, (n + 1) // 2))\n", 109 | "\n", 110 | "\n", 111 | "def test_consistency():\n", 112 | " \"\"\"Check that all compute_sum?? functions in this file produce\n", 113 | " the same answer for all n>=2 and =2. Raise AssertionError if outputs disagree.\"\"\"\n", 118 | " funcs = [compute_sum1, compute_sum2, compute_sum3,\n", 119 | " compute_sum4a, compute_sum4b, compute_sum4c,\n", 120 | " compute_sum4d, compute_sum4e, compute_sum5]\n", 121 | " ans1 = compute_sum1(n)\n", 122 | " for f in funcs[1:]:\n", 123 | " assert ans1 == f(n), \"%s(n)=%d not the same as %s(n)=%d \" \\\n", 124 | " % (funcs[0], funcs[0](n), f, f(n))\n", 125 | "\n", 126 | " #main testing loop in test_consistency function\n", 127 | " for n in range(2, 1000):\n", 128 | " check_one_n(n)\n", 129 | "\n", 130 | "if __name__ == \"__main__\": \n", 131 | " m = 7\n", 132 | " correct_result = 12\n", 133 | " thisresult = compute_sum1(m)\n", 134 | " print(\"this result is {}, expected to be {}\".format(\n", 135 | " thisresult, correct_result))\n", 136 | " # compare with correct result\n", 137 | " assert thisresult == correct_result\n", 138 | " # also check all other methods\n", 139 | " assert compute_sum2(m) == correct_result\n", 140 | " assert compute_sum3(m) == correct_result\n", 141 | " assert compute_sum4a(m) == correct_result\n", 142 | " assert compute_sum4b(m) == correct_result\n", 143 | " assert compute_sum4c(m) == correct_result\n", 144 | " assert compute_sum4d(m) == correct_result\n", 145 | " assert compute_sum4e(m) == correct_result\n", 146 | " assert compute_sum5(m) == correct_result\n", 147 | "\n", 148 | " # a more systematic check for many values\n", 149 | " test_consistency()" 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "metadata": {}, 155 | "source": [ 156 | "All the different implementations shown above compute the same result. There are a number of things to be learned from this:\n", 157 | "\n", 158 | "- There are a large (probably an infinite) number of solutions for one given problem. (This means that writing programs is a task that requires creativity!)\n", 159 | "\n", 160 | "- These may achieve the same ’result’ (in this case computation of a number).\n", 161 | "\n", 162 | "- Different solutions may have different characteristics. They might:\n", 163 | "\n", 164 | " - be faster or slower\n", 165 | "\n", 166 | " - use less or more memory\n", 167 | "\n", 168 | " - are easier or more difficult to understand (when reading the source code)\n", 169 | "\n", 170 | " - can be considered more or less elegant." 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "## Sorting\n", 178 | "\n", 179 | "Suppose we need to sort a list of 2-tuples of user-ids and names, i.e." 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 2, 185 | "metadata": { 186 | "collapsed": true 187 | }, 188 | "outputs": [], 189 | "source": [ 190 | "mylist = [(\"fangohr\", \"Hans Fangohr\",),\n", 191 | " (\"admin\", \"The Administrator\"),\n", 192 | " (\"guest\", \"The Guest\")]" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "which we want to sort in increasing order of user-ids. If there are two or more identical user-ids, they should be ordered by the order of the names associated with these user-ids. This behaviour is just the default behaviour of sort (which goes back to how to sequences are compared)." 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 3, 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "name": "stdout", 209 | "output_type": "stream", 210 | "text": [ 211 | "[('admin', 'The Administrator'), ('fangohr', 'Hans Fangohr'), ('guest', 'The Guest')]\n" 212 | ] 213 | } 214 | ], 215 | "source": [ 216 | "stuff = mylist # collect your data\n", 217 | "stuff.sort() # sort the data in place\n", 218 | "print(stuff) # inspect the sorted data" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "Sequences are compared by initially comparing the first elements only. If they differ, then a decision is reached on the basis of those elements only. If the elements are equal, only then are the next elements in the sequence compared ... and so on, until a difference is found, or we run out of elements. For example:" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 4, 231 | "metadata": {}, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "text/plain": [ 236 | "True" 237 | ] 238 | }, 239 | "execution_count": 4, 240 | "metadata": {}, 241 | "output_type": "execute_result" 242 | } 243 | ], 244 | "source": [ 245 | "(2,0) > (1,0)" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 5, 251 | "metadata": {}, 252 | "outputs": [ 253 | { 254 | "data": { 255 | "text/plain": [ 256 | "True" 257 | ] 258 | }, 259 | "execution_count": 5, 260 | "metadata": {}, 261 | "output_type": "execute_result" 262 | } 263 | ], 264 | "source": [ 265 | "(2,1) > (1,3)" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 6, 271 | "metadata": {}, 272 | "outputs": [ 273 | { 274 | "data": { 275 | "text/plain": [ 276 | "False" 277 | ] 278 | }, 279 | "execution_count": 6, 280 | "metadata": {}, 281 | "output_type": "execute_result" 282 | } 283 | ], 284 | "source": [ 285 | "(2,1) > (2,1)" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 7, 291 | "metadata": {}, 292 | "outputs": [ 293 | { 294 | "data": { 295 | "text/plain": [ 296 | "True" 297 | ] 298 | }, 299 | "execution_count": 7, 300 | "metadata": {}, 301 | "output_type": "execute_result" 302 | } 303 | ], 304 | "source": [ 305 | "(2,2) > (2,1)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "It is also possible to do:" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 8, 318 | "metadata": { 319 | "collapsed": true 320 | }, 321 | "outputs": [], 322 | "source": [ 323 | "stuff = sorted(stuff)" 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "Where the list is not particularly large, it is generally advisable to use the `sorted` function (which *returns a sorted copy* of the list) over the `sort` method of a list (which changes the list into sorted order of elements, and returns None).\n", 331 | "\n", 332 | "However, what if the data we have is stored such that in each tuple in the list, the name comes first, followed by the id, i.e.:" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 9, 338 | "metadata": { 339 | "collapsed": true 340 | }, 341 | "outputs": [], 342 | "source": [ 343 | "mylist2 = [(\"Hans Fangohr\", \"fangohr\"),\n", 344 | " (\"The Administrator\", \"admin\"),\n", 345 | " (\"The Guest\", \"guest\")]" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": {}, 351 | "source": [ 352 | "We want to sort with the id as the primary key. The first approach to do this is to change the order of `mylist2` to that of `mylist`, and use `sort` as shown above.\n", 353 | "\n", 354 | "The second, neater approach relies on being able to decypher the cryptic help for the sorted function. `list.sort()` has the same options, but its help is less helpful." 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": 10, 360 | "metadata": {}, 361 | "outputs": [ 362 | { 363 | "name": "stdout", 364 | "output_type": "stream", 365 | "text": [ 366 | "Help on built-in function sorted in module builtins:\n", 367 | "\n", 368 | "sorted(iterable, key=None, reverse=False)\n", 369 | " Return a new list containing all items from the iterable in ascending order.\n", 370 | " \n", 371 | " A custom key function can be supplied to customize the sort order, and the\n", 372 | " reverse flag can be set to request the result in descending order.\n", 373 | "\n" 374 | ] 375 | } 376 | ], 377 | "source": [ 378 | "# NBVAL_IGNORE_OUTPUT\n", 379 | "help(sorted)" 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "metadata": {}, 385 | "source": [ 386 | "You should notice that `sorted` and `list.sort` have two keyword parameters. The first of these is called key. You can use this to supply a *key function*, which will be used to transform the items for sort to compare.\n", 387 | "\n", 388 | "Let’s illustrate this in the context of our exercise, by assuming that we have stored a list of pairs like this\n", 389 | "\n", 390 | " pair = name, id" 391 | ] 392 | }, 393 | { 394 | "cell_type": "markdown", 395 | "metadata": {}, 396 | "source": [ 397 | "(i.e. as in `mylist2`) and that we want to sort according to id and ignore name. We can achieve this by writing a function that retrieves only the second element of the pair it receives:" 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": 11, 403 | "metadata": { 404 | "collapsed": true 405 | }, 406 | "outputs": [], 407 | "source": [ 408 | "def my_key(pair):\n", 409 | " return pair[1]" 410 | ] 411 | }, 412 | { 413 | "cell_type": "code", 414 | "execution_count": 12, 415 | "metadata": {}, 416 | "outputs": [], 417 | "source": [ 418 | "mylist2.sort(key=my_key)" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "metadata": {}, 424 | "source": [ 425 | "This also works with an anonymous function:" 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 13, 431 | "metadata": { 432 | "collapsed": true 433 | }, 434 | "outputs": [], 435 | "source": [ 436 | "mylist2.sort(key=lambda p: p[1]) " 437 | ] 438 | }, 439 | { 440 | "cell_type": "markdown", 441 | "metadata": {}, 442 | "source": [ 443 | "### Efficiency\n", 444 | "\n", 445 | "The `key` function will be called exactly once for every element in the list. This is much more efficient than calling a function on every *comparison* (which was how you customised sorting in older versions of Python). But if you have a large list to sort, the overhead of calling a Python function (which is relatively large compared to the C function overhead) might be noticeable.\n", 446 | "\n", 447 | "If efficiency is really important (and you have proven that a significant proportion of time is spent in these functions) then you have the option of re-coding them in C (or another low-level language)." 448 | ] 449 | } 450 | ], 451 | "metadata": { 452 | "kernelspec": { 453 | "display_name": "Python 3", 454 | "language": "python", 455 | "name": "python3" 456 | }, 457 | "language_info": { 458 | "codemirror_mode": { 459 | "name": "ipython", 460 | "version": 3 461 | }, 462 | "file_extension": ".py", 463 | "mimetype": "text/x-python", 464 | "name": "python", 465 | "nbconvert_exporter": "python", 466 | "pygments_lexer": "ipython3", 467 | "version": "3.6.0" 468 | } 469 | }, 470 | "nbformat": 4, 471 | "nbformat_minor": 1 472 | } -------------------------------------------------------------------------------- /book/13-numeric-computation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Numerical Computation\n", 8 | "\n", 9 | "## Numbers and numbers\n", 10 | "\n", 11 | "We have already seen (03 Data Types Structures, Numbers) that Python knows different *types* of numbers:\n", 12 | "\n", 13 | "- `float`ing point numbers such as 3.14\n", 14 | "\n", 15 | "- `int`egers such as 42\n", 16 | "\n", 17 | "- `complex` numbers such as 3.14 + 1*j*\n", 18 | "\n", 19 | "### Limitations of number types\n", 20 | "\n", 21 | "#### Limitations of `int`s\n", 22 | "\n", 23 | "Mathematics provides the infinite set of natural numbers ℕ = {1, 2, 3, …}. Because the computer has *finite* size, it is impossible to represent all of these numbers in the computer. Instead, only a small subset of numbers is represented.\n", 24 | "\n", 25 | "The `int`-type can (usually[3]) represent numbers between -2147483648 and +2147483647 and corresponds to 4 bytes (that’s 4\\*8 bit, and 232 = 4294967296 which is the range from -2147483648 and +2147483647).\n", 26 | "\n", 27 | "You can imagine that the hardware uses a table like this to encode integers using bits (suppose for simplicity we use only 8 bits for this):\n", 28 | "\n", 29 | "| natural number| bit-representation|\n", 30 | "|---------------:|-------------------:|\n", 31 | "| 0| 00000000|\n", 32 | "| 1| 00000001|\n", 33 | "| 2| 00000010|\n", 34 | "| 3| 00000011|\n", 35 | "| 4| 00000100|\n", 36 | "| 5| 00000101|\n", 37 | "| ⋮| ⋮|\n", 38 | "| 254| 11111110|\n", 39 | "| 255| 11111111|\n", 40 | "\n", 41 | "Using 8 bit we can represent 256 natural numbers (for example from 0 to 255) because we have 28 = 256 different ways of combining eight 0s and 1s.\n", 42 | "\n", 43 | "We could also use a slightly different table to describe 256 integer numbers ranging, for example, from -127 to +128.\n", 44 | "\n", 45 | "This is *in principle* how integers are represented in the computer. Depending on the number of bytes used, only integer numbers between a minimum and a maximum value can be represented. On today’s hardware, it is common to use 4 or 8 bytes to represent one integer, which leads exactly to the minimum and maximum values of -2147483648 and +2147483647 as shown above for 4 bytes, and +9223372036854775807 as the maximum integer for 8 bytes (that’s ≈9.2 ⋅ 1018).\n", 46 | "\n", 47 | "#### Limitations of `float`s\n", 48 | "\n", 49 | "The floating point numbers in a computer are not the same as the mathematical floating point numbers. (This is exactly the same as the (mathematical) integer numbers not being the same as the integer numbers in a computer: only a *subset* of the infinite set of integer numbers can be represented by the `int` data type as shown in [Numbers and numbers](#Numbers-and-numbers)). So how are floating point numbers represented in the computer?\n", 50 | "\n", 51 | "- Any real number *x* can be written as\n", 52 | " *x* = *a* ⋅ 10*b*\n", 53 | " where *a* is the mantissa and *b* the exponent.\n", 54 | "\n", 55 | "- Examples:\n", 56 | "\n", 57 | "| x | a | b |\n", 58 | "|-----------------------------------|---------|----|\n", 59 | "| 123.45 = 1.23456 ⋅ 102 | 1.23456 | 2 |\n", 60 | "| 1000000 = 1.0 ⋅ 106 | 1.00000 | 6 |\n", 61 | "| 0.0000024 = 2.4 ⋅ 10-6 | 2.40000 | -6 |" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "- Therefore, we can use 2 integers to encode one floating point number!\n", 69 | "\n", 70 | " *x* = *a* ⋅ 10*b*" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "- Following (roughly) the IEEE-754 standard, one uses 8 bytes for one float *x*: these 64 bits are split as\n", 78 | "\n", 79 | " - 10 bit for the exponent *b* and\n", 80 | "\n", 81 | " - 54 bit for the mantissa *a*.\n", 82 | "\n", 83 | "This results in\n", 84 | "\n", 85 | "- largest possible float ≈10308 (quality measure for *b*)\n", 86 | "\n", 87 | "- smallest possible (positive) float ≈10−308 (quality measure for *b*)\n", 88 | "\n", 89 | "- distance between 1.0 and next larger number ≈10−16 (quality measure for *a*)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "Note that this is *in principle* how floating point numbers are stored (it is actually a bit more complicated).\n", 97 | "\n", 98 | "#### Limitations of `complex` numbers\n", 99 | "\n", 100 | "The `complex` number type has essentially the same limitations as the `float` data type (see [limitations of floats](#Limitations-of-floats)) because a complex number consists of two `floats`: one represents the real part, the other one the imaginary part.\n", 101 | "\n", 102 | "#### …are these number types of practical value?\n", 103 | "\n", 104 | "In practice, we do not usually find numbers in our daily life that exceed 10300 (this is a number with 300 zeros!), and therefore the floating point numbers cover the range of numbers we usually need.\n", 105 | "\n", 106 | "However, be warned that in scientific computation small and large numbers are used which may (often in intermediate results) exceed the range of floating point numbers.\n", 107 | "\n", 108 | "- Imagine for example, that we have to take the fourth power of the constant ℏ = 1.0545716 ⋅ 10−34*k**g**m*2/*s*:\n", 109 | "\n", 110 | "- ℏ4 = 1.2368136958909421 ⋅ 10−136*k**g*4*m*8/*s*4 which is “halfway” to our representable smallest positive float of the order of 10−308.\n", 111 | "\n", 112 | "If there is any danger that we might exceed the range of the floating point numbers, we have to *rescale* our equations so that (ideally) all numbers are of order unity. Rescaling our equations so that all relevant numbers are approximately 1 is also useful in debugging our code: if numbers much greater or smaller than 1 appear, this may be an indication of an error.\n", 113 | "\n", 114 | "### Using floating point numbers (carelessly)\n", 115 | "\n", 116 | "We know already that we need to take care that our floating point values do not exceed the range of floating point numbers that can be represented in the computer.\n", 117 | "\n", 118 | "There is another complication due to the way floating point numbers have to be represented internally: not all floating point numbers can be represented exactly in the computer. The number 1.0 can be represented exactly but the numbers 0.1, 0.2 and 0.3 cannot:" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 1, 124 | "metadata": {}, 125 | "outputs": [ 126 | { 127 | "data": { 128 | "text/plain": [ 129 | "'1.00000000000000000000'" 130 | ] 131 | }, 132 | "execution_count": 1, 133 | "metadata": {}, 134 | "output_type": "execute_result" 135 | } 136 | ], 137 | "source": [ 138 | "'%.20f' % 1.0" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 2, 144 | "metadata": {}, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "'0.10000000000000000555'" 150 | ] 151 | }, 152 | "execution_count": 2, 153 | "metadata": {}, 154 | "output_type": "execute_result" 155 | } 156 | ], 157 | "source": [ 158 | "'%.20f' % 0.1" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 3, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "data": { 168 | "text/plain": [ 169 | "'0.20000000000000001110'" 170 | ] 171 | }, 172 | "execution_count": 3, 173 | "metadata": {}, 174 | "output_type": "execute_result" 175 | } 176 | ], 177 | "source": [ 178 | "'%.20f' % 0.2" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 4, 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "data": { 188 | "text/plain": [ 189 | "'0.29999999999999998890'" 190 | ] 191 | }, 192 | "execution_count": 4, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "'%.20f' % 0.3" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "Instead, the floating point number “nearest” to the real number is chosen.\n", 206 | "\n", 207 | "This can cause problems. Suppose we need a loop where x takes values 0.1, 0.2, 0.3, …, 0.9, 1.0. We might be tempted to write something like this:\n", 208 | "\n", 209 | "```python\n", 210 | "x = 0.0\n", 211 | "while not x == 1.0:\n", 212 | " x = x + 0.1\n", 213 | " print ( \" x =%19.17f\" % ( x ))\n", 214 | "```" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "However, this loop will never terminate. Here are the first 11 lines of output of the program:\n", 222 | "\n", 223 | " x=0.10000000000000001\n", 224 | " x=0.20000000000000001\n", 225 | " x=0.30000000000000004\n", 226 | " x=0.40000000000000002\n", 227 | " x= 0.5\n", 228 | " x=0.59999999999999998\n", 229 | " x=0.69999999999999996\n", 230 | " x=0.79999999999999993\n", 231 | " x=0.89999999999999991\n", 232 | " x=0.99999999999999989\n", 233 | " x=1.09999999999999987" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "Because the variable `x` never takes exactly the value 1.0, the while loop will continue forever.\n", 241 | "\n", 242 | "Thus: *Never compare two floating point numbers for equality.*" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "### Using floating point numbers carefully 1\n", 250 | "\n", 251 | "There are a number of alternative ways to solve this problem. For example, we can compare the distance between two floating point numbers:" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 5, 257 | "metadata": {}, 258 | "outputs": [ 259 | { 260 | "name": "stdout", 261 | "output_type": "stream", 262 | "text": [ 263 | " x =0.10000000000000001\n", 264 | " x =0.20000000000000001\n", 265 | " x =0.30000000000000004\n", 266 | " x =0.40000000000000002\n", 267 | " x =0.50000000000000000\n", 268 | " x =0.59999999999999998\n", 269 | " x =0.69999999999999996\n", 270 | " x =0.79999999999999993\n", 271 | " x =0.89999999999999991\n", 272 | " x =0.99999999999999989\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "x = 0.0\n", 278 | "while abs(x - 1.0) > 1e-8:\n", 279 | " x = x + 0.1\n", 280 | " print ( \" x =%19.17f\" % ( x ))" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "### Using floating point numbers carefully 2\n", 288 | "\n", 289 | "Alternatively, we can (for this example) iterate over a sequence of integers and compute the floating point number from the integer:" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 6, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "name": "stdout", 299 | "output_type": "stream", 300 | "text": [ 301 | " x =0.10000000000000001\n", 302 | " x =0.20000000000000001\n", 303 | " x =0.30000000000000004\n", 304 | " x =0.40000000000000002\n", 305 | " x =0.50000000000000000\n", 306 | " x =0.60000000000000009\n", 307 | " x =0.70000000000000007\n", 308 | " x =0.80000000000000004\n", 309 | " x =0.90000000000000002\n", 310 | " x =1.00000000000000000\n" 311 | ] 312 | } 313 | ], 314 | "source": [ 315 | "for i in range (1 , 11):\n", 316 | " x = i * 0.1\n", 317 | " print(\" x =%19.17f\" % ( x ))" 318 | ] 319 | }, 320 | { 321 | "cell_type": "code", 322 | "execution_count": 7, 323 | "metadata": {}, 324 | "outputs": [], 325 | "source": [ 326 | "x=0.10000000000000001\n", 327 | "x=0.20000000000000001\n", 328 | "x=0.30000000000000004\n", 329 | "x=0.40000000000000002\n", 330 | "x= 0.5\n", 331 | "x=0.60000000000000009\n", 332 | "x=0.70000000000000007\n", 333 | "x=0.80000000000000004\n", 334 | "x=0.90000000000000002\n", 335 | "x= 1" 336 | ] 337 | }, 338 | { 339 | "cell_type": "markdown", 340 | "metadata": {}, 341 | "source": [ 342 | "If we compare this with the output from the program in [Using floating point numbers (carelessly)](#Using-floating-point-numbers-(carelessly)), we can see that the floating point numbers differ. This means that – in a numerical calculation – it is not true that 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 1.0." 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "metadata": {}, 348 | "source": [ 349 | "### Symbolic calculation\n", 350 | "\n", 351 | "Using the sympy package we have arbitrary precision. Using `sympy.Rational`, we can define the fraction 1/10 exactly symbolically. Adding this 10 times will lead exactly to the value 1, as demonstrated by this script" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 8, 357 | "metadata": {}, 358 | "outputs": [ 359 | { 360 | "name": "stdout", 361 | "output_type": "stream", 362 | "text": [ 363 | "Current x=1/10 = 0.1 \n", 364 | " Reached x=1/10 \n", 365 | "Current x= 1/5 = 0.2 \n", 366 | " Reached x=1/5 \n", 367 | "Current x=3/10 = 0.3 \n", 368 | " Reached x=3/10 \n", 369 | "Current x= 2/5 = 0.4 \n", 370 | " Reached x=2/5 \n", 371 | "Current x= 1/2 = 0.5 \n", 372 | " Reached x=1/2 \n", 373 | "Current x= 3/5 = 0.6 \n", 374 | " Reached x=3/5 \n", 375 | "Current x=7/10 = 0.7 \n", 376 | " Reached x=7/10 \n", 377 | "Current x= 4/5 = 0.8 \n", 378 | " Reached x=4/5 \n", 379 | "Current x=9/10 = 0.9 \n", 380 | " Reached x=9/10 \n", 381 | "Current x= 1 = 1.0 \n", 382 | " Reached x=1 \n" 383 | ] 384 | } 385 | ], 386 | "source": [ 387 | "from sympy import Rational\n", 388 | "dx = Rational (1 ,10)\n", 389 | "x = 0\n", 390 | "while x != 1.0:\n", 391 | " x = x + dx\n", 392 | " print(\"Current x=%4s = %3.1f \" % (x , x . evalf ()))\n", 393 | " print(\" Reached x=%s \" % x)" 394 | ] 395 | }, 396 | { 397 | "cell_type": "markdown", 398 | "metadata": {}, 399 | "source": [ 400 | "However, this symbolic calculation is much slower as it is done through software rather than the CPU-based floating point operations. The next program approximates the relative performances:" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 9, 406 | "metadata": {}, 407 | "outputs": [ 408 | { 409 | "name": "stdout", 410 | "output_type": "stream", 411 | "text": [ 412 | "loop using float dx:\n", 413 | " deviation is -1.88483681995422e-08\n", 414 | "float loop n=100000 takes 0.00408 seconds\n", 415 | "loop using sympy symbolic dx:\n", 416 | " deviation is 0\n", 417 | "sympy loop n = 100000 takes 0.34109 seconds\n", 418 | "Symbolic loop is a factor 83.6 slower.\n" 419 | ] 420 | } 421 | ], 422 | "source": [ 423 | "# NBVAL_IGNORE_OUTPUT\n", 424 | "from sympy import Rational\n", 425 | "dx_symbolic = Rational (1 ,10)\n", 426 | "dx = 0.1\n", 427 | "\n", 428 | "def loop_sympy (n):\n", 429 | " x = 0\n", 430 | " for i in range(n):\n", 431 | " x = x + dx_symbolic\n", 432 | " return x\n", 433 | "\n", 434 | "def loop_float(n):\n", 435 | " x =0\n", 436 | " for i in range(n):\n", 437 | " x = x + dx\n", 438 | " return x\n", 439 | "\n", 440 | "def time_this (f, n):\n", 441 | " import time\n", 442 | " starttime = time.time()\n", 443 | " result = f(n)\n", 444 | " stoptime = time.time()\n", 445 | " print(\" deviation is %16.15g\" % ( n * dx_symbolic - result ))\n", 446 | " return stoptime - starttime\n", 447 | "\n", 448 | "n = 100000\n", 449 | "print(\"loop using float dx:\")\n", 450 | "time_float = time_this(loop_float, n)\n", 451 | "print(\"float loop n=%d takes %6.5f seconds\" % (n, time_float))\n", 452 | "print(\"loop using sympy symbolic dx:\")\n", 453 | "time_sympy = time_this (loop_sympy, n)\n", 454 | "print(\"sympy loop n =% d takes %6.5f seconds\" % (n , time_sympy ))\n", 455 | "print(\"Symbolic loop is a factor %.1f slower.\" % ( time_sympy / time_float ))" 456 | ] 457 | }, 458 | { 459 | "cell_type": "markdown", 460 | "metadata": {}, 461 | "source": [ 462 | "This is of course an artificial example: we have added the symbolic code to demonstrate that these round off errors originate from the approximative representation of floating point numbers in the hardware (and thus programming languages). We can, in principle, avoid these complications by computing using symbolic expressions, but this is in practice too slow.[4]" 463 | ] 464 | }, 465 | { 466 | "cell_type": "markdown", 467 | "metadata": {}, 468 | "source": [ 469 | "### Summary\n", 470 | "\n", 471 | "In summary, we have learned that\n", 472 | "\n", 473 | "- floating point numbers and integers used in numeric computation are generally quite different from “mathematical numbers” (symbolic calculations are exact and use the “mathematical numbers”):\n", 474 | "\n", 475 | " - there is a maximum number and a minimum number that can be represented (for both integers and floating point numbers)\n", 476 | "\n", 477 | " - within this range, not every floating point number can be represented in the computer.\n", 478 | "\n", 479 | "- We deal with this limitation by:\n", 480 | "\n", 481 | " - never comparing two floating point numbers for equality (instead we compute the absolute value of the difference)\n", 482 | "\n", 483 | " - use of algorithms that are *stable* (this means that small deviations from correct numbers can be corrected by the algorithm. We have not yet shown any such examples this document.)\n", 484 | "\n", 485 | "- Note that there is a lot more to be said about numerical and algorithmic tricks and methods to make numeric computation as accurate as possible but this is outside the scope of this section." 486 | ] 487 | }, 488 | { 489 | "cell_type": "markdown", 490 | "metadata": {}, 491 | "source": [ 492 | "### Exercise: infinite or finite loop\n", 493 | "\n", 494 | "1. What does the following piece of code compute? Will the loop ever finish? Why?\n", 495 | "\n", 496 | "```python\n", 497 | "eps = 1.0\n", 498 | "while 1.0 + eps > 1.0:\n", 499 | " eps = eps / 2.0\n", 500 | "print(eps)\n", 501 | "```" 502 | ] 503 | } 504 | ], 505 | "metadata": { 506 | "kernelspec": { 507 | "display_name": "Python 3 (ipykernel)", 508 | "language": "python", 509 | "name": "python3" 510 | }, 511 | "language_info": { 512 | "codemirror_mode": { 513 | "name": "ipython", 514 | "version": 3 515 | }, 516 | "file_extension": ".py", 517 | "mimetype": "text/x-python", 518 | "name": "python", 519 | "nbconvert_exporter": "python", 520 | "pygments_lexer": "ipython3", 521 | "version": "3.9.7" 522 | } 523 | }, 524 | "nbformat": 4, 525 | "nbformat_minor": 1 526 | } 527 | -------------------------------------------------------------------------------- /book/01-introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction\n", 8 | "\n", 9 | "\n", 10 | "This text summarises a number of core ideas relevant to Computational Engineering and Scientific Computing using Python. The emphasis is on introducing some basic Python (programming) concepts that are relevant for numerical algorithms. The later chapters touch upon numerical libraries such as `numpy` and `scipy` each of which deserves much more space than provided here. We aim to enable the reader to learn independently how to use other functionality of these libraries using the available documentation (online and through the packages itself).\n", 11 | "\n", 12 | "## Computational Modelling\n", 13 | "\n", 14 | "### Introduction\n", 15 | "\n", 16 | "Increasingly, processes and systems are researched or developed through computer simulations: new aircraft prototypes such as for the recent A380 are first designed and tested virtually through computer simulations. With the ever increasing computational power available through supercomputers, clusters of computers and even desktop and laptop machines, this trend is likely to continue.\n", 17 | "\n", 18 | "Computer simulations are routinely used in fundamental research to help understand experimental measurements, and to replace – for example – growth and fabrication of expensive samples/experiments where possible. In an industrial context, product and device design can often be done much more cost effectively if carried out virtually through simulation rather than through building and testing prototypes. This is in particular so in areas where samples are expensive such as nanoscience (where it is expensive to create small things) and aerospace industry (where it is expensive to build large things). There are also situations where certain experiments can only be carried out virtually (ranging from astrophysics to study of effects of large scale nuclear or chemical accidents). Computational modelling, including use of computational tools to post-process, analyse and visualise data, has been used in engineering, physics and chemistry for many decades but is becoming more important due to the cheap availability of computational resources. Computational Modelling is also starting to play a more important role in studies of biological systems, the economy, archeology, medicine, health care, and many other domains." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "### Computational Modelling\n", 26 | "\n", 27 | "To study a process with a computer simulation we distinguish two steps: the first one is to develop a *model* of the real system. When studying the motion of a small object, such as a penny, say, under the influence of gravity, we may be able to ignore friction of air: our model — which might only consider the gravitational force and the penny’s inertia, i.e. $a(t) = F/m = -9.81\\mathrm{m}/\\mathrm{s}^2$ — is an approximation of the real system. The model will normally allow us to express the behaviour of the system (in some approximated form) through mathematical equations, which often involve ordinary differential equations (ODEs) or partial differential equatons (PDEs).\n", 28 | "\n", 29 | "In the natural sciences such as physics, chemistry and related engineering, it is often not so difficult to find a suitable model, although the resulting equations tend to be very difficult to solve, and can in most cases not be solved analytically at all.\n", 30 | "\n", 31 | "On the other hand, in subjects that are not as well described through a mathematical framework and depend on behaviour of objects whose actions are impossible to predict deterministically (such as humans), it is much more difficult to find a good model to describe reality. As a rule of thumb, in these disciplines the resulting equations are easier to solve, but they are harder to find and the validity of a model needs to be questioned much more. Typical examples are attempts to simulate the economy, the use of global resources, the behaviour of a panicking crowd, etc.\n", 32 | "\n", 33 | "So far, we have just discussed the development of *models* to describe reality, and using these models does not necessarily involve any computers or numerical work at all. In fact, if a model’s equation can be solved analytically, then one should do this and write down the solution to the equation.\n", 34 | "\n", 35 | "In practice, hardly any model equations of systems of interest can be solved analytically, and this is where the computer comes in: using numerical methods, we can at least study the model *for a particular set of boundary conditions*. For the example considered above, we may not be able to easily see from a numerical solution that the penny’s velocity under the influence of gravity will change linearly with time (which we can read easily from the analytical solution that is available for this simple system: $v(t) = t\\cdot 9.81 \\mathrm{m}/\\mathrm{s}^2+v_0$)).\n", 36 | "\n", 37 | "The numerical solution that can be computed using a computer would consist of data that shows how the velocity changes over time for a particular initial velocity *v*0 (*v*0 is a boundary condition here). The computer program would report a long lists of two numbers keeping the (i) value of time *t**i* for which a particular (ii) value of the velocity *v**i* has been computed. By plotting all *v**i* against *t**i*, or by fitting a curve through the data, we may be able to understand the trend from the data (which we can just see from the analytical solution of course).\n", 38 | "\n", 39 | "It is clearly desirable to find an analytical solutions wherever possible but the number of problems where this is possible is small. Usually, the obtaining numerical result of a computer simulation is very useful (despite the shortcomings of the numerical results in comparison to an analytical expression) because it is the only possible way to study the system at all.\n", 40 | "\n", 41 | "The name *computational modelling* derives from the two steps: (i) *modelling*, i.e. finding a model description of a real system, and (ii) solving the resulting model equations using *computational* methods because this is the only way the equations can be solved at all." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "### Programming to support computational modelling\n", 49 | "\n", 50 | "A large number of packages exist that provide computational modelling capabilities. If these satisfy the research or design needs, and any data processing and visualisation is appropriately supported through existing tools, one can carry out computational modelling studies without any deeper programming knowledge.\n", 51 | "\n", 52 | "In a research environment – both in academia and research on new products/ideas/... in industry – one often reaches a point where existing packages will not be able to perform a required simulation task, or where more can be learned from analysing existing data in news ways etc.\n", 53 | "\n", 54 | "At that point, programming skills are required. It is also generally useful to have a broad understanding of the building blocks of software and basic ideas of software engineering as we use more and more devices that are software-controlled.\n", 55 | "\n", 56 | "It is often forgotten that there is nothing the computer can do that we as humans cannot do. The computer can do it much faster, though, and also with making far fewer mistakes. There is thus no magic in computations a computer carries out: they could have been done by humans, and – in fact – were for many years (see for example Wikipedia entry on [Human Computer](https://en.wikipedia.org/wiki/Human_computer)).\n", 57 | "\n", 58 | "Understanding how to build a computer simulation comes roughly down to: (i) finding the model (often this means finding the right equations), (ii) knowing how to solve these equations numerically, (ii) to implement the methods to compute these solutions (this is the programming bit)." 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "Why Python for scientific computing?\n", 66 | "------------------------------------\n", 67 | "\n", 68 | "The design focus on the Python language is on productivity and code readability, for example through:\n", 69 | "\n", 70 | "- Interactive python console\n", 71 | "\n", 72 | "- Very clear, readable syntax through whitespace indentation\n", 73 | "\n", 74 | "- Strong introspection capabilities\n", 75 | "\n", 76 | "- Full modularity, supporting hierarchical packages\n", 77 | "\n", 78 | "- Exception-based error handling\n", 79 | "\n", 80 | "- Dynamic data types & automatic memory management\n", 81 | "\n", 82 | "*As Python is an interpreted language, and it runs many times slower than compiled code, one might ask why anybody should consider such a ’slow’ language for computer simulations?*\n", 83 | "\n", 84 | "There are two replies to this criticism:\n", 85 | "\n", 86 | "1. *Implementation time versus execution time*: It is not the execution time alone that contributes to the cost of a computational project: one also needs to consider the cost of the development and maintenance work.\n", 87 | "\n", 88 | " In the early days of scientific computing (say in the 1960/70/80), compute time was so expensive that it made perfect sense to invest many person months of a programmer’s time to improve the performance of a calculation by a few percent.\n", 89 | "\n", 90 | " Nowadays, however, the CPU cycles have become much cheaper than the programmer’s time. For research codes which often run only a small number of times (before the researchers move on to the next problem), it may be economic to accept that the code runs only at 25% of the expected possible speed if this saves, say, a month of a researcher’s (or programmers) time. For example: if the execution time of the piece of code is 10 hours, and one can predict that it will run about 100 times, then the total execution time is approximately 1000 hours. It would be great if this could be reduced to 25% and one could save 750 (CPU) hours. On the other hand, is an extra wait (about a month) and the cost of 750 CPU hours worth investing one month of a person’s time \\[who could do something else while the calculation is running\\]? Often, the answer is not.\n", 91 | "\n", 92 | " *Code readability & maintenance - short code, fewer bugs*: A related issue is that a research code is not only used for one project, but carries on to be used again and again, evolves, grows, bifurcates etc. In this case, it is often justified to invest more time to make the code fast. At the same time, a significant amount of programmer time will go into (i) introducing the required changes, (ii) testing them even before work on speed optimisation of the changed version can start. To be able to maintain, extend and modify a code in often unforeseen ways, it can only be helpful to use a language that is easy to read and of great expressive power.\n", 93 | "\n", 94 | "2. *Well-written Python code can be very fast* if time critical parts in executed through compiled language.\n", 95 | "\n", 96 | " Typically, less than 5% percent of the code base of a simulation project need more than 95% of the execution time. As long as these calculations are done very efficiently, one doesn’t need to worry about all other parts of the code as the overall time their execution takes is insignificant.\n", 97 | "\n", 98 | " The compute intense part of the program should to be tuned to reach optimal performance. Python offers a number of options.\n", 99 | "\n", 100 | " - For example, the `numpy` Python extension provides a Python interface to the compiled and efficient LAPACK libraries that are the quasi-standard in numerical linear algebra. If the problems under study can be formulated such that eventually large systems of algebraic equations have to be solved, or eigenvalues computed, etc, then the compiled code in the LAPACK library can be used (through the Python-numpy package). At this stage, the calculations are carried out with the same performance of Fortran/C as it is essentially Fortran/C code that is used. Matlab, by the way, exploits exactly this: the Matlab scripting language is very slow (about 10 time slower than Python), but Matlab gains its power from delegating the matix operation to the compiled LAPACK libraries.\n", 101 | "\n", 102 | " - Existing numerical C/Fortran libraries can be interfaced to be usable from within Python (using for example Swig, Boost.Python and Cython).\n", 103 | "\n", 104 | " - Python can be extended through compiled languages if the computationally demanding part of the problem is algorithmically non-standard and no existing libraries can be used.\n", 105 | "\n", 106 | " Commonly used are C, Fortran and C++ to implement fast extensions.\n", 107 | "\n", 108 | " - We list some tools that are used to use compiled code from Python:\n", 109 | "\n", 110 | " - The `scipy.weave` extension is useful if just a short expression needs to be expressed in C.\n", 111 | " - The Cython interface is growing in popularity to (i) semi-automatically declare variable types in Python code, to translate that code to C (automatically) and to then use the compiled C code from Python. Cython is also used to quickly wrap an existing C library with an interface so the C library can be used from Python.\n", 112 | "\n", 113 | " - Boost.Python is specialised for wrapping C++ code in Python." 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "*The conclusion is that Python is “fast enough” for most computational tasks, and that its user friendly high-level language often makes up for reduced speed in comparison to compiled lower-level languages. Combining Python with tailor-written compiled code for the performance critical parts of the code, results in virtually optimal speed in most cases.*" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "### Optimisation strategies\n", 128 | "\n", 129 | "We generally understand reduction of execution time when discussing “code optimisation” in the context of computational modelling, and we essentially like to carry out the required calculations as fast as possible. (Sometimes we need to reduce the amount of RAM, the amount of data input output to disk or the network.) At the same time, we need to make sure that we do not invest inappropriate amounts of programming time to achieve this speed up: as always there needs to be a balance between the programmers’ time and the improvement we can gain from this." 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "### Get it right first, then make it fast\n", 137 | "\n", 138 | "To write fast code effectively, we note that the right order is to (i) first write a program that carries out the correct calculation. For this, choose a language/approach that allows you to *write the code quickly and make it work quickly* — regardless of execution speed. Then (ii) either change the program or re-write it from scratch in the same language to make the execution faster. During the process, keep comparing results with the slow version written first to make sure the optimisation does not introduce errors. (Once we are familiar with the concept of regression tests, they should be used here to compare the new and hopefully faster code with the original code.)\n", 139 | "\n", 140 | "A common pattern in Python is to start writing pure Python code, then start using Python libraries that use compiled code internally (such as the fast arrays Numpy provides, and routines from scipy that go back to established numerical codes such as ODEPACK, LAPACK and others). If required, one can – after careful profiling – start to replace parts of the Python code with a compiled language such as C and Fortran to improve execution speed further (as discussed above)." 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "### Prototyping in Python\n", 148 | "\n", 149 | "It turns out that – even if a particular code has to be written in, say, C++ – it is (often) more time efficient to prototype the code in Python, and once an appropriate design (and class structure) has been found, to translate the code to C++.\n", 150 | "\n", 151 | "### Literature\n", 152 | "\n", 153 | "While this text starts with an introduction of (some aspects of) the basic Python programming language, you may find - depending on your prior experience - that you need to refer to secondary sources to fully understand some ideas.\n", 154 | "\n", 155 | "We repeatedly refer to the following documents:\n", 156 | "\n", 157 | "- Allen Downey, *Think Python*. Available online in html and pdf at , or from Amazon.\n", 158 | "\n", 159 | "- The Python documentation , and:\n", 160 | "\n", 161 | "- The Python tutorial ()\n", 162 | "\n", 163 | "You may also find the following links useful:\n", 164 | "\n", 165 | "- The `numpy` home page ()\n", 166 | "\n", 167 | "- The `scipy` home page ()\n", 168 | "\n", 169 | "- The `matplotlib` home page ().\n", 170 | "\n", 171 | "- The Python style guide (" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "### Recorded video lectures on Python for beginners\n", 179 | "\n", 180 | "Do you like to listen/follow lectures? There is a series of 24 lectures titled *Introduction to Computer Science and Programming* delivered by Eric Grimsom and John Guttag from the MIT available at This is aimed at students with little or no programming experience. It aims to provide students with an understanding of the role computation can play in solving problems. It also aims to help students, regardless of their major, to feel justifiably confident of their ability to write small programs that allow them to accomplish useful goals.\n", 181 | "\n", 182 | "An more recent [collection of topic specific (and shorter) tutorial videos](https://www.youtube.com/playlist?list=PLi01XoE8jYohWFPpC17Z-wWhPOSuh8Er-) is available from [Socratica](https://www.youtube.com/c/Socratica) .\n", 183 | "\n", 184 | "### Python tutor mailing list\n", 185 | "\n", 186 | "There is also a Python tutor mailing list () where beginners are welcome to ask questions regarding Python. Both using the archives and posting your own queries (or in fact helping others) may help with understanding the language. Use the normal mailing list etiquette (i.e. be polite, concise, etc). You may want to read for some guidance on how to ask questions on mailing lists." 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "Python version\n", 194 | "--------------\n", 195 | "\n", 196 | "There are two version of the Python language out there: Python 2.x and Python 3.x. They are (slightly) different — the changes in Python 3.x were introduced to address shortcomings in the design of the language that were identified since Python’s inception. A decision was made that some incompatibility should be accepted to achieve the higher goal of a better language for the future.\n", 197 | "\n", 198 | "For scientific computation, it is crucial to make use of numerical libraries such as [numpy](https://numpy.org/), [scipy](https://www.scipy.org) and the plotting package [matplotlib](https://matplotlib.org/).\n", 199 | "\n", 200 | "All of these are now available for Python 3, and we will use Python 3.x in this book.\n", 201 | "\n", 202 | "However, there is a lot of code still in use that was written for Python 2, and it's useful to be aware of the differences. The most prominent example is that in Python 2.x, the `print` command is special, whereas in Python 3 it is an ordinary function. For example, in Python 2.7, we can write:\n", 203 | "\n", 204 | "```python\n", 205 | "print \"Hello World\"\n", 206 | "```" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "where as in Python 3, this would cause a SyntaxError. The right way to use `print` in Python 3 would be as a function, i.e." 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 1, 219 | "metadata": {}, 220 | "outputs": [ 221 | { 222 | "name": "stdout", 223 | "output_type": "stream", 224 | "text": [ 225 | "Hello World\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "print(\"Hello World\")" 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "See [Chapter 5: Input and Output](05-input-output.ipynb) for further details.\n", 238 | "\n", 239 | "Fortunately, the function notation (i.e. with the parantheses) is also allowed in Python 2.7, so our examples should execute in Python 3.x and Python 2.7. (There are other differences.)" 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "These documents\n", 247 | "---------------\n", 248 | "\n", 249 | "This material has been converted from Latex to a set of [Jupyter Notebooks](https://jupyter.org), making it possible to interact with the examples. You can run any code block with an `In [ ]:` prompt by clicking on it and pressing shift-enter, or by clicking the button in the toolbar.\n", 250 | "\n", 251 | "*Code blocks* can be recognised (in the html and pdf version of this book) by having coloured items (to emphasise the syntactic role). For example:" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 1, 257 | "metadata": {}, 258 | "outputs": [ 259 | { 260 | "name": "stdout", 261 | "output_type": "stream", 262 | "text": [ 263 | "Hello\n", 264 | "Hello\n", 265 | "Hello\n" 266 | ] 267 | } 268 | ], 269 | "source": [ 270 | "for i in range(3):\n", 271 | " print(\"Hello\")" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "The *output* (here `Hello` repeated on three lines) that is produced by the code block, is shown below the code block and not coloured." 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": {}, 284 | "source": [ 285 | "### The `%%file` magic\n", 286 | "\n", 287 | "We use some features in the notebook that are worth being aware of at this point: a cell starting with the special command `%%file FILENAME` will create (or override) a file with name `FILENAME` that contains the content that is shown in the cell below. \n", 288 | "\n", 289 | "For example " 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 1, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "name": "stdout", 299 | "output_type": "stream", 300 | "text": [ 301 | "Overwriting hello.txt\n" 302 | ] 303 | } 304 | ], 305 | "source": [ 306 | "%%file hello.txt\n", 307 | "This is the content of the file hello.txt" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "To confirm the file has been written and contains, we use some Python commands (which you are not expected to understand at this point):" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 2, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "name": "stdout", 324 | "output_type": "stream", 325 | "text": [ 326 | "This is the content of the file hello.txt\n", 327 | "\n" 328 | ] 329 | } 330 | ], 331 | "source": [ 332 | "with open(\"hello.txt\") as f:\n", 333 | " print(f.read())" 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "metadata": {}, 339 | "source": [ 340 | "### The `!` to execute shell commands\n", 341 | "\n", 342 | "If we want to run a shell command, we can type it and preceed it by the `!` character. Here is an example: first we create a file that contains a Python hello world program, then we execute it:" 343 | ] 344 | }, 345 | { 346 | "cell_type": "code", 347 | "execution_count": 4, 348 | "metadata": {}, 349 | "outputs": [ 350 | { 351 | "name": "stdout", 352 | "output_type": "stream", 353 | "text": [ 354 | "Overwriting hello.py\n" 355 | ] 356 | } 357 | ], 358 | "source": [ 359 | "%%file hello.py\n", 360 | "print(\"Hello World\")" 361 | ] 362 | }, 363 | { 364 | "cell_type": "code", 365 | "execution_count": 5, 366 | "metadata": {}, 367 | "outputs": [ 368 | { 369 | "name": "stdout", 370 | "output_type": "stream", 371 | "text": [ 372 | "Hello World\r\n" 373 | ] 374 | } 375 | ], 376 | "source": [ 377 | "!python hello.py" 378 | ] 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "metadata": {}, 383 | "source": [ 384 | "### The `#NBVAL` tags\n", 385 | "\n", 386 | "In some cells, you will find tags like `#NBVAL_SKIP`, `#NBVAL_IGNORE_OUTPUT` and `#NBVAL_RAISES_EXCEPTION`. You can ignore these. \n", 387 | "\n", 388 | "(We use them to be able to [automatically execute all notebooks](https://app.circleci.com/pipelines/github/fangohr/introduction-to-python-for-computational-science-and-engineering) to check that the output produced is the same as what is stored in the notebook. This is an advanced topic of testing, and you can read more about NBVAL at https://github.com/computationalmodelling/nbval)." 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "metadata": {}, 394 | "source": [ 395 | "See [Chapter 11](11-python-shells.ipynb) for more information on Jupyter and other Python interfaces." 396 | ] 397 | }, 398 | { 399 | "cell_type": "markdown", 400 | "metadata": {}, 401 | "source": [ 402 | "Your feedback\n", 403 | "-------------\n", 404 | "\n", 405 | "is desired. If you find anything wrong in this text, or have suggestions how to change or extend it, please feel free to contact Hans at `hans.fangohr@xfel.eu` .\n", 406 | "\n", 407 | "If you find a URL that is not working (or pointing to the wrong material), please let Hans know as well. As the content of the Internet is changing rapidly, it is difficult to keep up with these changes without feedback." 408 | ] 409 | } 410 | ], 411 | "metadata": { 412 | "kernelspec": { 413 | "display_name": "Python 3 (ipykernel)", 414 | "language": "python", 415 | "name": "python3" 416 | }, 417 | "language_info": { 418 | "codemirror_mode": { 419 | "name": "ipython", 420 | "version": 3 421 | }, 422 | "file_extension": ".py", 423 | "mimetype": "text/x-python", 424 | "name": "python", 425 | "nbconvert_exporter": "python", 426 | "pygments_lexer": "ipython3", 427 | "version": "3.9.7" 428 | } 429 | }, 430 | "nbformat": 4, 431 | "nbformat_minor": 1 432 | } 433 | -------------------------------------------------------------------------------- /book/07-functions-modules.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Functions and modules\n", 8 | "\n", 9 | "## Introduction\n", 10 | "\n", 11 | "Functions allow us to group a number of statements into a logical block. We communicate with a function through a clearly defined interface, providing certain parameters to the function, and receiving some information back. Apart from this interface, we generally do not how exactly a function does the work to obtain the value it returns\n", 12 | "\n", 13 | "For example the function `math.sqrt`: we do not know how exactly it computes the square root, but we know about the interface: if we pass *x* into the function, it will return (an approximation of) $\\sqrt{x}$.\n", 14 | "\n", 15 | "This abstraction is a useful thing: it is a common technique in engineering to break down a system into smaller (black-box) components that all work together through well defined interfaces, but which do not need to know about the internal realisations of each other’s functionality. In fact, not having to care about these implementation details can help to have a clearer view of the system composed of many of these components.\n", 16 | "\n", 17 | "Functions provide the basic building blocks of functionality in larger programs (and computer simulations), and help to control the inherent complexity of the process.\n", 18 | "\n", 19 | "We can group functions together into a Python module (see [modules](#Modules)), and in this way create our own libraries of functionality.\n", 20 | "\n", 21 | "## Using functions\n", 22 | "\n", 23 | "The word “function” has different meanings in mathematics and programming. In programming it refers to a named sequence of operations that perform a computation. For example, the function `sqrt()` which is defined in the `math` module computes the square root of a given value:" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": {}, 30 | "outputs": [ 31 | { 32 | "data": { 33 | "text/plain": [ 34 | "2.0" 35 | ] 36 | }, 37 | "execution_count": 1, 38 | "metadata": {}, 39 | "output_type": "execute_result" 40 | } 41 | ], 42 | "source": [ 43 | "from math import sqrt\n", 44 | "sqrt(4)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "The value we pass to the function `sqrt` is 4 in this example. This value is called the *argument* of the function. A function may have more than one argument.\n", 52 | "\n", 53 | "The function returns the value 2.0 (the result of its computation) to the “calling context”. This value is called the *return value* of the function.\n", 54 | "\n", 55 | "It is common to say that a function *takes* an argument and *returns* a result or return value.\n", 56 | "\n", 57 | "**Common confusion about printing and returning values**\n", 58 | "\n", 59 | "It is a common beginner’s mistake to confuse the *printing* of values with *returning* values. In the following example it is hard to see whether the function `math.sin` returns a value or whether it prints the value:" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "data": { 69 | "text/plain": [ 70 | "0.9092974268256817" 71 | ] 72 | }, 73 | "execution_count": 2, 74 | "metadata": {}, 75 | "output_type": "execute_result" 76 | } 77 | ], 78 | "source": [ 79 | "import math\n", 80 | "math.sin(2)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "We import the `math` module, and call the `math.sin` function with an argument of `2`. The `math.sin(2)` call will actually *return* the value `0.909...` not print it. However, because we have not assigned the return value to a variable, the Python prompt will print the returned object.\n", 88 | "\n", 89 | "The following alternative sequence works only if the value is returned:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 3, 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "0.9092974268256817\n" 102 | ] 103 | } 104 | ], 105 | "source": [ 106 | "x = math.sin(2)\n", 107 | "print(x)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "The return value of the function call `math.sin(2)` is assigned to the variable `x`, and `x` is printed in the next line.\n", 115 | "\n", 116 | "Generally, functions should execute “silently” (i.e. not print anything) and report the result of their computation through the return value.\n", 117 | "\n", 118 | "Part of the confusion about printed versus return values at the Python prompt comes from the Python prompt printing (a representation) of returned objects *if* the returned objects are not assigned. Generally, seeing the returned objects is exactly what we want (as we normally care about the returned object), just when learning Python this may cause mild confusion about functions returning values or printing values.\n", 119 | "\n", 120 | "**Further information**\n", 121 | "\n", 122 | "- Think Python has a gentle introduction to functions (on which the previous paragraph is based) in [chapter 3 (Functions)](https://www.greenteapress.com/thinkpython/html/book004.html) and [chapter 6 (Fruitful functions)](https://www.greenteapress.com/thinkpython/html/book007.html)." 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "## Defining functions\n", 130 | "\n", 131 | "The generic format of a function definitions:\n", 132 | "\n", 133 | "```python\n", 134 | "def my_function(arg1, arg2, ..., argn):\n", 135 | " \"\"\"Optional docstring.\"\"\"\n", 136 | "\n", 137 | " # Implementation of the function\n", 138 | "\n", 139 | " return result # optional\n", 140 | "\n", 141 | "# this is not part of the function\n", 142 | "some_command\n", 143 | "```" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "Allen Downey’s terminology (in his book [Think Python](https://www.greenteapress.com/thinkpython/html/index.html)) of fruitful and fruitless functions distinguishes between functions that return a value, and those that do not return a value. The distinction refers to whether a function provides a return value (=fruitful) or whether the function does not explicitly return a value (=fruitless). If a function does not make use of the `return` statement, we tend to say that the function returns nothing (whereas in reality in will always return the `None` object when it terminates – even if the `return` statement is missing).\n", 151 | "\n", 152 | "For example, the function `greeting` will print “Hello World” when called (and is fruitless as it does not return a value)." 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 4, 158 | "metadata": { 159 | "collapsed": true 160 | }, 161 | "outputs": [], 162 | "source": [ 163 | "def greeting():\n", 164 | " print(\"Hello World!\")" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "If we call that function:" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 5, 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "name": "stdout", 181 | "output_type": "stream", 182 | "text": [ 183 | "Hello World!\n" 184 | ] 185 | } 186 | ], 187 | "source": [ 188 | "greeting()" 189 | ] 190 | }, 191 | { 192 | "cell_type": "markdown", 193 | "metadata": {}, 194 | "source": [ 195 | "it prints “Hello World” to stdout, as we would expect. If we assign the return value of the function to a variable `x`, we can inspect it subsequently:" 196 | ] 197 | }, 198 | { 199 | "cell_type": "code", 200 | "execution_count": 6, 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "Hello World!\n" 208 | ] 209 | } 210 | ], 211 | "source": [ 212 | "x = greeting()" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 7, 218 | "metadata": {}, 219 | "outputs": [ 220 | { 221 | "name": "stdout", 222 | "output_type": "stream", 223 | "text": [ 224 | "None\n" 225 | ] 226 | } 227 | ], 228 | "source": [ 229 | "print(x)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "and find that the `greeting` function has indeed returned the `None` object.\n", 237 | "\n", 238 | "Another example for a function that does not return any value (that means there is no `return` keyword in the function) would be:" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 8, 244 | "metadata": { 245 | "collapsed": true 246 | }, 247 | "outputs": [], 248 | "source": [ 249 | "def printpluses(n): \n", 250 | " print(n * \"+\")" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "metadata": {}, 256 | "source": [ 257 | "Generally, functions that return values are more useful as these can be used to assemble code (maybe as another function) by combining them cleverly. Let’s look at some examples of functions that do return a value.\n", 258 | "\n", 259 | "Suppose we need to define a function that computes the square of a given variable. The function source could be:" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": 9, 265 | "metadata": { 266 | "collapsed": true 267 | }, 268 | "outputs": [], 269 | "source": [ 270 | "def square(x):\n", 271 | " return x * x" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "The keyword `def` tells Python that we are *defining* a function at that point. The function takes one argument (`x`). The function returns `x*x` which is of course $x^2$. Here is the listing of a file that shows how the function can be defined and used: (note that the numbers on the left are line numbers and are not part of the program)" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": 10, 284 | "metadata": { 285 | "attributes": { 286 | "classes": [ 287 | "numberLines" 288 | ], 289 | "id": "" 290 | } 291 | }, 292 | "outputs": [ 293 | { 294 | "name": "stdout", 295 | "output_type": "stream", 296 | "text": [ 297 | "0 * 0 = 0\n", 298 | "1 * 1 = 1\n", 299 | "2 * 2 = 4\n", 300 | "3 * 3 = 9\n", 301 | "4 * 4 = 16\n" 302 | ] 303 | } 304 | ], 305 | "source": [ 306 | "def square(x):\n", 307 | " return x * x\n", 308 | "\n", 309 | "for i in range(5):\n", 310 | " i_squared = square(i)\n", 311 | " print(i, '*', i, '=', i_squared)" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "It is worth mentioning that lines 1 and 2 define the square function whereas lines 4 to 6 are the main program." 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "metadata": {}, 324 | "source": [ 325 | "We can define functions that take more than one argument:" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": 11, 331 | "metadata": { 332 | "collapsed": true 333 | }, 334 | "outputs": [], 335 | "source": [ 336 | "import math\n", 337 | "\n", 338 | "def hypot(x, y):\n", 339 | " return math.sqrt(x * x + y * y)" 340 | ] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "metadata": {}, 345 | "source": [ 346 | "It is also possible to return more than one argument. Here is an example of a function that converts a given string into all characters uppercase and all characters lowercase and returns the two versions. We have included the main program to show how this function can be called:" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 12, 352 | "metadata": {}, 353 | "outputs": [ 354 | { 355 | "name": "stdout", 356 | "output_type": "stream", 357 | "text": [ 358 | "Banana in lowercase: banana and in uppercase BANANA\n" 359 | ] 360 | } 361 | ], 362 | "source": [ 363 | "def upperAndLower(string):\n", 364 | " return string.upper(), string.lower()\n", 365 | "\n", 366 | "testword = 'Banana'\n", 367 | "\n", 368 | "uppercase, lowercase = upperAndLower(testword)\n", 369 | "\n", 370 | "print(testword, 'in lowercase:', lowercase,\n", 371 | " 'and in uppercase', uppercase)" 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "metadata": {}, 377 | "source": [ 378 | "We can define multiple Python functions in one file. Here is an example with two functions:" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": 13, 384 | "metadata": {}, 385 | "outputs": [ 386 | { 387 | "name": "stdout", 388 | "output_type": "stream", 389 | "text": [ 390 | "*****************Hello world!*****************\n" 391 | ] 392 | } 393 | ], 394 | "source": [ 395 | "def returnstars( n ):\n", 396 | " return n * '*'\n", 397 | "\n", 398 | "def print_centred_in_stars( string ):\n", 399 | " linelength = 46 \n", 400 | " starstring = returnstars((linelength - len(string)) // 2)\n", 401 | "\n", 402 | " print(starstring + string + starstring)\n", 403 | "\n", 404 | "print_centred_in_stars('Hello world!')" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "metadata": {}, 410 | "source": [ 411 | "**Further reading**\n", 412 | "\n", 413 | "- [Python Tutorial: Section 4.6 Defining Functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)\n", 414 | "\n", 415 | "## Default values and optional parameters\n", 416 | "\n", 417 | "Python allows to define *default* values for function parameters. Here is an example: This program will print the following output when executed: So how does it work? The function `print_mult_table` takes two arguments: `n` and `upto`. The first argument `n` is a “normal” variable. The second argument `upto` has a default value of 10. In other words: should the user of this function only provide one argument, then this provides the value for `n` and `upto` will default to 10. If two arguments are provided, the first one will be for `n` and the second for `upto` (as shown in the code example above)." 418 | ] 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "metadata": {}, 423 | "source": [ 424 | "## Modules\n", 425 | "\n", 426 | "Modules\n", 427 | "\n", 428 | "- Group together functionality\n", 429 | "\n", 430 | "- Provide namespaces\n", 431 | "\n", 432 | "- Python’s standard library contains a vast collection of modules - “Batteries Included”\n", 433 | "\n", 434 | " - Try `help(’modules’)`\n", 435 | "\n", 436 | "- Means of extending Python\n", 437 | "\n", 438 | "### Importing modules" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": 14, 444 | "metadata": { 445 | "collapsed": true 446 | }, 447 | "outputs": [], 448 | "source": [ 449 | "import math" 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "metadata": {}, 455 | "source": [ 456 | "This will introduce the name `math` into the namespace in which the import command was issued. The names within the `math` module will not appear in the enclosing namespace: they must be accessed through the name `math`. For example: `math.sin`." 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 15, 462 | "metadata": { 463 | "collapsed": true 464 | }, 465 | "outputs": [], 466 | "source": [ 467 | "import math, cmath" 468 | ] 469 | }, 470 | { 471 | "cell_type": "markdown", 472 | "metadata": {}, 473 | "source": [ 474 | "More than one module can be imported in the same statement, although the [Python Style Guide](https://www.python.org/dev/peps/pep-0008/) recommends not to do this. Instead, we should write" 475 | ] 476 | }, 477 | { 478 | "cell_type": "code", 479 | "execution_count": 16, 480 | "metadata": { 481 | "collapsed": true 482 | }, 483 | "outputs": [], 484 | "source": [ 485 | "import math\n", 486 | "import cmath\n", 487 | "\n", 488 | "import math as mathematics" 489 | ] 490 | }, 491 | { 492 | "cell_type": "markdown", 493 | "metadata": {}, 494 | "source": [ 495 | "The name by which the module is known locally can be different from its “official” name. Typical uses of this are\n", 496 | "\n", 497 | "- To avoid name clashes with existing names\n", 498 | "\n", 499 | "- To change the name to something more manageable. For example `import SimpleHTTPServer as shs`. This is discouraged for production code (as longer meaningful names make programs far more understandable than short cryptic ones), but for interactively testing out ideas, being able to use a short synonym can make your life much easier. Given that (imported) modules are first class objects, you can, of course, simply do `shs = SimpleHTTPServer` in order to obtain the more easily typable handle on the module.\n", 500 | "\n", 501 | "" 502 | ] 503 | }, 504 | { 505 | "cell_type": "code", 506 | "execution_count": 17, 507 | "metadata": { 508 | "collapsed": true 509 | }, 510 | "outputs": [], 511 | "source": [ 512 | "from math import sin" 513 | ] 514 | }, 515 | { 516 | "cell_type": "markdown", 517 | "metadata": {}, 518 | "source": [ 519 | "This will import the `sin` function from the `math` module, but it will not introduce the name math into the current namespace. It will only introduce the name `sin` into the current namespace. It is possible to pull in more than one name from the module in one go:" 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "execution_count": 18, 525 | "metadata": { 526 | "collapsed": true 527 | }, 528 | "outputs": [], 529 | "source": [ 530 | "from math import sin, cos" 531 | ] 532 | }, 533 | { 534 | "cell_type": "markdown", 535 | "metadata": {}, 536 | "source": [ 537 | "Finally, let’s look at this notation:" 538 | ] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "execution_count": 19, 543 | "metadata": { 544 | "collapsed": true 545 | }, 546 | "outputs": [], 547 | "source": [ 548 | "from math import *" 549 | ] 550 | }, 551 | { 552 | "cell_type": "markdown", 553 | "metadata": {}, 554 | "source": [ 555 | "Once again, this does not introduce the name math into the current namespace. It does however introduce *all public names* of the math module into the current namespace. Broadly speaking, it is a bad idea to do this:\n", 556 | "\n", 557 | "- Lots of new names will be dumped into the current namespace.\n", 558 | "\n", 559 | "- Are you sure they will not clobber any names already present?\n", 560 | "\n", 561 | "- It will be very difficult to trace where these names came from\n", 562 | "\n", 563 | "- Having said that, some modules (including ones in the standard library, recommend that they be imported in this way). Use with caution!\n", 564 | "\n", 565 | "- This is fine for interactive quick and dirty testing or small calculations." 566 | ] 567 | }, 568 | { 569 | "cell_type": "markdown", 570 | "metadata": {}, 571 | "source": [ 572 | "### Creating modules\n", 573 | "\n", 574 | "A module is in principle nothing else than a python file. \n", 575 | "We create an example of a module file which is saved in `module1.py`:\n" 576 | ] 577 | }, 578 | { 579 | "cell_type": "code", 580 | "execution_count": 20, 581 | "metadata": {}, 582 | "outputs": [ 583 | { 584 | "name": "stdout", 585 | "output_type": "stream", 586 | "text": [ 587 | "Overwriting module1.py\n" 588 | ] 589 | } 590 | ], 591 | "source": [ 592 | "%%file module1.py\n", 593 | "def someusefulfunction():\n", 594 | " pass\n", 595 | "\n", 596 | "print(\"My name is\", __name__)" 597 | ] 598 | }, 599 | { 600 | "cell_type": "markdown", 601 | "metadata": {}, 602 | "source": [ 603 | "We can execute this (module) file as a normal python program (for example `python module1.py`):" 604 | ] 605 | }, 606 | { 607 | "cell_type": "code", 608 | "execution_count": 21, 609 | "metadata": {}, 610 | "outputs": [ 611 | { 612 | "name": "stdout", 613 | "output_type": "stream", 614 | "text": [ 615 | "My name is __main__\r\n" 616 | ] 617 | } 618 | ], 619 | "source": [ 620 | "!python3 module1.py" 621 | ] 622 | }, 623 | { 624 | "cell_type": "markdown", 625 | "metadata": {}, 626 | "source": [ 627 | "We note that the Python magic variable `__name__` takes the value `__main__` if the program file `module1.py` is executed.\n", 628 | "\n", 629 | "On the other hand, we can *import* `module1.py` in another file (which could have the name `prog.py`), for example like this:" 630 | ] 631 | }, 632 | { 633 | "cell_type": "code", 634 | "execution_count": 2, 635 | "metadata": {}, 636 | "outputs": [ 637 | { 638 | "name": "stdout", 639 | "output_type": "stream", 640 | "text": [ 641 | "My name is module1\n" 642 | ] 643 | } 644 | ], 645 | "source": [ 646 | "import module1 # in file prog.py" 647 | ] 648 | }, 649 | { 650 | "cell_type": "markdown", 651 | "metadata": {}, 652 | "source": [ 653 | "When Python comes across the `import module1` statement in `prog.py`, it looks for the file `module1.py` in the current working directory (and if it can’t find it there in all the directories in `sys.path`) and opens the file `module1.py`. While parsing the file `module1.py` from top to bottom, it will add any function definitions in this file into the `module1` name space in the calling context (that is the main program in `prog.py`). It this example, there is only the function `someusefulfunction`. Once the import process is completed, we can make use of `module1.someusefulfunction` in `prog.py`. If Python comes across statements other than function (and class) definitions while importing `module1.py`, it carries those out immediately. In this case, it will thus come across the statement `print(My name is, __name__)`." 654 | ] 655 | }, 656 | { 657 | "cell_type": "markdown", 658 | "metadata": {}, 659 | "source": [ 660 | "Note the difference to the output if we *import* `module1.py` rather than executing it on its own: `__name__` inside a module takes the value of the module name if the file is imported.\n", 661 | "\n", 662 | "### Use of \\_\\_name\\_\\_\n", 663 | "\n", 664 | "In summary,\n", 665 | "\n", 666 | "- `__name__` is `__main__` if the module file is run on its own\n", 667 | "\n", 668 | "- `__name__` is the name of the module (i.e. the module filename without the `.py` suffix) if the module file is imported.\n", 669 | "\n", 670 | "We can therefor use the following `if` statement in `module1.py` to write code that is *only run* when the module is executed on its own: This is useful to keep test programs or demonstrations of the abilities of a module in this “conditional” main program. It is common practice for any module files to have such a conditional main program which demonstrates its capabilities." 671 | ] 672 | }, 673 | { 674 | "cell_type": "markdown", 675 | "metadata": {}, 676 | "source": [ 677 | "### Example 1\n", 678 | "\n", 679 | "The next example shows a main program for the another file `vectools.py` that is used to demonstrate the capabilities of the functions defined in that file:" 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": 1, 685 | "metadata": {}, 686 | "outputs": [ 687 | { 688 | "output_type": "stream", 689 | "name": "stdout", 690 | "text": [ 691 | "Overwriting vectools.py\n" 692 | ] 693 | } 694 | ], 695 | "source": [ 696 | "%%file vectools.py\n", 697 | "from __future__ import division\n", 698 | "import math\n", 699 | "\n", 700 | "import numpy as N\n", 701 | "\n", 702 | "\n", 703 | "def norm(x):\n", 704 | " \"\"\"returns the magnitude of a vector x\"\"\"\n", 705 | " return math.sqrt(sum(x ** 2))\n", 706 | "\n", 707 | "\n", 708 | "def unitvector(x):\n", 709 | " \"\"\"returns a unit vector x/|x|. x needs to be a numpy array.\"\"\"\n", 710 | " xnorm = norm(x)\n", 711 | " if xnorm == 0:\n", 712 | " raise ValueError(\"Can't normalise vector with length 0\")\n", 713 | " return x / norm(x)\n", 714 | "\n", 715 | "\n", 716 | "if __name__ == \"__main__\":\n", 717 | " # a little demo of how the functions in this module can be used:\n", 718 | " x1 = N.array([0, 1, 2])\n", 719 | " print(\"The norm of \" + str(x1) + \" is \" + str(norm(x1)) + \".\")\n", 720 | " print(\"The unitvector in direction of \" + str(x1) + \" is \" \\\n", 721 | " + str(unitvector(x1)) + \".\")" 722 | ] 723 | }, 724 | { 725 | "cell_type": "markdown", 726 | "metadata": {}, 727 | "source": [ 728 | "If this file is executed using `python vectools.py`, then `__name__==__main__` is true, and the output reads" 729 | ] 730 | }, 731 | { 732 | "cell_type": "code", 733 | "execution_count": 24, 734 | "metadata": {}, 735 | "outputs": [ 736 | { 737 | "name": "stdout", 738 | "output_type": "stream", 739 | "text": [ 740 | "The norm of [0 1 2] is 2.23606797749979.\r\n", 741 | "The unitvector in direction of [0 1 2] is [0. 0.4472136 0.89442719].\r\n" 742 | ] 743 | } 744 | ], 745 | "source": [ 746 | "!python3 vectools.py" 747 | ] 748 | }, 749 | { 750 | "cell_type": "markdown", 751 | "metadata": {}, 752 | "source": [ 753 | "If this file is imported (i.e. used as a module) into another python file or the python prompt or in the Jupyter Notebook, then `__name__==__main__` is false, and that statement block will not be executed." 754 | ] 755 | }, 756 | { 757 | "cell_type": "markdown", 758 | "metadata": {}, 759 | "source": [ 760 | "This is quite a common way to conditionally execute code in files providing library-like functions. The code that is executed if the file is run on its own, often consists of a series of tests (to check that the file’s functions carry out the right operations – *regression tests* or *unit tests* ), or some examples of how the library functions in the file can be used.\n", 761 | "\n", 762 | "### Example 2\n", 763 | "\n", 764 | "Even if a Python program is not intended to be used as a module file, it is good practice to always use a conditional main program:\n", 765 | "\n", 766 | "- often, it turns out later that functions in the file can be reused (and saves work then)\n", 767 | "\n", 768 | "- this is convenient for regression testing.\n", 769 | "\n", 770 | "Suppose an exercise is given to write a function that returns the first 5 prime numbers, and in addition to print them. (There is of course a trivial solution to this as we know the prime numbers, and we should imagine that the required calculation is more complex). One might be tempted to write" 771 | ] 772 | }, 773 | { 774 | "cell_type": "code", 775 | "execution_count": 25, 776 | "metadata": {}, 777 | "outputs": [ 778 | { 779 | "name": "stdout", 780 | "output_type": "stream", 781 | "text": [ 782 | "2 3 5 7 11 " 783 | ] 784 | } 785 | ], 786 | "source": [ 787 | "def primes5():\n", 788 | " return (2, 3, 5, 7, 11)\n", 789 | "\n", 790 | "for p in primes5():\n", 791 | " print(\"%d\" % p, end=' ')" 792 | ] 793 | }, 794 | { 795 | "cell_type": "markdown", 796 | "metadata": {}, 797 | "source": [ 798 | "It is better style to use a conditional main function, i.e.:" 799 | ] 800 | }, 801 | { 802 | "cell_type": "code", 803 | "execution_count": 26, 804 | "metadata": {}, 805 | "outputs": [ 806 | { 807 | "name": "stdout", 808 | "output_type": "stream", 809 | "text": [ 810 | "2 3 5 7 11 " 811 | ] 812 | } 813 | ], 814 | "source": [ 815 | "def primes5():\n", 816 | " return (2, 3, 5, 7, 11)\n", 817 | "\n", 818 | "if __name__==\"__main__\":\n", 819 | " for p in primes5():\n", 820 | " print(\"%d\" % p, end=' ')" 821 | ] 822 | }, 823 | { 824 | "cell_type": "markdown", 825 | "metadata": {}, 826 | "source": [ 827 | "A purist might argue that the following is even cleaner:" 828 | ] 829 | }, 830 | { 831 | "cell_type": "code", 832 | "execution_count": 27, 833 | "metadata": {}, 834 | "outputs": [ 835 | { 836 | "name": "stdout", 837 | "output_type": "stream", 838 | "text": [ 839 | "2 3 5 7 11 " 840 | ] 841 | } 842 | ], 843 | "source": [ 844 | "def primes5():\n", 845 | " return (2, 3, 5, 7, 11)\n", 846 | "\n", 847 | "def main():\n", 848 | " for p in primes5():\n", 849 | " print(\"%d\" % p, end=' ')\n", 850 | "\n", 851 | "if __name__==\"__main__\":\n", 852 | " main()" 853 | ] 854 | }, 855 | { 856 | "cell_type": "markdown", 857 | "metadata": {}, 858 | "source": [ 859 | "but either of the last two options is good.\n", 860 | "\n", 861 | "The example in [Many ways to compute a series](#Many-ways-to-compute-a-series) demonstrates this technique. Including functions with names starting with `test_` is compatible with the very useful py.test regression testing framework (see ).\n", 862 | "\n", 863 | "## Further Reading\n", 864 | "\n", 865 | "- [Python Tutorial Section 6](https://docs.python.org/3/tutorial/modules.html#modules)" 866 | ] 867 | } 868 | ], 869 | "metadata": { 870 | "anaconda-cloud": {}, 871 | "kernelspec": { 872 | "display_name": "Python 3", 873 | "language": "python", 874 | "name": "python3" 875 | }, 876 | "language_info": { 877 | "codemirror_mode": { 878 | "name": "ipython", 879 | "version": 3 880 | }, 881 | "file_extension": ".py", 882 | "mimetype": "text/x-python", 883 | "name": "python", 884 | "nbconvert_exporter": "python", 885 | "pygments_lexer": "ipython3", 886 | "version": "3.8.6-final" 887 | } 888 | }, 889 | "nbformat": 4, 890 | "nbformat_minor": 1 891 | } 892 | -------------------------------------------------------------------------------- /book/02-powerful-calculator.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A powerful calculator\n", 8 | "\n", 9 | "## Python prompt and Read-Eval-Print Loop (REPL)\n", 10 | "\n", 11 | "Python is an *interpreted* language. We can collect sequences of commands into text files and save this to file as a *Python program*. It is convention that these files have the file extension “`.py`”, for example `hello.py`.\n", 12 | "\n", 13 | "We can also enter individual commands at the Python prompt which are immediately evaluated and carried out by the Python interpreter. This is very useful for the programmer/learner to understand how to use certain commands (often before one puts these commands together in a longer Python program). Python’s role can be described as Reading the command, Evaluating it, Printing the evaluated value and repeating (Loop) the cycle – this is the origin of the REPL abbreviation.\n", 14 | "\n", 15 | "Python comes with a basic terminal prompt; you may see examples from this with `>>>` marking the input:\n", 16 | "\n", 17 | "\n", 18 | " >>> 2 + 2\n", 19 | " 4\n", 20 | "\n", 21 | "We are using a more powerful REPL interface, the Jupyter Notebook. Blocks of code appear with an `In` prompt next to them:" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 1, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "9" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "4 + 5" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "To edit the code, click inside the code area. You should get a green border around it. To run it, press Shift-Enter." 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "## Calculator\n", 56 | "\n", 57 | "Basic operations such as addition (`+`), subtraction (`-`), multiplication (`*`), division (`/`) and exponentiation (`**`) work (mostly) as expected:" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 2, 63 | "metadata": {}, 64 | "outputs": [ 65 | { 66 | "data": { 67 | "text/plain": [ 68 | "10010" 69 | ] 70 | }, 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "output_type": "execute_result" 74 | } 75 | ], 76 | "source": [ 77 | "10 + 10000" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 3, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "data": { 87 | "text/plain": [ 88 | "40.5" 89 | ] 90 | }, 91 | "execution_count": 3, 92 | "metadata": {}, 93 | "output_type": "execute_result" 94 | } 95 | ], 96 | "source": [ 97 | "42 - 1.5" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 4, 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "data": { 107 | "text/plain": [ 108 | "517" 109 | ] 110 | }, 111 | "execution_count": 4, 112 | "metadata": {}, 113 | "output_type": "execute_result" 114 | } 115 | ], 116 | "source": [ 117 | "47 * 11" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 5, 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "data": { 127 | "text/plain": [ 128 | "20.0" 129 | ] 130 | }, 131 | "execution_count": 5, 132 | "metadata": {}, 133 | "output_type": "execute_result" 134 | } 135 | ], 136 | "source": [ 137 | "10 / 0.5" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": 6, 143 | "metadata": {}, 144 | "outputs": [ 145 | { 146 | "data": { 147 | "text/plain": [ 148 | "4" 149 | ] 150 | }, 151 | "execution_count": 6, 152 | "metadata": {}, 153 | "output_type": "execute_result" 154 | } 155 | ], 156 | "source": [ 157 | "2**2 # Exponentiation ('to the power of') is **, NOT ^" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 7, 163 | "metadata": {}, 164 | "outputs": [ 165 | { 166 | "data": { 167 | "text/plain": [ 168 | "8" 169 | ] 170 | }, 171 | "execution_count": 7, 172 | "metadata": {}, 173 | "output_type": "execute_result" 174 | } 175 | ], 176 | "source": [ 177 | "2**3" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 8, 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "data": { 187 | "text/plain": [ 188 | "16" 189 | ] 190 | }, 191 | "execution_count": 8, 192 | "metadata": {}, 193 | "output_type": "execute_result" 194 | } 195 | ], 196 | "source": [ 197 | "2**4" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 9, 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "data": { 207 | "text/plain": [ 208 | "4" 209 | ] 210 | }, 211 | "execution_count": 9, 212 | "metadata": {}, 213 | "output_type": "execute_result" 214 | } 215 | ], 216 | "source": [ 217 | "2 + 2" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 10, 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "data": { 227 | "text/plain": [ 228 | "4" 229 | ] 230 | }, 231 | "execution_count": 10, 232 | "metadata": {}, 233 | "output_type": "execute_result" 234 | } 235 | ], 236 | "source": [ 237 | "# This is a comment\n", 238 | "2 + 2" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 11, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "data": { 248 | "text/plain": [ 249 | "4" 250 | ] 251 | }, 252 | "execution_count": 11, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "2 + 2 # and a comment on the same line as code" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "and, using the fact that $\\sqrt[n]{x} = x^{1/n}$, we can compute the $\\sqrt{3} = 1.732050\\dots$ using `**`:" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 12, 271 | "metadata": {}, 272 | "outputs": [ 273 | { 274 | "data": { 275 | "text/plain": [ 276 | "1.7320508075688772" 277 | ] 278 | }, 279 | "execution_count": 12, 280 | "metadata": {}, 281 | "output_type": "execute_result" 282 | } 283 | ], 284 | "source": [ 285 | "3**0.5" 286 | ] 287 | }, 288 | { 289 | "cell_type": "markdown", 290 | "metadata": {}, 291 | "source": [ 292 | "Parenthesis can be used for grouping:" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 13, 298 | "metadata": {}, 299 | "outputs": [ 300 | { 301 | "data": { 302 | "text/plain": [ 303 | "25" 304 | ] 305 | }, 306 | "execution_count": 13, 307 | "metadata": {}, 308 | "output_type": "execute_result" 309 | } 310 | ], 311 | "source": [ 312 | "2 * 10 + 5" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 14, 318 | "metadata": {}, 319 | "outputs": [ 320 | { 321 | "data": { 322 | "text/plain": [ 323 | "30" 324 | ] 325 | }, 326 | "execution_count": 14, 327 | "metadata": {}, 328 | "output_type": "execute_result" 329 | } 330 | ], 331 | "source": [ 332 | "2 * (10 + 5)" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "metadata": {}, 338 | "source": [ 339 | "## Integer division\n", 340 | "\n", 341 | "In Python 3, division works as you'd expect:" 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 15, 347 | "metadata": {}, 348 | "outputs": [ 349 | { 350 | "data": { 351 | "text/plain": [ 352 | "2.5" 353 | ] 354 | }, 355 | "execution_count": 15, 356 | "metadata": {}, 357 | "output_type": "execute_result" 358 | } 359 | ], 360 | "source": [ 361 | "15/6" 362 | ] 363 | }, 364 | { 365 | "cell_type": "markdown", 366 | "metadata": {}, 367 | "source": [ 368 | "In Python 2, however, `15/6` will give you `2`.\n", 369 | "\n", 370 | "\n", 371 | "This phenomenon is known (in many programming languages, including C) as *integer division*: because we provide two integer numbers (`15` and `6`) to the division operator (`/`), the assumption is that we seek a return value of type integer. The mathematically correct answer is (the floating point number) 2.5. (→ numerical data types in [Chapter 13](13-numeric-computation.ipynb).)\n", 372 | "\n", 373 | "The convention for integer division is to truncate the fractional digits and to return the integer part only (i.e. `2` in this example). It is also called “floor division”.\n", 374 | "\n", 375 | "### How to avoid integer division\n", 376 | "\n", 377 | "There are two ways to avoid the problem of integer division:\n", 378 | "\n", 379 | "1. Use Python 3 style division: this is available even in Python 2 with a special import statement:\n", 380 | "\n", 381 | " ```python\n", 382 | " >>> from __future__ import division\n", 383 | " >>> 15/6\n", 384 | " 2.5\n", 385 | " ```\n", 386 | "\n", 387 | " If you want to use the `from __future__ import division` feature in a python program, it would normally be included at the beginning of the file.\n", 388 | "\n", 389 | "2. Alternatively, if we ensure that at least one number (numerator or denominator) is of type float (or complex), the division operator will return a floating point number. This can be done by writing `15.` instead of `15`, of by forcing conversion of the number to a float, i.e. use `float(15)` instead of `15`:\n", 390 | "\n", 391 | " ```python\n", 392 | " >>> 15./6\n", 393 | " 2.5\n", 394 | " >>> float(15)/6\n", 395 | " 2.5\n", 396 | " >>> 15/6.\n", 397 | " 2.5\n", 398 | " >>> 15/float(6)\n", 399 | " 2.5\n", 400 | " >>> 15./6.\n", 401 | " 2.5\n", 402 | " ```\n", 403 | "\n", 404 | "If we really want integer division, we can use `//`: `1//2` returns 0, in both Python 2 and 3." 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "metadata": {}, 410 | "source": [ 411 | "### Why should I care about this division problem?\n", 412 | "\n", 413 | "Integer division can result in surprising bugs: suppose you are writing code to compute the mean value *m* = (*x* + *y*)/2 of two numbers *x* and *y*. The first attempt of writing this may read:\n", 414 | "\n", 415 | "```python\n", 416 | "m = (x + y) / 2\n", 417 | "```\n", 418 | "\n", 419 | "Suppose this is tested with *x* = 0.5, *y* = 0.5, then the line above computes the correct answers *m* = 0.5 (because`0.5 + 0.5 = 1.0`, i.e. a 1.0 is a floating point number, and thus `1.0/2` evaluates to `0.5`). Or we could use *x* = 10, *y* = 30, and because `10 + 30 = 40` and `40/2` evaluates to `20`, we get the correct answer *m* = 20. However, if the integers *x* = 0 and *y* = 1 would come up, then the code returns *m* = 0 (because `0 + 1 = 1` and `1/2` evaluates to `0`) whereas *m* = 0.5 would have been the right answer.\n", 420 | "\n", 421 | "We have many possibilities to change the line of code above to work safely, including these three versions:\n", 422 | "\n", 423 | "```python\n", 424 | "m = (x + y) / 2.0\n", 425 | "\n", 426 | "m = float(x + y) / 2\n", 427 | "\n", 428 | "m = (x + y) * 0.5\n", 429 | "```\n", 430 | "\n", 431 | "This integer division behaviour is common amongst most programming languages (including the important ones C, C++ and Fortran), and it is important to be aware of the issue.\n", 432 | "\n", 433 | "## Mathematical functions\n", 434 | "\n", 435 | "Because Python is a general purpose programming language, commonly used mathematical functions such as sin, cos, exp, log and many others are located in the mathematics module with name `math`. We can make use of this as soon as we *import* the math module:" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 16, 441 | "metadata": {}, 442 | "outputs": [ 443 | { 444 | "data": { 445 | "text/plain": [ 446 | "2.718281828459045" 447 | ] 448 | }, 449 | "execution_count": 16, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "import math\n", 456 | "math.exp(1.0)" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "metadata": {}, 462 | "source": [ 463 | "Using the `dir` function, we can see the directory of objects available in the math module:" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 17, 469 | "metadata": {}, 470 | "outputs": [ 471 | { 472 | "data": { 473 | "text/plain": [ 474 | "['__doc__',\n", 475 | " '__file__',\n", 476 | " '__loader__',\n", 477 | " '__name__',\n", 478 | " '__package__',\n", 479 | " '__spec__',\n", 480 | " 'acos',\n", 481 | " 'acosh',\n", 482 | " 'asin',\n", 483 | " 'asinh',\n", 484 | " 'atan',\n", 485 | " 'atan2',\n", 486 | " 'atanh',\n", 487 | " 'ceil',\n", 488 | " 'copysign',\n", 489 | " 'cos',\n", 490 | " 'cosh',\n", 491 | " 'degrees',\n", 492 | " 'e',\n", 493 | " 'erf',\n", 494 | " 'erfc',\n", 495 | " 'exp',\n", 496 | " 'expm1',\n", 497 | " 'fabs',\n", 498 | " 'factorial',\n", 499 | " 'floor',\n", 500 | " 'fmod',\n", 501 | " 'frexp',\n", 502 | " 'fsum',\n", 503 | " 'gamma',\n", 504 | " 'gcd',\n", 505 | " 'hypot',\n", 506 | " 'inf',\n", 507 | " 'isclose',\n", 508 | " 'isfinite',\n", 509 | " 'isinf',\n", 510 | " 'isnan',\n", 511 | " 'ldexp',\n", 512 | " 'lgamma',\n", 513 | " 'log',\n", 514 | " 'log10',\n", 515 | " 'log1p',\n", 516 | " 'log2',\n", 517 | " 'modf',\n", 518 | " 'nan',\n", 519 | " 'pi',\n", 520 | " 'pow',\n", 521 | " 'radians',\n", 522 | " 'sin',\n", 523 | " 'sinh',\n", 524 | " 'sqrt',\n", 525 | " 'tan',\n", 526 | " 'tanh',\n", 527 | " 'tau',\n", 528 | " 'trunc']" 529 | ] 530 | }, 531 | "execution_count": 17, 532 | "metadata": {}, 533 | "output_type": "execute_result" 534 | } 535 | ], 536 | "source": [ 537 | "# NBVAL_IGNORE_OUTPUT\n", 538 | "dir(math)" 539 | ] 540 | }, 541 | { 542 | "cell_type": "markdown", 543 | "metadata": {}, 544 | "source": [ 545 | "As usual, the `help` function can provide more information about the module (`help(math)`) on individual objects:" 546 | ] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "execution_count": 18, 551 | "metadata": {}, 552 | "outputs": [ 553 | { 554 | "name": "stdout", 555 | "output_type": "stream", 556 | "text": [ 557 | "Help on built-in function exp in module math:\n", 558 | "\n", 559 | "exp(...)\n", 560 | " exp(x)\n", 561 | " \n", 562 | " Return e raised to the power of x.\n", 563 | "\n" 564 | ] 565 | } 566 | ], 567 | "source": [ 568 | "# NBVAL_IGNORE_OUTPUT\n", 569 | "help(math.exp)" 570 | ] 571 | }, 572 | { 573 | "cell_type": "markdown", 574 | "metadata": {}, 575 | "source": [ 576 | "The mathematics module defines to constants *π* and *e*:" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": 19, 582 | "metadata": {}, 583 | "outputs": [ 584 | { 585 | "data": { 586 | "text/plain": [ 587 | "3.141592653589793" 588 | ] 589 | }, 590 | "execution_count": 19, 591 | "metadata": {}, 592 | "output_type": "execute_result" 593 | } 594 | ], 595 | "source": [ 596 | "math.pi" 597 | ] 598 | }, 599 | { 600 | "cell_type": "code", 601 | "execution_count": 20, 602 | "metadata": {}, 603 | "outputs": [ 604 | { 605 | "data": { 606 | "text/plain": [ 607 | "2.718281828459045" 608 | ] 609 | }, 610 | "execution_count": 20, 611 | "metadata": {}, 612 | "output_type": "execute_result" 613 | } 614 | ], 615 | "source": [ 616 | "math.e" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": 21, 622 | "metadata": {}, 623 | "outputs": [ 624 | { 625 | "data": { 626 | "text/plain": [ 627 | "-1.0" 628 | ] 629 | }, 630 | "execution_count": 21, 631 | "metadata": {}, 632 | "output_type": "execute_result" 633 | } 634 | ], 635 | "source": [ 636 | "math.cos(math.pi)" 637 | ] 638 | }, 639 | { 640 | "cell_type": "code", 641 | "execution_count": 22, 642 | "metadata": {}, 643 | "outputs": [ 644 | { 645 | "data": { 646 | "text/plain": [ 647 | "1.0" 648 | ] 649 | }, 650 | "execution_count": 22, 651 | "metadata": {}, 652 | "output_type": "execute_result" 653 | } 654 | ], 655 | "source": [ 656 | "math.log(math.e)" 657 | ] 658 | }, 659 | { 660 | "cell_type": "markdown", 661 | "metadata": {}, 662 | "source": [ 663 | "## Variables\n", 664 | "\n", 665 | "A *variable* can be used to store a certain value or object. In Python, all numbers (and everything else, including functions, modules and files) are objects. A variable is created through assignement:" 666 | ] 667 | }, 668 | { 669 | "cell_type": "code", 670 | "execution_count": 23, 671 | "metadata": { 672 | "collapsed": true, 673 | "jupyter": { 674 | "outputs_hidden": true 675 | } 676 | }, 677 | "outputs": [], 678 | "source": [ 679 | "x = 0.5" 680 | ] 681 | }, 682 | { 683 | "cell_type": "markdown", 684 | "metadata": {}, 685 | "source": [ 686 | "Once the variable `x` has been created through assignment of 0.5 in this example, we can make use of it:" 687 | ] 688 | }, 689 | { 690 | "cell_type": "code", 691 | "execution_count": 24, 692 | "metadata": {}, 693 | "outputs": [ 694 | { 695 | "data": { 696 | "text/plain": [ 697 | "1.5" 698 | ] 699 | }, 700 | "execution_count": 24, 701 | "metadata": {}, 702 | "output_type": "execute_result" 703 | } 704 | ], 705 | "source": [ 706 | "x*3" 707 | ] 708 | }, 709 | { 710 | "cell_type": "code", 711 | "execution_count": 25, 712 | "metadata": {}, 713 | "outputs": [ 714 | { 715 | "data": { 716 | "text/plain": [ 717 | "0.25" 718 | ] 719 | }, 720 | "execution_count": 25, 721 | "metadata": {}, 722 | "output_type": "execute_result" 723 | } 724 | ], 725 | "source": [ 726 | "x**2" 727 | ] 728 | }, 729 | { 730 | "cell_type": "code", 731 | "execution_count": 26, 732 | "metadata": {}, 733 | "outputs": [ 734 | { 735 | "data": { 736 | "text/plain": [ 737 | "333" 738 | ] 739 | }, 740 | "execution_count": 26, 741 | "metadata": {}, 742 | "output_type": "execute_result" 743 | } 744 | ], 745 | "source": [ 746 | "y = 111\n", 747 | "y + 222" 748 | ] 749 | }, 750 | { 751 | "cell_type": "markdown", 752 | "metadata": {}, 753 | "source": [ 754 | "A variable is overriden if a new value is assigned:" 755 | ] 756 | }, 757 | { 758 | "cell_type": "code", 759 | "execution_count": 27, 760 | "metadata": {}, 761 | "outputs": [ 762 | { 763 | "data": { 764 | "text/plain": [ 765 | "1.0" 766 | ] 767 | }, 768 | "execution_count": 27, 769 | "metadata": {}, 770 | "output_type": "execute_result" 771 | } 772 | ], 773 | "source": [ 774 | "y = 0.7\n", 775 | "math.sin(y) ** 2 + math.cos(y) ** 2" 776 | ] 777 | }, 778 | { 779 | "cell_type": "markdown", 780 | "metadata": {}, 781 | "source": [ 782 | "The equal sign (’=’) is used to assign a value to a variable." 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": 28, 788 | "metadata": {}, 789 | "outputs": [ 790 | { 791 | "data": { 792 | "text/plain": [ 793 | "900" 794 | ] 795 | }, 796 | "execution_count": 28, 797 | "metadata": {}, 798 | "output_type": "execute_result" 799 | } 800 | ], 801 | "source": [ 802 | "width = 20\n", 803 | "height = 5 * 9\n", 804 | "width * height" 805 | ] 806 | }, 807 | { 808 | "cell_type": "markdown", 809 | "metadata": {}, 810 | "source": [ 811 | "A value can be assigned to several variables simultaneously:" 812 | ] 813 | }, 814 | { 815 | "cell_type": "code", 816 | "execution_count": 29, 817 | "metadata": {}, 818 | "outputs": [ 819 | { 820 | "data": { 821 | "text/plain": [ 822 | "0" 823 | ] 824 | }, 825 | "execution_count": 29, 826 | "metadata": {}, 827 | "output_type": "execute_result" 828 | } 829 | ], 830 | "source": [ 831 | "x = y = z = 0 # initialise x, y and z with 0\n", 832 | "x" 833 | ] 834 | }, 835 | { 836 | "cell_type": "code", 837 | "execution_count": 30, 838 | "metadata": {}, 839 | "outputs": [ 840 | { 841 | "data": { 842 | "text/plain": [ 843 | "0" 844 | ] 845 | }, 846 | "execution_count": 30, 847 | "metadata": {}, 848 | "output_type": "execute_result" 849 | } 850 | ], 851 | "source": [ 852 | "y" 853 | ] 854 | }, 855 | { 856 | "cell_type": "code", 857 | "execution_count": 31, 858 | "metadata": {}, 859 | "outputs": [ 860 | { 861 | "data": { 862 | "text/plain": [ 863 | "0" 864 | ] 865 | }, 866 | "execution_count": 31, 867 | "metadata": {}, 868 | "output_type": "execute_result" 869 | } 870 | ], 871 | "source": [ 872 | "z" 873 | ] 874 | }, 875 | { 876 | "cell_type": "markdown", 877 | "metadata": {}, 878 | "source": [ 879 | "Variables must be created (assigned a value) before they can be used, or an error will occur:" 880 | ] 881 | }, 882 | { 883 | "cell_type": "code", 884 | "execution_count": 32, 885 | "metadata": { 886 | "tags": [ 887 | "raises-exception" 888 | ] 889 | }, 890 | "outputs": [ 891 | { 892 | "ename": "NameError", 893 | "evalue": "name 'n' is not defined", 894 | "output_type": "error", 895 | "traceback": [ 896 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 897 | "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", 898 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# NBVAL_SKIP\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;31m# try to access an undefined variable:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 899 | "\u001b[0;31mNameError\u001b[0m: name 'n' is not defined" 900 | ] 901 | } 902 | ], 903 | "source": [ 904 | "# NBVAL_RAISES_EXCEPTION\n", 905 | "# try to access an undefined variable:\n", 906 | "n" 907 | ] 908 | }, 909 | { 910 | "cell_type": "markdown", 911 | "metadata": {}, 912 | "source": [ 913 | "In interactive mode, the last printed expression is assigned to the variable `_`. This means that when you are using Python as a desk calculator, it is somewhat easier to continue calculations, for example:" 914 | ] 915 | }, 916 | { 917 | "cell_type": "code", 918 | "execution_count": null, 919 | "metadata": {}, 920 | "outputs": [], 921 | "source": [ 922 | "tax = 12.5 / 100\n", 923 | "price = 100.50\n", 924 | "price * tax" 925 | ] 926 | }, 927 | { 928 | "cell_type": "code", 929 | "execution_count": null, 930 | "metadata": {}, 931 | "outputs": [], 932 | "source": [ 933 | "price + _" 934 | ] 935 | }, 936 | { 937 | "cell_type": "markdown", 938 | "metadata": {}, 939 | "source": [ 940 | "This variable should be treated as read-only by the user. Don’t explicitly assign a value to it — you would create an independent local variable with the same name masking the built-in variable with its magic behavior.\n", 941 | "\n", 942 | "### Terminology\n", 943 | "\n", 944 | "Strictly speaking, the following happens when we write" 945 | ] 946 | }, 947 | { 948 | "cell_type": "code", 949 | "execution_count": null, 950 | "metadata": { 951 | "collapsed": true, 952 | "jupyter": { 953 | "outputs_hidden": true 954 | } 955 | }, 956 | "outputs": [], 957 | "source": [ 958 | "x = 0.5" 959 | ] 960 | }, 961 | { 962 | "cell_type": "markdown", 963 | "metadata": {}, 964 | "source": [ 965 | "First, Python creates the object `0.5`. Everything in Python is an object, and so is the floating point number 0.5. This object is stored somewhere in memory. Next, Python *binds a name to the object*. The name is `x`, and we often refer casually to `x` as a variable, an object, or even the value 0.5. However, technically, `x` is a name that is bound to the object `0.5`. Another way to say this is that `x` is a reference to the object.\n", 966 | "\n", 967 | "While it is often sufficient to think about assigning 0.5 to a variable x, there are situations where we need to remember what actually happens. In particular, when we pass references to objects to functions, we need to realise that the function may operate on the object (rather than a copy of the object). This is discussed in more detail in [the next chapter](03-data-types-structures.ipynb).\n", 968 | "\n", 969 | "## Impossible equations\n", 970 | "\n", 971 | "In computer programs we often find statements like" 972 | ] 973 | }, 974 | { 975 | "cell_type": "code", 976 | "execution_count": null, 977 | "metadata": { 978 | "collapsed": true, 979 | "jupyter": { 980 | "outputs_hidden": true 981 | } 982 | }, 983 | "outputs": [], 984 | "source": [ 985 | "x = x + 1" 986 | ] 987 | }, 988 | { 989 | "cell_type": "markdown", 990 | "metadata": {}, 991 | "source": [ 992 | "If we read this as an equation as we are use to from mathematics,\n", 993 | "*x* = *x* + 1\n", 994 | " we could subtract *x* on both sides, to find that\n", 995 | "0 = 1.\n", 996 | " We know this is not true, so something is wrong here.\n", 997 | "\n", 998 | "The answer is that “equations“ in computer codes are not equations but *assignments*. They always have to be read in the following way two-step way:\n", 999 | "\n", 1000 | "1. Evaluate the value on the right hand side of the equal sign\n", 1001 | "\n", 1002 | "2. Assign this value to the variable name shown on the left hand side. (In Python: bind the name on the left hand side to the object shown on the right hand side.)\n", 1003 | "\n", 1004 | "Some computer science literature uses the following notation to express assignments and to avoid the confusion with mathematical equations:\n", 1005 | "\n", 1006 | "$$x \\leftarrow x + 1$$\n", 1007 | "\n", 1008 | "Let’s apply our two-step rule to the assignment `x = x + 1` given above:\n", 1009 | "\n", 1010 | "1. Evaluate the value on the right hand side of the equal sign: for this we need to know what the current value of `x` is. Let’s assume `x` is currently `4`. In that case, the right hand side `x+1` evaluates to `5`.\n", 1011 | "\n", 1012 | "2. Assign this value (i.e. `5`) to the variable name shown on the left hand side `x`.\n", 1013 | "\n", 1014 | "Let’s confirm with the Python prompt that this is the correct interpretation:" 1015 | ] 1016 | }, 1017 | { 1018 | "cell_type": "code", 1019 | "execution_count": null, 1020 | "metadata": {}, 1021 | "outputs": [], 1022 | "source": [ 1023 | "x = 4 \n", 1024 | "x = x + 1\n", 1025 | "x" 1026 | ] 1027 | }, 1028 | { 1029 | "cell_type": "markdown", 1030 | "metadata": {}, 1031 | "source": [ 1032 | "### The `+=` notation\n", 1033 | "\n", 1034 | "Because it is a quite a common operation to increase a variable `x` by some fixed amount `c`, we can write\n", 1035 | "\n", 1036 | "```python\n", 1037 | "x += c\n", 1038 | "```\n", 1039 | "\n", 1040 | "instead of\n", 1041 | "\n", 1042 | "```python\n", 1043 | "x = x + c\n", 1044 | "```" 1045 | ] 1046 | }, 1047 | { 1048 | "cell_type": "markdown", 1049 | "metadata": {}, 1050 | "source": [ 1051 | "Our initial example above could thus have been written" 1052 | ] 1053 | }, 1054 | { 1055 | "cell_type": "code", 1056 | "execution_count": null, 1057 | "metadata": {}, 1058 | "outputs": [], 1059 | "source": [ 1060 | "x = 4\n", 1061 | "x += 1\n", 1062 | "x" 1063 | ] 1064 | }, 1065 | { 1066 | "cell_type": "markdown", 1067 | "metadata": {}, 1068 | "source": [ 1069 | "The same operators are defined for multiplication with a constant (`*=`), subtraction of a constant (`-=`) and division by a constant (`/=`).\n", 1070 | "\n", 1071 | "Note that the order of `+` and `=` matters:" 1072 | ] 1073 | }, 1074 | { 1075 | "cell_type": "code", 1076 | "execution_count": null, 1077 | "metadata": { 1078 | "collapsed": true, 1079 | "jupyter": { 1080 | "outputs_hidden": true 1081 | } 1082 | }, 1083 | "outputs": [], 1084 | "source": [ 1085 | "x += 1" 1086 | ] 1087 | }, 1088 | { 1089 | "cell_type": "markdown", 1090 | "metadata": {}, 1091 | "source": [ 1092 | "will increase the variable `x` by one where as" 1093 | ] 1094 | }, 1095 | { 1096 | "cell_type": "code", 1097 | "execution_count": null, 1098 | "metadata": { 1099 | "collapsed": true, 1100 | "jupyter": { 1101 | "outputs_hidden": true 1102 | } 1103 | }, 1104 | "outputs": [], 1105 | "source": [ 1106 | "x =+ 1" 1107 | ] 1108 | }, 1109 | { 1110 | "cell_type": "markdown", 1111 | "metadata": {}, 1112 | "source": [ 1113 | "will assign the value `+1` to the variable `x`." 1114 | ] 1115 | } 1116 | ], 1117 | "metadata": { 1118 | "kernelspec": { 1119 | "display_name": "Python 3", 1120 | "language": "python", 1121 | "name": "python3" 1122 | }, 1123 | "language_info": { 1124 | "codemirror_mode": { 1125 | "name": "ipython", 1126 | "version": 3 1127 | }, 1128 | "file_extension": ".py", 1129 | "mimetype": "text/x-python", 1130 | "name": "python", 1131 | "nbconvert_exporter": "python", 1132 | "pygments_lexer": "ipython3", 1133 | "version": "3.8.6" 1134 | } 1135 | }, 1136 | "nbformat": 4, 1137 | "nbformat_minor": 4 1138 | } -------------------------------------------------------------------------------- /book/04-introspection.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introspection\n", 8 | "\n", 9 | "A Python code can ask and answer questions about itself and the objects it is manipulating.\n", 10 | "\n", 11 | "## dir\n", 12 | "\n", 13 | "`dir()` is a built-in function which returns a list of all the names belonging to some namespace.\n", 14 | "\n", 15 | "- If no arguments are passed to dir (i.e. `dir()`), it inspects the namespace in which it was called.\n", 16 | "\n", 17 | "- If `dir` is given an argument (i.e. `dir()`, then it inspects the namespace of the object which it was passed.\n", 18 | "\n", 19 | "For example:" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "['__add__',\n", 31 | " '__class__',\n", 32 | " '__contains__',\n", 33 | " '__delattr__',\n", 34 | " '__delitem__',\n", 35 | " '__dir__',\n", 36 | " '__doc__',\n", 37 | " '__eq__',\n", 38 | " '__format__',\n", 39 | " '__ge__',\n", 40 | " '__getattribute__',\n", 41 | " '__getitem__',\n", 42 | " '__gt__',\n", 43 | " '__hash__',\n", 44 | " '__iadd__',\n", 45 | " '__imul__',\n", 46 | " '__init__',\n", 47 | " '__init_subclass__',\n", 48 | " '__iter__',\n", 49 | " '__le__',\n", 50 | " '__len__',\n", 51 | " '__lt__',\n", 52 | " '__mul__',\n", 53 | " '__ne__',\n", 54 | " '__new__',\n", 55 | " '__reduce__',\n", 56 | " '__reduce_ex__',\n", 57 | " '__repr__',\n", 58 | " '__reversed__',\n", 59 | " '__rmul__',\n", 60 | " '__setattr__',\n", 61 | " '__setitem__',\n", 62 | " '__sizeof__',\n", 63 | " '__str__',\n", 64 | " '__subclasshook__',\n", 65 | " 'append',\n", 66 | " 'clear',\n", 67 | " 'copy',\n", 68 | " 'count',\n", 69 | " 'extend',\n", 70 | " 'index',\n", 71 | " 'insert',\n", 72 | " 'pop',\n", 73 | " 'remove',\n", 74 | " 'reverse',\n", 75 | " 'sort']" 76 | ] 77 | }, 78 | "execution_count": 1, 79 | "metadata": {}, 80 | "output_type": "execute_result" 81 | } 82 | ], 83 | "source": [ 84 | "# NBVAL_IGNORE_OUTPUT\n", 85 | "apples = ['Cox', 'Braeburn', 'Jazz']\n", 86 | "dir(apples)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 2, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | "['In',\n", 98 | " 'Out',\n", 99 | " '_',\n", 100 | " '_1',\n", 101 | " '__',\n", 102 | " '___',\n", 103 | " '__builtin__',\n", 104 | " '__builtins__',\n", 105 | " '__doc__',\n", 106 | " '__loader__',\n", 107 | " '__name__',\n", 108 | " '__package__',\n", 109 | " '__spec__',\n", 110 | " '_dh',\n", 111 | " '_i',\n", 112 | " '_i1',\n", 113 | " '_i2',\n", 114 | " '_ih',\n", 115 | " '_ii',\n", 116 | " '_iii',\n", 117 | " '_oh',\n", 118 | " 'apples',\n", 119 | " 'exit',\n", 120 | " 'get_ipython',\n", 121 | " 'quit']" 122 | ] 123 | }, 124 | "execution_count": 2, 125 | "metadata": {}, 126 | "output_type": "execute_result" 127 | } 128 | ], 129 | "source": [ 130 | "# NBVAL_IGNORE_OUTPUT\n", 131 | "dir()" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 3, 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "data": { 141 | "text/plain": [ 142 | "['__add__',\n", 143 | " '__class__',\n", 144 | " '__contains__',\n", 145 | " '__delattr__',\n", 146 | " '__dir__',\n", 147 | " '__doc__',\n", 148 | " '__eq__',\n", 149 | " '__format__',\n", 150 | " '__ge__',\n", 151 | " '__getattribute__',\n", 152 | " '__getitem__',\n", 153 | " '__getnewargs__',\n", 154 | " '__gt__',\n", 155 | " '__hash__',\n", 156 | " '__init__',\n", 157 | " '__init_subclass__',\n", 158 | " '__iter__',\n", 159 | " '__le__',\n", 160 | " '__len__',\n", 161 | " '__lt__',\n", 162 | " '__mod__',\n", 163 | " '__mul__',\n", 164 | " '__ne__',\n", 165 | " '__new__',\n", 166 | " '__reduce__',\n", 167 | " '__reduce_ex__',\n", 168 | " '__repr__',\n", 169 | " '__rmod__',\n", 170 | " '__rmul__',\n", 171 | " '__setattr__',\n", 172 | " '__sizeof__',\n", 173 | " '__str__',\n", 174 | " '__subclasshook__',\n", 175 | " 'capitalize',\n", 176 | " 'casefold',\n", 177 | " 'center',\n", 178 | " 'count',\n", 179 | " 'encode',\n", 180 | " 'endswith',\n", 181 | " 'expandtabs',\n", 182 | " 'find',\n", 183 | " 'format',\n", 184 | " 'format_map',\n", 185 | " 'index',\n", 186 | " 'isalnum',\n", 187 | " 'isalpha',\n", 188 | " 'isdecimal',\n", 189 | " 'isdigit',\n", 190 | " 'isidentifier',\n", 191 | " 'islower',\n", 192 | " 'isnumeric',\n", 193 | " 'isprintable',\n", 194 | " 'isspace',\n", 195 | " 'istitle',\n", 196 | " 'isupper',\n", 197 | " 'join',\n", 198 | " 'ljust',\n", 199 | " 'lower',\n", 200 | " 'lstrip',\n", 201 | " 'maketrans',\n", 202 | " 'partition',\n", 203 | " 'replace',\n", 204 | " 'rfind',\n", 205 | " 'rindex',\n", 206 | " 'rjust',\n", 207 | " 'rpartition',\n", 208 | " 'rsplit',\n", 209 | " 'rstrip',\n", 210 | " 'split',\n", 211 | " 'splitlines',\n", 212 | " 'startswith',\n", 213 | " 'strip',\n", 214 | " 'swapcase',\n", 215 | " 'title',\n", 216 | " 'translate',\n", 217 | " 'upper',\n", 218 | " 'zfill']" 219 | ] 220 | }, 221 | "execution_count": 3, 222 | "metadata": {}, 223 | "output_type": "execute_result" 224 | } 225 | ], 226 | "source": [ 227 | "# NBVAL_IGNORE_OUTPUT\n", 228 | "name = \"Peter\"\n", 229 | "dir(name)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "### Magic names\n", 237 | "\n", 238 | "You will find many names which both start and end with a double underscore (e.g. `__name__`). These are called magic names. Functions with magic names provide the implementation of particular python functionality.\n", 239 | "\n", 240 | "For example, the application of the `str` to an object `a`, i.e. `str(a)`, will – internally – result in the method `a.__str__()` being called. This method `__str__` generally needs to return a string. The idea is that the `__str__()` method should be defined for all objects (including those that derive from new classes that a programmer may create) so that all objects (independent of their type or class) can be printed using the `str()` function. The actual conversion of some object `x` to the string is then done via the object specific method `x.__str__()`.\n", 241 | "\n", 242 | "We can demonstrate this by creating a class `my_int` which inherits from the Python’s integer base class, and overrides the `__str__` method. (It requires more Python knowledge than provided up to this point in the text to be able to understand this example.)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 4, 248 | "metadata": {}, 249 | "outputs": [ 250 | { 251 | "name": "stdout", 252 | "output_type": "stream", 253 | "text": [ 254 | "a * b = 12\n", 255 | "Type a = str(a) = my_int: 3\n", 256 | "Type b = str(b) = 4\n" 257 | ] 258 | } 259 | ], 260 | "source": [ 261 | "class my_int(int): \n", 262 | " \"\"\"Inherited from int\"\"\" \n", 263 | " def __str__(self): \n", 264 | " \"\"\"Tailored str representation of my int\"\"\" \n", 265 | " return \"my_int: %s\" % (int.__str__(self))\n", 266 | " \n", 267 | "a = my_int(3)\n", 268 | "b = int(4) # equivalent to b = 4\n", 269 | "print(\"a * b = \", a * b)\n", 270 | "print(\"Type a = \", type(a), \"str(a) = \", str(a))\n", 271 | "print(\"Type b = \", type(b), \"str(b) = \", str(b))" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "**Further Reading**\n", 279 | "\n", 280 | "See → [Python documentation, Data Model](https://docs.python.org/3/reference/datamodel.html)\n", 281 | "\n", 282 | "## type\n", 283 | "\n", 284 | "The `type()`command returns the type of an object:" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 5, 290 | "metadata": {}, 291 | "outputs": [ 292 | { 293 | "data": { 294 | "text/plain": [ 295 | "int" 296 | ] 297 | }, 298 | "execution_count": 5, 299 | "metadata": {}, 300 | "output_type": "execute_result" 301 | } 302 | ], 303 | "source": [ 304 | "type(1)" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 6, 310 | "metadata": {}, 311 | "outputs": [ 312 | { 313 | "data": { 314 | "text/plain": [ 315 | "float" 316 | ] 317 | }, 318 | "execution_count": 6, 319 | "metadata": {}, 320 | "output_type": "execute_result" 321 | } 322 | ], 323 | "source": [ 324 | "type(1.0)" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": 7, 330 | "metadata": {}, 331 | "outputs": [ 332 | { 333 | "data": { 334 | "text/plain": [ 335 | "str" 336 | ] 337 | }, 338 | "execution_count": 7, 339 | "metadata": {}, 340 | "output_type": "execute_result" 341 | } 342 | ], 343 | "source": [ 344 | "type(\"Python\")" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": 8, 350 | "metadata": {}, 351 | "outputs": [ 352 | { 353 | "data": { 354 | "text/plain": [ 355 | "module" 356 | ] 357 | }, 358 | "execution_count": 8, 359 | "metadata": {}, 360 | "output_type": "execute_result" 361 | } 362 | ], 363 | "source": [ 364 | "import math\n", 365 | "type(math)" 366 | ] 367 | }, 368 | { 369 | "cell_type": "code", 370 | "execution_count": 9, 371 | "metadata": {}, 372 | "outputs": [ 373 | { 374 | "data": { 375 | "text/plain": [ 376 | "builtin_function_or_method" 377 | ] 378 | }, 379 | "execution_count": 9, 380 | "metadata": {}, 381 | "output_type": "execute_result" 382 | } 383 | ], 384 | "source": [ 385 | "type(math.sin)" 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "metadata": {}, 391 | "source": [ 392 | "## isinstance\n", 393 | "\n", 394 | "`isinstance(, )` returns True if the given object is an instance of the given type, or any of its superclasses. Use `help(isinstance)` for the full syntax." 395 | ] 396 | }, 397 | { 398 | "cell_type": "code", 399 | "execution_count": 10, 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "data": { 404 | "text/plain": [ 405 | "True" 406 | ] 407 | }, 408 | "execution_count": 10, 409 | "metadata": {}, 410 | "output_type": "execute_result" 411 | } 412 | ], 413 | "source": [ 414 | "isinstance(2,int)" 415 | ] 416 | }, 417 | { 418 | "cell_type": "code", 419 | "execution_count": 11, 420 | "metadata": {}, 421 | "outputs": [ 422 | { 423 | "data": { 424 | "text/plain": [ 425 | "False" 426 | ] 427 | }, 428 | "execution_count": 11, 429 | "metadata": {}, 430 | "output_type": "execute_result" 431 | } 432 | ], 433 | "source": [ 434 | "isinstance(2.,int)" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": 12, 440 | "metadata": {}, 441 | "outputs": [ 442 | { 443 | "data": { 444 | "text/plain": [ 445 | "True" 446 | ] 447 | }, 448 | "execution_count": 12, 449 | "metadata": {}, 450 | "output_type": "execute_result" 451 | } 452 | ], 453 | "source": [ 454 | "isinstance(a,int) # a is an instance of my_int" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": 13, 460 | "metadata": {}, 461 | "outputs": [ 462 | { 463 | "data": { 464 | "text/plain": [ 465 | "__main__.my_int" 466 | ] 467 | }, 468 | "execution_count": 13, 469 | "metadata": {}, 470 | "output_type": "execute_result" 471 | } 472 | ], 473 | "source": [ 474 | "type(a)" 475 | ] 476 | }, 477 | { 478 | "cell_type": "markdown", 479 | "metadata": {}, 480 | "source": [ 481 | "## help\n", 482 | "\n", 483 | "- The `help()` function will report the docstring (magic attritube with name `__doc__`) of the object that it is given, sometimes complemented with additional information. In the case of functions, `help` will also show the list of arguments that the function accepts (but it cannot provide the return value).\n", 484 | "\n", 485 | "- `help()` starts an interactive help environment.\n", 486 | "\n", 487 | "- It is common to use the `help` command a lot to remind oneself of the syntax and semantic of commands.\n", 488 | "\n", 489 | "" 490 | ] 491 | }, 492 | { 493 | "cell_type": "code", 494 | "execution_count": 14, 495 | "metadata": {}, 496 | "outputs": [ 497 | { 498 | "name": "stdout", 499 | "output_type": "stream", 500 | "text": [ 501 | "Help on built-in function isinstance in module builtins:\n", 502 | "\n", 503 | "isinstance(obj, class_or_tuple, /)\n", 504 | " Return whether an object is an instance of a class or of a subclass thereof.\n", 505 | " \n", 506 | " A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to\n", 507 | " check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)\n", 508 | " or ...`` etc.\n", 509 | "\n" 510 | ] 511 | } 512 | ], 513 | "source": [ 514 | "help(isinstance)" 515 | ] 516 | }, 517 | { 518 | "cell_type": "code", 519 | "execution_count": 15, 520 | "metadata": {}, 521 | "outputs": [ 522 | { 523 | "name": "stdout", 524 | "output_type": "stream", 525 | "text": [ 526 | "Help on built-in function sin in module math:\n", 527 | "\n", 528 | "sin(...)\n", 529 | " sin(x)\n", 530 | " \n", 531 | " Return the sine of x (measured in radians).\n", 532 | "\n" 533 | ] 534 | } 535 | ], 536 | "source": [ 537 | "# NBVAL_IGNORE_OUTPUT\n", 538 | "import math\n", 539 | "help(math.sin)" 540 | ] 541 | }, 542 | { 543 | "cell_type": "code", 544 | "execution_count": 16, 545 | "metadata": {}, 546 | "outputs": [ 547 | { 548 | "name": "stdout", 549 | "output_type": "stream", 550 | "text": [ 551 | "Help on module math:\n", 552 | "\n", 553 | "NAME\n", 554 | " math\n", 555 | "\n", 556 | "MODULE REFERENCE\n", 557 | " https://docs.python.org/3.6/library/math\n", 558 | " \n", 559 | " The following documentation is automatically generated from the Python\n", 560 | " source files. It may be incomplete, incorrect or include features that\n", 561 | " are considered implementation detail and may vary between Python\n", 562 | " implementations. When in doubt, consult the module reference at the\n", 563 | " location listed above.\n", 564 | "\n", 565 | "DESCRIPTION\n", 566 | " This module is always available. It provides access to the\n", 567 | " mathematical functions defined by the C standard.\n", 568 | "\n", 569 | "FUNCTIONS\n", 570 | " acos(...)\n", 571 | " acos(x)\n", 572 | " \n", 573 | " Return the arc cosine (measured in radians) of x.\n", 574 | " \n", 575 | " acosh(...)\n", 576 | " acosh(x)\n", 577 | " \n", 578 | " Return the inverse hyperbolic cosine of x.\n", 579 | " \n", 580 | " asin(...)\n", 581 | " asin(x)\n", 582 | " \n", 583 | " Return the arc sine (measured in radians) of x.\n", 584 | " \n", 585 | " asinh(...)\n", 586 | " asinh(x)\n", 587 | " \n", 588 | " Return the inverse hyperbolic sine of x.\n", 589 | " \n", 590 | " atan(...)\n", 591 | " atan(x)\n", 592 | " \n", 593 | " Return the arc tangent (measured in radians) of x.\n", 594 | " \n", 595 | " atan2(...)\n", 596 | " atan2(y, x)\n", 597 | " \n", 598 | " Return the arc tangent (measured in radians) of y/x.\n", 599 | " Unlike atan(y/x), the signs of both x and y are considered.\n", 600 | " \n", 601 | " atanh(...)\n", 602 | " atanh(x)\n", 603 | " \n", 604 | " Return the inverse hyperbolic tangent of x.\n", 605 | " \n", 606 | " ceil(...)\n", 607 | " ceil(x)\n", 608 | " \n", 609 | " Return the ceiling of x as an Integral.\n", 610 | " This is the smallest integer >= x.\n", 611 | " \n", 612 | " copysign(...)\n", 613 | " copysign(x, y)\n", 614 | " \n", 615 | " Return a float with the magnitude (absolute value) of x but the sign \n", 616 | " of y. On platforms that support signed zeros, copysign(1.0, -0.0) \n", 617 | " returns -1.0.\n", 618 | " \n", 619 | " cos(...)\n", 620 | " cos(x)\n", 621 | " \n", 622 | " Return the cosine of x (measured in radians).\n", 623 | " \n", 624 | " cosh(...)\n", 625 | " cosh(x)\n", 626 | " \n", 627 | " Return the hyperbolic cosine of x.\n", 628 | " \n", 629 | " degrees(...)\n", 630 | " degrees(x)\n", 631 | " \n", 632 | " Convert angle x from radians to degrees.\n", 633 | " \n", 634 | " erf(...)\n", 635 | " erf(x)\n", 636 | " \n", 637 | " Error function at x.\n", 638 | " \n", 639 | " erfc(...)\n", 640 | " erfc(x)\n", 641 | " \n", 642 | " Complementary error function at x.\n", 643 | " \n", 644 | " exp(...)\n", 645 | " exp(x)\n", 646 | " \n", 647 | " Return e raised to the power of x.\n", 648 | " \n", 649 | " expm1(...)\n", 650 | " expm1(x)\n", 651 | " \n", 652 | " Return exp(x)-1.\n", 653 | " This function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.\n", 654 | " \n", 655 | " fabs(...)\n", 656 | " fabs(x)\n", 657 | " \n", 658 | " Return the absolute value of the float x.\n", 659 | " \n", 660 | " factorial(...)\n", 661 | " factorial(x) -> Integral\n", 662 | " \n", 663 | " Find x!. Raise a ValueError if x is negative or non-integral.\n", 664 | " \n", 665 | " floor(...)\n", 666 | " floor(x)\n", 667 | " \n", 668 | " Return the floor of x as an Integral.\n", 669 | " This is the largest integer <= x.\n", 670 | " \n", 671 | " fmod(...)\n", 672 | " fmod(x, y)\n", 673 | " \n", 674 | " Return fmod(x, y), according to platform C. x % y may differ.\n", 675 | " \n", 676 | " frexp(...)\n", 677 | " frexp(x)\n", 678 | " \n", 679 | " Return the mantissa and exponent of x, as pair (m, e).\n", 680 | " m is a float and e is an int, such that x = m * 2.**e.\n", 681 | " If x is 0, m and e are both 0. Else 0.5 <= abs(m) < 1.0.\n", 682 | " \n", 683 | " fsum(...)\n", 684 | " fsum(iterable)\n", 685 | " \n", 686 | " Return an accurate floating point sum of values in the iterable.\n", 687 | " Assumes IEEE-754 floating point arithmetic.\n", 688 | " \n", 689 | " gamma(...)\n", 690 | " gamma(x)\n", 691 | " \n", 692 | " Gamma function at x.\n", 693 | " \n", 694 | " gcd(...)\n", 695 | " gcd(x, y) -> int\n", 696 | " greatest common divisor of x and y\n", 697 | " \n", 698 | " hypot(...)\n", 699 | " hypot(x, y)\n", 700 | " \n", 701 | " Return the Euclidean distance, sqrt(x*x + y*y).\n", 702 | " \n", 703 | " isclose(...)\n", 704 | " isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool\n", 705 | " \n", 706 | " Determine whether two floating point numbers are close in value.\n", 707 | " \n", 708 | " rel_tol\n", 709 | " maximum difference for being considered \"close\", relative to the\n", 710 | " magnitude of the input values\n", 711 | " abs_tol\n", 712 | " maximum difference for being considered \"close\", regardless of the\n", 713 | " magnitude of the input values\n", 714 | " \n", 715 | " Return True if a is close in value to b, and False otherwise.\n", 716 | " \n", 717 | " For the values to be considered close, the difference between them\n", 718 | " must be smaller than at least one of the tolerances.\n", 719 | " \n", 720 | " -inf, inf and NaN behave similarly to the IEEE 754 Standard. That\n", 721 | " is, NaN is not close to anything, even itself. inf and -inf are\n", 722 | " only close to themselves.\n", 723 | " \n", 724 | " isfinite(...)\n", 725 | " isfinite(x) -> bool\n", 726 | " \n", 727 | " Return True if x is neither an infinity nor a NaN, and False otherwise.\n", 728 | " \n", 729 | " isinf(...)\n", 730 | " isinf(x) -> bool\n", 731 | " \n", 732 | " Return True if x is a positive or negative infinity, and False otherwise.\n", 733 | " \n", 734 | " isnan(...)\n", 735 | " isnan(x) -> bool\n", 736 | " \n", 737 | " Return True if x is a NaN (not a number), and False otherwise.\n", 738 | " \n", 739 | " ldexp(...)\n", 740 | " ldexp(x, i)\n", 741 | " \n", 742 | " Return x * (2**i).\n", 743 | " \n", 744 | " lgamma(...)\n", 745 | " lgamma(x)\n", 746 | " \n", 747 | " Natural logarithm of absolute value of Gamma function at x.\n", 748 | " \n", 749 | " log(...)\n", 750 | " log(x[, base])\n", 751 | " \n", 752 | " Return the logarithm of x to the given base.\n", 753 | " If the base not specified, returns the natural logarithm (base e) of x.\n", 754 | " \n", 755 | " log10(...)\n", 756 | " log10(x)\n", 757 | " \n", 758 | " Return the base 10 logarithm of x.\n", 759 | " \n", 760 | " log1p(...)\n", 761 | " log1p(x)\n", 762 | " \n", 763 | " Return the natural logarithm of 1+x (base e).\n", 764 | " The result is computed in a way which is accurate for x near zero.\n", 765 | " \n", 766 | " log2(...)\n", 767 | " log2(x)\n", 768 | " \n", 769 | " Return the base 2 logarithm of x.\n", 770 | " \n", 771 | " modf(...)\n", 772 | " modf(x)\n", 773 | " \n", 774 | " Return the fractional and integer parts of x. Both results carry the sign\n", 775 | " of x and are floats.\n", 776 | " \n", 777 | " pow(...)\n", 778 | " pow(x, y)\n", 779 | " \n", 780 | " Return x**y (x to the power of y).\n", 781 | " \n", 782 | " radians(...)\n", 783 | " radians(x)\n", 784 | " \n", 785 | " Convert angle x from degrees to radians.\n", 786 | " \n", 787 | " sin(...)\n", 788 | " sin(x)\n", 789 | " \n", 790 | " Return the sine of x (measured in radians).\n", 791 | " \n", 792 | " sinh(...)\n", 793 | " sinh(x)\n", 794 | " \n", 795 | " Return the hyperbolic sine of x.\n", 796 | " \n", 797 | " sqrt(...)\n", 798 | " sqrt(x)\n", 799 | " \n", 800 | " Return the square root of x.\n", 801 | " \n", 802 | " tan(...)\n", 803 | " tan(x)\n", 804 | " \n", 805 | " Return the tangent of x (measured in radians).\n", 806 | " \n", 807 | " tanh(...)\n", 808 | " tanh(x)\n", 809 | " \n", 810 | " Return the hyperbolic tangent of x.\n", 811 | " \n", 812 | " trunc(...)\n", 813 | " trunc(x:Real) -> Integral\n", 814 | " \n", 815 | " Truncates x to the nearest Integral toward 0. Uses the __trunc__ magic method.\n", 816 | "\n", 817 | "DATA\n", 818 | " e = 2.718281828459045\n", 819 | " inf = inf\n", 820 | " nan = nan\n", 821 | " pi = 3.141592653589793\n", 822 | " tau = 6.283185307179586\n", 823 | "\n", 824 | "FILE\n", 825 | " /Users/fangohr/anaconda3/lib/python3.6/lib-dynload/math.cpython-36m-darwin.so\n", 826 | "\n", 827 | "\n" 828 | ] 829 | } 830 | ], 831 | "source": [ 832 | "# NBVAL_IGNORE_OUTPUT\n", 833 | "help(math)" 834 | ] 835 | }, 836 | { 837 | "cell_type": "markdown", 838 | "metadata": {}, 839 | "source": [ 840 | "The `help` function needs to be given the name of an object (which must exist in the current name space). For example `help(math.sqrt)` will not work if the `math` module has not been imported before." 841 | ] 842 | }, 843 | { 844 | "cell_type": "code", 845 | "execution_count": 17, 846 | "metadata": {}, 847 | "outputs": [ 848 | { 849 | "name": "stdout", 850 | "output_type": "stream", 851 | "text": [ 852 | "Help on built-in function sqrt in module math:\n", 853 | "\n", 854 | "sqrt(...)\n", 855 | " sqrt(x)\n", 856 | " \n", 857 | " Return the square root of x.\n", 858 | "\n" 859 | ] 860 | } 861 | ], 862 | "source": [ 863 | "# NBVAL_IGNORE_OUTPUT\n", 864 | "help(math.sqrt)" 865 | ] 866 | }, 867 | { 868 | "cell_type": "code", 869 | "execution_count": 18, 870 | "metadata": {}, 871 | "outputs": [ 872 | { 873 | "name": "stdout", 874 | "output_type": "stream", 875 | "text": [ 876 | "Help on built-in function sqrt in module math:\n", 877 | "\n", 878 | "sqrt(...)\n", 879 | " sqrt(x)\n", 880 | " \n", 881 | " Return the square root of x.\n", 882 | "\n" 883 | ] 884 | } 885 | ], 886 | "source": [ 887 | "# NBVAL_IGNORE_OUTPUT\n", 888 | "import math\n", 889 | "help(math.sqrt)" 890 | ] 891 | }, 892 | { 893 | "cell_type": "markdown", 894 | "metadata": {}, 895 | "source": [ 896 | "Instead of importing the module, we could also have given the *string* of `math.sqrt` to the help function, i.e.:" 897 | ] 898 | }, 899 | { 900 | "cell_type": "code", 901 | "execution_count": 19, 902 | "metadata": {}, 903 | "outputs": [ 904 | { 905 | "name": "stdout", 906 | "output_type": "stream", 907 | "text": [ 908 | "Help on built-in function sqrt in math:\n", 909 | "\n", 910 | "math.sqrt = sqrt(...)\n", 911 | " sqrt(x)\n", 912 | " \n", 913 | " Return the square root of x.\n", 914 | "\n" 915 | ] 916 | } 917 | ], 918 | "source": [ 919 | "# NBVAL_IGNORE_OUTPUT\n", 920 | "help('math.sqrt')" 921 | ] 922 | }, 923 | { 924 | "cell_type": "markdown", 925 | "metadata": {}, 926 | "source": [ 927 | "`help` is a function which gives information about the object which is passed as its argument. Most things in Python (classes, functions, modules, etc.) are objects, and therefor can be passed to help. There are, however, some things on which you might like to ask for help, which are not existing Python objects. In such cases it is often possible to pass a string containing the name of the thing or concept to help, for example\n", 928 | "\n", 929 | "- `help(’modules’)` will generate a list of all modules which can be imported into the current interpreter. Note that help(modules) (note absence of quotes) will result in a NameError (unless you are unlucky enough to have a variable called modules floating around, in which case you will get help on whatever that variable happens to refer to.)\n", 930 | "\n", 931 | "- `help(’some_module’)`, where some\\_module is a module which has not been imported yet (and therefor isn’t an object yet), will give you that module’s help information.\n", 932 | "\n", 933 | "- `help(’some_keyword’)`: For example `and`, `if` or `print` (*i.e.* `help(’and’)`, `help(’if’)` and `help(’print’)`). These are special words recognized by Python: they are not objects and thus cannot be passed as arguments to help. Passing the name of the keyword as a string to help works, but only if you have Python’s HTML documentation installed, and the interpreter has been made aware of its location by setting the environment variable PYTHONDOCS.\n", 934 | "\n", 935 | "## Docstrings\n", 936 | "\n", 937 | "The command `help()` accesses the documentation strings of objects.\n", 938 | "\n", 939 | "Any literal string apparing as the first item in the definition of a class, function, method or module, is taken to be its *docstring*.\n", 940 | "\n", 941 | "`help` includes the docstring in the information it displays about the object.\n", 942 | "\n", 943 | "In addition to the docstring it may display some other information, for example, in the case of functions, it displays the function’s signature.\n", 944 | "\n", 945 | "The docstring is stored in the object’s `__doc__` attribute." 946 | ] 947 | }, 948 | { 949 | "cell_type": "code", 950 | "execution_count": 20, 951 | "metadata": {}, 952 | "outputs": [ 953 | { 954 | "name": "stdout", 955 | "output_type": "stream", 956 | "text": [ 957 | "Help on built-in function sin in module math:\n", 958 | "\n", 959 | "sin(...)\n", 960 | " sin(x)\n", 961 | " \n", 962 | " Return the sine of x (measured in radians).\n", 963 | "\n" 964 | ] 965 | } 966 | ], 967 | "source": [ 968 | "# NBVAL_IGNORE_OUTPUT\n", 969 | "help(math.sin)" 970 | ] 971 | }, 972 | { 973 | "cell_type": "code", 974 | "execution_count": 21, 975 | "metadata": {}, 976 | "outputs": [ 977 | { 978 | "name": "stdout", 979 | "output_type": "stream", 980 | "text": [ 981 | "sin(x)\n", 982 | "\n", 983 | "Return the sine of x (measured in radians).\n" 984 | ] 985 | } 986 | ], 987 | "source": [ 988 | "# NBVAL_IGNORE_OUTPUT\n", 989 | "print(math.sin.__doc__)" 990 | ] 991 | }, 992 | { 993 | "cell_type": "markdown", 994 | "metadata": {}, 995 | "source": [ 996 | "For user-defined functions, classes, types, modules, …), one should always provide a docstring.\n", 997 | "\n", 998 | "Documenting a user-provided function:" 999 | ] 1000 | }, 1001 | { 1002 | "cell_type": "code", 1003 | "execution_count": 22, 1004 | "metadata": {}, 1005 | "outputs": [ 1006 | { 1007 | "data": { 1008 | "text/plain": [ 1009 | "(4, 8)" 1010 | ] 1011 | }, 1012 | "execution_count": 22, 1013 | "metadata": {}, 1014 | "output_type": "execute_result" 1015 | } 1016 | ], 1017 | "source": [ 1018 | "def power2and3(x):\n", 1019 | " \"\"\"Returns the tuple (x**2, x**3)\"\"\"\n", 1020 | " return x**2 ,x**3\n", 1021 | "\n", 1022 | "power2and3(2)" 1023 | ] 1024 | }, 1025 | { 1026 | "cell_type": "code", 1027 | "execution_count": 23, 1028 | "metadata": {}, 1029 | "outputs": [ 1030 | { 1031 | "data": { 1032 | "text/plain": [ 1033 | "(20.25, 91.125)" 1034 | ] 1035 | }, 1036 | "execution_count": 23, 1037 | "metadata": {}, 1038 | "output_type": "execute_result" 1039 | } 1040 | ], 1041 | "source": [ 1042 | "power2and3(4.5)" 1043 | ] 1044 | }, 1045 | { 1046 | "cell_type": "code", 1047 | "execution_count": 24, 1048 | "metadata": {}, 1049 | "outputs": [ 1050 | { 1051 | "data": { 1052 | "text/plain": [ 1053 | "((-1+0j), (-0-1j))" 1054 | ] 1055 | }, 1056 | "execution_count": 24, 1057 | "metadata": {}, 1058 | "output_type": "execute_result" 1059 | } 1060 | ], 1061 | "source": [ 1062 | "power2and3(0+1j)" 1063 | ] 1064 | }, 1065 | { 1066 | "cell_type": "code", 1067 | "execution_count": 25, 1068 | "metadata": {}, 1069 | "outputs": [ 1070 | { 1071 | "name": "stdout", 1072 | "output_type": "stream", 1073 | "text": [ 1074 | "Help on function power2and3 in module __main__:\n", 1075 | "\n", 1076 | "power2and3(x)\n", 1077 | " Returns the tuple (x**2, x**3)\n", 1078 | "\n" 1079 | ] 1080 | } 1081 | ], 1082 | "source": [ 1083 | "help(power2and3)" 1084 | ] 1085 | }, 1086 | { 1087 | "cell_type": "code", 1088 | "execution_count": 26, 1089 | "metadata": {}, 1090 | "outputs": [ 1091 | { 1092 | "name": "stdout", 1093 | "output_type": "stream", 1094 | "text": [ 1095 | "Returns the tuple (x**2, x**3)\n" 1096 | ] 1097 | } 1098 | ], 1099 | "source": [ 1100 | "print(power2and3.__doc__)" 1101 | ] 1102 | } 1103 | ], 1104 | "metadata": { 1105 | "kernelspec": { 1106 | "display_name": "Python 3", 1107 | "language": "python", 1108 | "name": "python3" 1109 | }, 1110 | "language_info": { 1111 | "codemirror_mode": { 1112 | "name": "ipython", 1113 | "version": 3 1114 | }, 1115 | "file_extension": ".py", 1116 | "mimetype": "text/x-python", 1117 | "name": "python", 1118 | "nbconvert_exporter": "python", 1119 | "pygments_lexer": "ipython3", 1120 | "version": "3.6.8" 1121 | } 1122 | }, 1123 | "nbformat": 4, 1124 | "nbformat_minor": 1 1125 | } 1126 | --------------------------------------------------------------------------------