Python For Mathematics
Python For Mathematics
Vincent Knight
Front cover image: Mendin\Shutterstock
First edition published 2025
by CRC Press
2385 NW Executive Center Drive, Suite 320, Boca Raton FL 33431
and by CRC Press
4 Park Square, Milton Park, Abingdon, Oxon, OX14 4RN
CRC Press is an imprint of Taylor & Francis Group, LLC
© 2025 Vincent Knight
Reasonable efforts have been made to publish reliable data and information, but the
author and publisher
cannot assume responsibility for the validity of all materials or the consequences
of their use. The authors
and publishers have attempted to trace the copyright holders of all material
reproduced in this publication and apologize to copyright holders if permission to
publish in this form has not been obtained. If any
copyright material has not been acknowledged please write and let us know so we may
rectify in any future
reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be
reprinted, reproduced, transmitted, or utilized in any form by any electronic,
mechanical, or other means, now known or hereafter
invented, including photocopying, microfilming, and recording, or in any
information storage or retrieval
system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access
www.copyright.com or
contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA
01923, 978-7508400. For works that are not available on CCC please contact
[email protected]
Trademark notice: Product or corporate names may be trademarks or registered
trademarks and are used
only for identification and explanation without intent to infringe.
ISBN: 978-1-032-58817-9 (hbk)
ISBN: 978-1-032-58218-4 (pbk)
ISBN: 978-1-003-45186-0 (ebk)
DOI: 10.1201/9781003451860
Typeset in Latin Modern font
by KnowledgeWorks Global Ltd.
Publisher’s note: This book has been prepared from camera-ready copy provided by
the authors.
To Daniele, may someone who reads this book learn a fraction
of what I have learnt from you. I am grateful for our friendship.
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
Contents
Preface
xv
Section I
Overview
Chapter
1 ■ Introduction
3
3
4
6
6
7
7
9
2 ■ Using Notebooks
2.1 INTRODUCTION
2.2 TUTORIAL
2.2.1
Installation
2.2.2
Starting a Jupyter notebook server
2.2.3
Creating a new notebook
2.2.4
Organising your files
2.2.5
Writing some basic Python code
2.2.6
Writing markdown
2.2.7
Saving your notebook to a different format
2.3 HOW TO
2.3.1
Install Anaconda
2.3.2
Start a Jupyter notebook server
2.3.3
Create a new notebook
2.3.4
Find/open a notebook
2.3.5
Run Python code
2.3.6
Carry out basic arithmetic operations
2.3.7
Write markdown
13
13
13
13
14
14
14
17
20
20
21
21
21
21
21
21
22
22
vii
viii ■ Contents
2.3.8
2.3.9
2.4 EXERCISES
2.5 FURTHER INFORMATION
2.5.1
Why use anaconda?
2.5.2
Why use Jupyter?
2.5.3
Why can I not double click on a Jupyter notebook file?
2.5.4
Where can I find keyboard shortcuts for using Jupyter
2.5.5
What is markdown?
2.5.6
What is LaTeX?
2.5.7
Can I use \( and \) instead of $ for LATEX?
2.5.8
What is a markup language?
Chapter
3 ■ Algebra
3.1 INTRODUCTION
3.2 TUTORIAL
3.3 HOW TO
3.3.1
Create a symbolic numeric value
3.3.2
Get the numerical value of a symbolic expression
3.3.3
Factor an expression
3.3.4
Expand an expression
3.3.5
Simplify an expression
3.3.6
Solve an equation
3.3.7
Substitute a value into an expression
3.4 EXERCISES
3.5 FURTHER INFORMATION
3.5.1
Why is some code in separate libraries?
3.5.2
Why do I need to use sympy?
3.5.3
Why do I sometimes see from sympy import *?
3.5.4
How to extract a solution from the output of sympy.solveset?
3.5.5
Why do I sometimes see import sympy as sym?
Chapter
4 ■ Calculus
4.1 TUTORIAL
4.2 HOW TO
4.2.1
Calculate the derivative of an expression.
4.2.2
Calculate the indefinite integral of an expression.
4.2.3
Calculate the definite integral of an expression.
4.2.4
Use ∞
4.2.5
Calculate limits of an expression
4.3 EXERCISES
22
23
24
24
24
24
25
25
25
25
26
26
27
27
27
33
33
34
35
35
36
36
38
38
39
39
39
40
42
44
45
45
50
50
51
51
52
52
53
Contents ■ ix
Chapter
5 ■ Matrices
5.1 TUTORIAL
5.2 HOW TO
5.2.1
Create a matrix
5.2.2
Calculate the determinant of a matrix
5.2.3
Calculate the inverse of a matrix
5.2.4
Multiply matrices by a scalar
5.2.5
Add matrices together
5.2.6
Multiply matrices together
5.2.7
Create a vector
5.2.8
Solve a linear system
5.3 EXERCISES
5.4 FURTHER INFORMATION
5.4.1
Why does this book not discuss commenting of code?
5.4.2
Why use @ for matrix multiplication and not *?
5.4.3
Is Numpy a library that can be used for linear algebra?
Chapter
6 ■ Combinatorics
6.1 TUTORIAL
6.2 HOW TO
6.2.1
Create a tuple
6.2.2
How to access particular elements in a tuple
6.2.3
Create boolean variables
6.2.4
Create an iterable with a given number of items
6.2.5
Create permutations of a given set of elements
6.2.6
Create combinations of a given set of elements
6.2.7
Summing items in an iterable
6.2.8
Directly compute n!
# #
6.2.9
Directly compute ni
6.2.10 Directly compute n Pi
6.3 EXERCISES
6.4 FURTHER INFORMATION
6.4.1
Are there other ways to access elements in tuples?
6.4.2
Why do range, itertools.permutations, and
itertools.combinations not directly give the elements?
#
6.4.3
How does the summation notation
correspond to the code?
53
53
56
56
59
59
60
60
61
61
62
62
62
63
64
64
64
65
66
66
70
70
70
70
73
73
74
75
76
76
77
77
78
78
78
78
x ■ Contents
Chapter
7 ■ Probability
7.1 TUTORIAL
7.2 HOW TO
7.2.1
Create a list
7.2.2
Define a function
7.2.3
Call a function
7.2.4
Run code based on a condition
7.2.5
Create a list using a list comprehension
7.2.6
Summing items in a list
7.2.7
Sample from an iterable
7.2.8
Sample a random number
7.2.9
Reproduce random events
7.3 EXERCISES
7.4 FURTHER INFORMATION
7.4.1
What is the difference between a Python list and a Python tuple?
7.4.2
Why does the sum of booleans count the Trues?
7.4.3
What is the difference between print and return?
7.4.4
How does Python sample randomness?
7.4.5
What is the difference between a docstring and a comment
Chapter
8 ■ Sequences
8.1 TUTORIAL
8.2 HOW TO
8.2.1
Define a function using recursion
8.3 EXERCISES
8.4 FURTHER INFORMATION
8.4.1
What are the differences between recursion and iteration?
8.4.2
What is caching?
Chapter
9 ■ Statistics
9.1 TUTORIAL
9.2 HOW TO
9.2.1
Calculate measures of spread and tendency
9.2.2
Calculate the sample covariance
9.2.3
Calculate the Pearson correlation coefficient
9.2.4
Fit a line of best fit
9.2.5
Create an instance of the normal distribution
9.2.6
Use the cumulative distribution function of a normal distribution
9.2.7
Use the inverse cumulative distribution function of a normal
distribution
9.3 EXERCISES
80
80
87
87
89
89
90
92
93
94
95
95
96
97
97
97
98
99
99
102
102
104
104
105
106
106
108
110
110
118
118
122
123
123
124
125
125
126
Contents ■ xi
129
129
130
131
131
132
132
134
134
135
136
137
137
138
138
139
145
145
148
148
149
149
150
150
151
152
153
153
154
155
156
156
157
158
158
159
xii ■ Contents
161
161
164
164
164
165
165
165
165
166
166
167
167
168
168
168
169
169
170
170
171
172
173
173
173
174
175
175
175
175
176
176
181
181
181
182
183
184
186
Contents ■ xiii
13.3 EXERCISES
13.4 FURTHER INFORMATION
13.4.1 How to pronounce the double underscore?
13.4.2 What is the self variable for?
13.4.3 Why use CamelCase for classes but snake case for functions?
13.4.4 What is the difference between a method and a function?
187
188
188
188
190
190
191
14.1 TUTORIAL
191
14.2 HOW TO
196
14.2.1 Navigate directories using the command line
196
14.2.2 Create a new directory using the command line
197
14.2.3 See the contents of a directory in the command line
197
14.2.4 Run Python code in a file
198
14.2.5 Run Python code without using a file or Jupyter
198
14.2.6 Install VScode plugins
198
14.3 EXERCISES
199
14.4 FURTHER INFORMATION
199
14.4.1 Why do you need to use the print function with an editor?
199
14.4.2 Can you use a Python plugin to run my code from inside my editor? 199
14.4.3 Can I open a Jupyter notebook inside VScode?
200
14.4.4 What is the difference between an Integrated Development
Environment and an editor?
200
14.4.5 Can I use \( and \) instead of $ for LATEX?
200
Chapter 15 ■ Modularisation
15.1 TUTORIAL
15.2 HOW TO
15.2.1 Import code from Python files
15.2.2 Break up code into modular components
15.3 EXERCISES
15.4 FURTHER INFORMATION
15.4.1 Why modularise?
15.4.2 Why do I get an import error?
15.4.3 How do I make my file importable from other directories?
Chapter 16 ■ Documentation
16.1 TUTORIAL
16.1.1 Writing a tutorial
16.1.2 Writing the how-to guides
16.1.3 Writing the explanations section
201
201
207
207
208
210
212
212
213
213
214
214
214
216
218
xiv ■ Contents
Chapter 17 ■ Testing
17.1 TUTORIAL
17.1.1 Writing tests for code
17.1.2 Testing documentation
17.1.3 Documenting the tests
17.2 HOW TO
17.2.1 Write an assert statement
17.2.2 Write assert statements for code that acts randomly
17.2.3 Write a test file
17.2.4 Format doctests
17.2.5 Run doctests
17.3 EXERCISES
17.4 FURTHER INFORMATION
17.4.1 Why are tests written as functions?
17.4.2 Is there a more efficient way to run tests?
17.4.3 What should be tested?
17.4.4 Why do you need doctests?
17.4.5 What is test driven development?
17.4.6 How are modularisation, documentation and testing related?
227
227
227
231
236
237
237
238
239
241
242
242
242
242
242
243
243
243
244
247
Bibliography
249
Index
251
Preface
Welcome to this book.
This is not a book for learning to program to do mathematics. There are many
excellent
books that do this [1, 6, 10]. This is a book for people who would like to learn
how to use
programming tools to assist with when doing Mathematics.
Mathematics is often thought of as solving problems. In secondary school this can
be
sets of quadratic equations that need to be solved or probabilities of specific
hands of cards
that need to be calculated.
As one progresses further into mathematics, the subject becomes less about solving
problems through mechanical calculation and more about using our mathematical
knowledge and
insight to choose which problems to solve.
This is what this book attempts to address. It aims to be a user guide for how the
Python programming language can be used to reduce mechanical calculation which
leaves
more space to do real mathematics.
Whilst no book should ever try to stop a mathematician from picking up a pen and
pencil
and thinking about a problem, this one does aim to show how modern mathematicians
can
replace, some of, the use of their pen with openly available Python tools. For
example,
in Chapter 3, how to solve an equation by essentially just writing it down is
covered. In
Chapter 7 probabilities of specific events are simulated.
In the second part of this book, a more traditional approach of programming with
Python is used to show how to build tools. Not only does this cover commonly taught
programming techniques but also goes into principles of software development used
in industry.
For example, Chapter 16 covers a modern way of writing documentation for software
and
Chapter 17 covers how to write code that tests software.
This book is for you, whether you are a seasoned professional mathematician who
would
like to know some of the best practices for using Python or perhaps more typically,
if you are
a first year university student with an understanding of the mathematical topics
covered. I
hope you enjoy it.
xv
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
I
Overview
1
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
CHAPTER
Introduction
1.1
3. Use the second derivative test on the solution to the previous equation.
For each of those three steps you will usually make use of our mathematical
techniques:
1. Differentiate f (x):
df
= 2x − 3
dx
df
2. Equate dx
= 0:
2x − 3 = 0 ⇒ x = 3/2
3. Use the second derivative test on the solution:
d2 f
= 2 > 0 for all values of x
dx2
Thus x = 3/2 is the global minima of the function.
As you progress as a mathematician, mathematical knowledge is more prominent
than mathematical technique: often knowing what to do is the real problem as
opposed
to having the technical ability to do it.
This is what this book will cover: programming allows you to instruct a computer to
carry out mathematical techniques.
DOI: 10.1201/9781003451860-1
3
4 ■ Python for Mathematics
For example, you will learn how to solve the above problem by instructing a
computer
which mathematical technique to carry out.
This book covers how to give the correct instructions to a computer.
The following is an example; do not worry too much about the specific code used for
now:
df
Differentiate f (x) to get dx
Jupyter input
1
2
3
4
x = sym.Symbol("x")
sym.diff(x ** 2 - 3 * x + 1, x)
Equate
df
dx = 0
2x − 3
Jupyter input
1
sym.solveset(2 * x - 3, x)
# #
3
2
Jupyter input
1
sym.diff(x ** 2 - 3 * x + 1, x, 2)
2
Figure 1.1 shows a summary.
1.1.1
Figure 1.1
14. Chapter 15
15. Chapter 16
16. Chapter 17
The choice to flip this structure and start with real use cases (and not code
recipes) is
deliberate. The tools covered in Chapters 3–10 can be used with little to no
programming
knowledge and need only an understanding of the mathematics. Following this, the
topics
covered in Chapters 11–13 let the reader expand on the knowledge and learn basics
of
programming. The topics and techniques covered in Chapters 14–17 show how modern
research software is designed.
1.2
Most programming texts introduce readers to the building blocks of programming and
build
up to using more sophisticated tools for a specific purpose.
This is akin to teaching someone how to forge metal so as to make a nail and then
slowly
work up to using more sophisticated tools such as power tools to build a house.
This book will do things in a different way: you will start with using and
understanding
tools that are helpful to mathematicians. In the later part of the book, you will
cover the
building blocks and you will be able to build your own sophisticated tools.
1.2.1
• Linear algebra
• Sequences
• Statistics
• Differential equations
The questions you will tackle aim to be familiar in their presentation and
description.
What will be different is that no by hand calculations will be done. You will
instead
carry them all out using a programming language.
In the second part of the book, you will be encouraged to build your own tools for
tackling problems of your choice.
Each chapter will have four parts:
Introduction ■ 7
• statistics (Chapters 9)
1.2.2
Readers are welcome to use this book in any way they find useful; however, it is
designed
with the following suggestions in mind:
• Start by following along with the tutorial. Carrying out the steps and observing
the
outcomes. It is not expected that a reader gains a deep understanding of a given
topic when working through the tutorial. The goal here is to achieve some level of
familiarity.
• After the tutorial, work through the how to section. It is through this section
that
a deeper understanding is to be gained by making connections to steps taken in the
tutorial. After working through the how to section it is hoped that the
reader would understand all steps taken in the tutorial.
• The exercise section is an opportunity for the reader to practice the topics in
the how
to section.
• After working through those three sections, it is possible that some readers have
further questions or would like to find more information about a given topic. This
is
covered in the further information section.
1.2.3
In this book, you will see code displayed in a number of different formats. The
most common
is the following:
8 ■ Python for Mathematics
Jupyter input
1
2 + 2
4
This is shown as input to the programming tool “Jupyter” which is described at
length
in Chapter 2. As well as the input, it will also display the output (as above).
You will see typical usage instructions for particular code commands:
Usage
1
sym.solveset(<equation>)
You will see how to write a particular language called “markdown” (covered in
Chapters 2
and 16):
Markdown input
1
In Chapters 14–17 you will also see Python code saved to Python files:
Python file
1
print(2 + 2)
You will also see commands written for a command line tool. This is how you will
start
“Jupyter” in Chapter 2 but will be introduced more formally in Chapter 14.
$ ls
Note that when some lines of code are long, they might include a carriage return
symbol
(#→):
Jupyter input
1
864197523
The carriage return symbol should be ignored as it is only present in the book due
to
the physical constraint of the page width. The code should still be written as a
single line.
1.3
http://taylorandfrancis.com
II
Tools for Mathematics
11
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
CHAPTER
Using Notebooks
2.1
INTRODUCTION
• Gottfried Leibniz
One of the differences between the approaches taken by Newton and Leibniz is their
notation. Newton denoted the derivative of a function f as:
Df
Leibniz denoted the derivative with the now more commonly used notation:
df
dx
The mathematics itself is unchanged: what changes is the language/notation used to
communicate it. Similarly when giving instructions through code to a computer there
are
a number of notations, more commonly called languages available. This book will be
using
a language called Python. Python was originally designed as a teaching language but
it is
now popular both in academia and in industry.
In this chapter you will cover:
• Installing the specific distribution of Python on your computer.
• Using something called a Jupyter notebook to write and run Python code.
2.2
TUTORIAL
This tutorial will take the reader through an example of using Jupyter notebooks.
Jupyter
is the interface to the Python programming language used in the first part of this
book.
2.2.1
Installation
1. Navigate to https://www.anaconda.com/products/individual.
DOI: 10.1201/9781003451860-2
13
14 ■ Python for Mathematics
2. Identify and download the version of Python 3 for your operating system
(Windows,
MacOS, Linux).
3. Run the installer. I recommend using the default choices during the installation
process.
If you have already used Python, it is still recommended that you use the Anaconda
distribution. An explanation for this is available in Section 2.5.1. If you are
using a
Chromebook or a Tablet, there are websites and applications that you can use
instead
of Anaconda to follow along in this book. See the online version of this book for
up-todate information on this.
2.2.2
$ jupyter notebook
2.2.3
In the top right, click on the New button (Figure 2.4) and click on Notebook. This
will be
followed by a prompt to choose the programming language to use, this is referred to
as the
kernel: select Python 3. Change the name of the notebook by clicking on “Untitled”
and
changing the name. You will call it “introduction” as shown in Figure 2.5.
2.2.4
Figure 2.1
Figure 2.2
Figure 2.3
Figure 2.4
Figure 2.5
Navigate to where your notebook is (this might not be immediately evident): you
should
see a introduction.ipynb file. Find a location on your computer where you want to
keep
the files for this book, using your file browser:
1. Create a new directory called pfm (short for “Python for Mathematics”);
2. Inside that directory create a new directory called nbs (short for “Notebooks”);
3. Move the introduction.ipynb file to this nbs directory.
Figure 2.6
2.2.5
Go back to the Jupyter notebook server (in your browser). Use the interface to
navigate
to the pfm directory and inside that the nbs directory and open the
introduction.ipynb
notebook.
18 ■ Python for Mathematics
Figure 2.7
Figure 2.8
Opening a notebook.
Using Notebooks ■ 19
Usage
1
2 + 2
When you have done that click on the Run button shown in Figure 2.9. You can also
use
Shift + Enter as a keyboard shortcut.
Jupyter input
1
2 + 2
Figure 2.9
Running code.
2
2.2.6
Writing markdown
One of the reasons for using Jupyter notebooks is that it allows a user to include
both code
and communication using something called markdown. Create a new cell and change the
cell
type to Markdown. Now write the following in there:
Markdown input
1
2
3
As well as using Python in Jupyter notebooks you can also write using
Markdown. This allows us to use basic \LaTeX\; as a way to display
mathematics. For example:
4
5
6
1. $\frac{2}{3}$
2. $\sum_{i=0}ˆn i$
Figure 2.10
2.2.7
Rendering markdown.
Click on File and Download As which brings up a number of formats that Jupyter
notebooks
can be exported to. Some of these might need other tools installed on your computer
but
a portable option is HTML. Click on HTML (.html). Now use your file browser and
open the
downloaded file. This will open in your browser a static version of the file you
have been
working on. This is a helpful way to share your work with someone who might not
have
Jupyter (or even Python).
Using Notebooks ■ 21
2.3
HOW TO
2.3.1
Install Anaconda
1. Navigate to https://www.anaconda.com/download/success
2. Download the distribution of anaconda for your Operating System
3. Run the installer
2.3.2
2.3.3
2.3.4
Find/open a notebook
Using a file browser you can navigate the directories and files on your computer.
Jupyter
notebooks appear as generic files with the .ipynb extension.
You cannot double click on these to open them, you need to navigate to them through
the Jupyter interface.
2.3.5
Usage
1
3 / 5
and click on the Run button or use Shift + Enter as a keyboard shortcut.
22 ■ Python for Mathematics
2.3.6
3. Multiplication, 3 × 5: 3 * 5;
4. Division, 20/5: 20 / 5;
5. Exponentiation, 24 : 2 ** 4;
6. Integer remainder, 5 mod 2: 5 % 2;
3
2.3.7
Write markdown
To write markdown click on a cell and change the type to Markdown, you can do this
by
clicking on Cell, Cell Type or by using the scroll wheel in the menu bar. Markdown
is a lightweight mark up language that allows you to write and include various
types of
formatting which include:
1. Headings;
2. Bold and italics;
3. Ordered and unordered lists;
4. Code (which will only be displayed but not run);
5. Hyperlinks
A more detailed guide for writing markdown is given in Section 16.2.2.
2.3.8
Jupyter notebooks allow for markdown cells to not only include markdown but also
include
mathematics using another mark up language called LATEX. Here is a brief overview
of the
syntax for arithmetic operations:
• $a+b$ gives: a + b:
• $a-b$ gives: a − b
• $-a$ gives: −a
• $ab$ gives ab
• $a\cdot b$ gives a · b
Using Notebooks ■ 23
• $a\times b$ gives a × b
• $a/b$ gives a/b
• $\frac{a}{b}$ gives ab
• $a ˆ b$ gives ab
a
c
e
b
d
f
∞
xdx
$$
\int_{0}ˆ{\infty}x dx
$$
gives:
n
2.3.9
2.4
EXERCISES
+2
(d) 42×5
− 52
3. Write a markdown cell with the following and view the rendered version:
# Euler's equation
$$
e ˆ {i\pi} = -1
$$
4. Render the following expressions by writing markdown.
3
+2
(a) 42×5
1
(b) −5 2
df
(c) dx
# 12
(d) 5 x2 dx
#
#
4 12 3
(e)
2 x i
2.5
FURTHER INFORMATION
2.5.1
Python is a free and open source piece of software. One of the main reasons for its
popularity
is that there are a number of separate tools that work well with it, these are
called libraries.
Sometimes installing these libraries can require an understanding of some potential
pitfalls.
In scientific circles the Anaconda distribution was developed to give a single
download of
not only Python but a lot of commonly used libraries.
2.5.2
The second part of this book will use an editor. One strength of Jupyter is that it
allows
you to include communication (writing through markdown) with your code. This allows
you
to use code and describe what you are using it for. Another advantage is that it
allows you
to immediately have your output next to your input. There are some limitations to
Jupyter
as an editor which is why you will explore using a powerful editor in the second
part of the
course. In general:
1. Jupyter is a fantastic way to interactively use and communicate code;
2. Integrated development environments and/or editors are the correct tool to write
software.
In this book you will learn to use either approach in the appropriate manner for
the
right task. For the first part, code will be used interactively and so you will use
Jupyter
notebooks.
2.5.3
When you double click on a file and your computer opens it in an application
because a
default is set for the particular file extension. For example, double clicking on
main.docx
will automatically open up the document using a word processor (like Microsoft
word). This
is because the file has the extension .docx and your operating system has set that
anything
with that extension will be opened in that particular application. You could also
open the
application and navigate to the file and open it directly.
With Jupyter notebooks no default is set by the operating system as the application
that opens it is in fact a local web server in your browser. As such you do not
have a choice
and need to open it in the Jupyter interface.
2.5.4
In a notebook if you go to the menu bar and click on Help followed by Keyboard
Shortcuts
you will find a number of helpful keyboard shortcuts.
For example, when on a cell pressing Esc followed by m will turn the cell into a
markdown
cell.
2.5.5
What is markdown?
As described at https://www.markdownguide.org/getting-started/:
“Markdown is a lightweight markup language that you can use to add formatting
elements to plain text documents. Created by John Gruber in 2004, Markdown
is now one of the world’s most popular markup languages.”
2.5.6
What is LaTeX?
As described at https://www.latex-project.org/about/:
“LaTeX;, which is pronounced ‘Lah-tech’ or ‘Lay-tech’ (to rhyme with ‘blech’ or
‘Bertolt Brecht’), is a document preparation system for high-quality typesetting.
It is most often used for medium-to-large technical or scientific documents but
it can be used for almost any form of publishing.”
26 ■ Python for Mathematics
2.5.7
You will see in some places that \(, \) or \[, \] can be used as delimiters for
LATEX when
used outside of Jupyter notebooks. This is in fact recommended for a number of
reasons,
one of which is given at https://vknight.org/tex/:
“Note that using \( and \) is preferred over $. One of the reasons is that it is
easier for humans (and machines) to find the start and end of some mathematics.”
If you want to use \(, \) or \[, \] as mathematics delimiters within Jupyter
notebooks,
you need to escape the \ and use: \\(, \\) or \\[, \\] instead.
2.5.8
LATEX and markdown are both examples of what is called a markup language. Another
common example of a markup language is html (the way web pages are written). A
markup language is a system that allows us to write content alongside annotations
to specify how the content is to appear. This description of markdown from
https://www.markdownguide.org/getting-started/ applies to any markup language:
“Using Markdown is different than using a WYSIWYG editor. In an application
like Microsoft Word, you click buttons to format words and phrases, and the
changes are visible immediately. Markdown isn’t like that. When you create
a Markdown-formatted file, you add Markdown syntax to the text to indicate
which words and phrases should look different.”
In general whilst it might take a little while to learn all the intricacies of a
markup
language it allows for more portability and precision. Markup languages differ in
complexity:
• LATEX is incredibly sophisticated and has a huge range of capabilities.
Algebra
3.1
INTRODUCTION
3.2
TUTORIAL
To demonstrate the ways in which a computer can assist with Algebra, in this
tutorial you
will solve the following two problems:
1
1. Rationalise the denominator of √2+1
27
28 ■ Python for Mathematics
Jupyter input
1
import sympy
This will allow you to solve the first part of the question. Create a variable
expression
1
.
and assign it a value of √2+1
Jupyter input
1
2
expression = 1 / (sympy.sqrt(2) + 1)
expression
1
√
1+ 2
This is not what would happen if you plugged the above into a basic calculator, it
would instead give you a value of:
Jupyter input
1
float(expression)
0.41421356237309503
The sympy library has a diverse set of tools available, one of which is to
algorithmically
attempt to simplify an expression. Here is how to do that:
Jupyter input
1
sympy.simplify(expression)
−1 +
√
Multiplying both sides by
1= √
1
2+1
√
1
= −1 + 2
2+1
2 + 1 gives:
#√
# #
#
√ # #√
×
2 + 1 = −1 + 2 ×
2+1
Algebra ■ 29
Jupyter input
1
1
The sympy library allows you to carry out basic expression manipulation. Now
consider
the second part of the question:
1. Consider the : f (x) = 2x2 + x + 1:
2. Calculate the of the equation 2x2 + x + 1 = 0. What does this tell you about the
solutions to the equation? What does this tell you about the graph of f (x)?
#
#
3. By completing the square, show that the minimum point of f (x) is − 14 , 78
Start by reassigning the value of the variable expression to be the expression: 2x2
+x+1.
Jupyter input
1
2
3
x = sympy.Symbol("x")
expression = 2 * x ** 2 + x + 1
expression
2x2 + x + 1
The first line communicates to the code that x is going to be a symbolic variable.
Recall that the ** symbol is how you communicate exponentiation.
You can immediately use this to compute the:
Jupyter input
1
sympy.discriminant(expression)
−7
Jupyter input
1
2
2x2 + x + 1 = 0
Now ask sympy to solve it:
Jupyter input
1
sympy.solveset(equation)
√
√ #
1
7i 1
7i
− −
,− +
4
4
4
4
Indeed the only solutions are imaginary numbers: this confirms that the graph of f
(x)
is a convex parabola that is above the y = 0 line. Now complete the square so that
you can
write:
f (x) = a(x − b)2 + c
for some values of a, b, c. Create variables that have those 3 constants as value
but also
create a variable completed square and assign it the general expression:
Jupyter input
1
2
3
a (−b + x) + c
Expand this:
Jupyter input
1
sympy.expand(completed_square)
ab2 − 2abx + ax2 + c
Use sympy to solve the various equations that arise from comparing the coefficients
of:
Algebra ■ 31
f (x) = 2x2 + x + 1
with the completed square. First, you see that the coefficient of x2 gives you an
equation:
a=2
For completeness write the code that solves this trivial equation:
Jupyter input
1
2
equation = sympy.Eq(a, 2)
sympy.solveset(equation, a)
{2}
Now substitute this value of a into the completed square and update the variable
with
the new value:
Jupyter input
1
2
c + 2 (−b + x)
There are different types of brackets being used here: both () and {}. This is
important
and has specific meaning in Python which will be covered in future chapters.
Now look at the expression with the two remaining constants:
Jupyter input
1
sympy.expand(completed_square)
−4b = 1
32 ■ Python for Mathematics
Jupyter input
1
2
equation = sympy.Eq(-4 * b, 1)
sympy.solveset(equation, b)
1
4
Substitute this value of b back into our expression. Make a point to tell sympy to
treat
1/4 symbolically and to not calculate the numeric value:
Jupyter input
1
2
#2
#
1
c+2 x+
4
Expand this to see the expression with the one remaining constant gives:
Jupyter input
1
sympy.expand(completed_square)
1
8
This gives a final equation for the constant term:
c + 2x2 + x +
c + 1/8 = 1
Now use sympy to find the value of c:
Jupyter input
1
# #
7
8
As before substitute in and update the value of completed square:
Algebra ■ 33
Jupyter input
1
2
1
2 x+
4
#2
7
8
Using this shows that the there are indeed no values of x which give negative
values of
f (x) as f (x) is a square added to a constant. The minimum is when x = −1/4 which
gives:
f (−1/4) = 7/8:
Jupyter input
1
completed_square.subs({x: -1 / sympy.S(4)})
7
8
This tutorial has:
• Created symbolic expressions.
3.3
HOW TO
3.3.1
Usage
1
sympy.S(a)
For example:
34 ■ Python for Mathematics
Jupyter input
1
import sympy
2
3
4
value = sympy.S(3)
value
3
If you combine a symbolic value with a non-symbolic value, it will automatically
give a
symbolic value:
Jupyter input
1
1 / value
1
3
3.3.2
You can get the numerical value of a symbolic value using float or int:
• float will give the numeric approximation in {R}
Usage
1
float(x)
Usage
1
int(x)
Jupyter input
1
2
value = 1 / sympy.S(5)
value
Algebra ■ 35
1
5
To get the numerical value:
Jupyter input
1
float(value)
0.2
To get the integer part:
Jupyter input
1
int(value)
0
This is not rounding to the nearest integer. It is returning the integer part.
3.3.3
Factor an expression
Jupyter input
1
sympy.factor(expression)
For example:
Jupyter input
1
2
x = sympy.Symbol("x")
sympy.factor(x ** 2 - 9)
(x − 3) (x + 3)
3.3.4
Expand an expression
Jupyter input
1
sympy.expand(expression)
For example:
Jupyter input
1
sympy.expand((x - 3) * (x + 3))
x2 − 9
3.3.5
Simplify an expression
Jupyter input
1
sympy.simplify(expression)
For example:
Jupyter input
1
sympy.simplify((x - 3) * (x + 3))
x2 − 9
This will not always give the expected (or any) result. At times it could be more
beneficial to use sympy.expand and/or sympy.factor.
3.3.6
Solve an equation
Use the sympy.solveset tool to solve an equation. It takes two values as inputs.
The first
is either:
• An expression for which a root is to be found
• An equation
Usage
1
sympy.solveset(equation, variable)
Here is how you can use sympy to obtain the roots of the general:
ax2 + bx + c
Jupyter input
1
2
3
4
5
a = sympy.Symbol("a")
b = sympy.Symbol("b")
c = sympy.Symbol("c")
quadratic = a * x ** 2 + b * x + c
sympy.solveset(quadratic, x)
b
− −
2a
−4ac + b2
b
,− +
2a
2a
−4ac + b2
2a
Here is to solve the same equation but not for x but for b:
Jupyter input
1
sympy.solveset(quadratic, b)
ax2 + c
−
x
#
It is however clearer to specifically write the equation to solve:
Jupyter input
1
2
equation = sympy.Eq(a * x ** 2 + b * x + c, 0)
sympy.solveset(equation, x)
b
− −
2a
b
−4ac + b2
,− +
2a
2a
−4ac + b2
2a
#
38 ■ Python for Mathematics
3.3.7
Usage
1
expression.subs({variable: value})
Jupyter input
1
2
quadratic = a * x ** 2 + b * x + c
quadratic.subs({a: 1, b: sympy.S(7) / 8, c: 0})
x2 +
3.4
7x
8
EXERCISES
If you are not sure how to do something, have a look at the “How To” section.
1. Simplify the following expressions.
(a) √33
78
(b) 21222−32
(c) 80
(d) a4 b−2 + a3 b2 + a4 b0
2. Solve the following equations.
(a) x + 3 = −1
(b) 3x2 − 2x = 5
(d) 4x3 + 7x − 24 = 1
(b) By completing the square, show that the minimum point of f (x) is (−2, 9)
5. Consider: f (x) = −3x2 + 24x − 97
(a) Calculate the discriminant of the equation −3x2 + 24x − 97 = 0. What does this
tell you about the solutions to the equation? What does this tell you about the
graph of f (x)?
(b) By completing the square, show that the maximum point of f (x) is (4, −49).
(a) Given that f (0) = 0 and f (3) = 0 obtain the values of a and b.
(b) By completing the square, confirm that graph of f (x) has a line of symmetry at
x = 32 .
3.5
FURTHER INFORMATION
3.5.1
When you run the import sympy command you are telling Python that you want to use a
specific set of tools. You will see other examples of this throughout this book.
One of the
advantages of having code in libraries is that it is more efficient for Python to
only use what
is needed. There are two types of Python libraries:
• Those that are parts of the so-called “standard library”: these are parts of
Python
itself.
• Those that are completely separate: sympy is one such example of this.
3.5.2
sympy is the library for symbolic mathematics. There are other python libraries for
carrying
out mathematics in Python. For example, compute the value of the following
expression:
√
( 2 + 2)2 − 2
You could compute this using the math library (for the square root tool):
Jupyter input
1
import math
2
3
(math.sqrt(2) + 2) ** 2 - 2
9.65685424949238
You could also make use of the fact that you do not need a square root tool at all:
√
( 2 + 2)2 − 2 = (21/2 + 2)2 − 2
40 ■ Python for Mathematics
Jupyter input
1
(2 ** (1 / 2) + 2) ** 2 - 2
9.65685424949238
You see that in both those instances, you have a numeric value for the expression
that
seems to be precise up to 14 decimal places.
However, that is not the exact value of that expression. The exact value of the
expression
needs to be computed symbolically:
Jupyter input
1
import sympy
2
3
4
expression = (sympy.sqrt(2) + 2) ** 2 - 2
sympy.expand(expression)
√
4+4 2
This is one example of why sympy is an effective tool for mathematicians. The other
one
seen in this chapter is being able to compute expressions with no numerical value
at all:
Jupyter input
1
2
3
a = sympy.Symbol("a")
b = sympy.Symbol("b")
sympy.factor(a ** 2 - b ** 2)
(a − b) (a + b)
3.5.3
There a number of resources available from which you can learn to use sympy. In
some
instances you will not see import sympy but instead you will see from sympy import
*.
This it not a good way to do it.
What this does is taking all the tools inside of sympy and putting it at the same
level of
all the other tools available to you. The problem with doing this is that it no
longer makes
your code clear. An example of this are trigonometric functions. These exist in a
number
of libraries:
Algebra ■ 41
Jupyter input
1
import math
Jupyter input
1
import sympy
Jupyter input
1
sympy.cos(0)
Jupyter input
1
math.cos(0)
1.0
One of these tools allows you to carry out exact computations:
Jupyter input
1
sympy.cos(sympy.pi / 4)
√
2
2
Jupyter input
1
math.cos(math.pi / 4)
0.7071067811865476
If you chose to import all the functionality using from sympy import *, then you
cannot
tell immediately which function you are using (except from its output):
42 ■ Python for Mathematics
Jupyter input
1
Jupyter input
1
Jupyter input
1
cos(pi / 4)
0.7071067811865476
In that case the second import has overwritten the first.
It is never recommended to use import * which makes your code less clear and
you are more likely to make mistakes when your code is not clear.
3.5.4
In some cases you might want to directly access the items in a solution set. For
example,
consider the equation (x − 1)(x − 2).
Jupyter input
1
import sympy
2
3
4
5
6
7
x = sympy.Symbol("x")
expression = (x - 1) * (x - 2)
equation = sympy.Eq(expression, 0)
set_of_solutions = sympy.solveset(equation, x)
set_of_solutions
{1, 2}
The set of solutions has value the set of solutions of the equation. If you wanted
to
access them directly, you can use the following:
Algebra ■ 43
Jupyter input
1
2
tuple_of_solutions = set_of_solutions.args
tuple_of_solutions
(1, 2)
This creates a finite ordered tuple of the solutions. You can use concepts that are
covered in Chapter 6 and access them directly. Because there are two roots you can
use the
following to create two new variables:
Jupyter input
1
x1, x2 = tuple_of_solutions
Jupyter input
1
expression.subs({x: x1})
Jupyter input
1
expression.subs({x: x2})
0
Note that this is not always possible to get a finite ordered tuple of the
solutions, for
example, there are some equations where the set of solutions is an infinite set:
Jupyter input
1
2
3
#
#
# #
#
5π ##
15π ##
10nπ +
n ∈ Z ∪ 10nπ +
n∈Z
2 #
2 #
44 ■ Python for Mathematics
3.5.5
In some resources you will see that instead of import sympy people use: import
sympy as
sym. This is called aliasing. This is common and takes advantage of the fact that
Python
can import a library and give it an alias/nickname at the same time:
Usage
1
Jupyter input
1
2
3
sym.cos(sym.pi / 4)
√
2
2
There is nothing stopping you using whatever alias you want:
Jupyter input
1
2
3
a_poor_name_choice.cos(a_poor_name_choice.pi / 4)
√
2
2
It is important when aliasing to use accepted conventions for these nicknames. For
sympy, an accepted convention is indeed import sympy as sym.
CHAPTER
Calculus
Here you will see how to instruct a computer to carry out these techniques.
In this chapter you will cover:
• Taking limits of functions.
• Differentiating functions.
4.1
TUTORIAL
You will solve the following problem using a computer to assist with the technical
aspects:
Consider the function f (x) = 24x(a−4x)+2(a−8x)(b−4x)
(b−4x)4
2
df
1. Given that dx
|x=0 = 0, ddxf2 |x=0 = −1 and that b > 0 find the values of a and b.
Sympy is once again the library you will use for this. You will start by creating a
variable
expression that has the value of the expression of f (x):
DOI: 10.1201/9781003451860-4
45
46 ■ Python for Mathematics
Jupyter input
1
2
3
4
5
6
x = sym.Symbol("x")
a = sym.Symbol("a")
b = sym.Symbol("b")
expression = (24 * x * (a - 4 * x) + 2 * (a - 8 * x) * (b - 4 * x)) /
#→
((b - 4 * x) ** 4)
expression
(b − 4x)
You will use sympy.diff to calculate the derivative. This tool takes two inputs:
• the first is the expression you are differentiating. Essentially this is the
numerator of
df
dx .
• the second is the variable you are differentiating with respect to. This is the
denomidf
nator of dx
.
You have imported import sympy as sym so you are going to write sym.diff:
Jupyter input
1
2
derivative = sym.diff(expression, x)
derivative
(b − 4x)
(b − 4x)
sym.factor(derivative)
#
#
16 −3ab − 12ax + b2 + 16bx + 16x2
5
(−b + 4x)
You will now create the first equation, which is obtained by substituting x = 0
into the
value of the derivative and equating that to 0:
Calculus ■ 47
Jupyter input
1
2
Jupyter input
1
sym.factor(first_equation)
16 · (3a − b)
=0
b4
Now you are going to create the second equation, substituting x = 0 into the value
of
the second derivative. Calculate the second derivative by passing a third
(optional) input
to sym.diff:
Jupyter input
1
2
second_derivative = sym.diff(expression, x, 2)
second_derivative
#
#
10·(12x(a−4x)+(a−8x)(b−4x))
64 −1 − 8(−a+b+4x)
+
2
b−4x
(b−4x)
4
(b − 4x)
Jupyter input
1
2
64 ·
#
8(−a+b)
10a
b −1−
b
b4
= −1
48 ■ Python for Mathematics
Jupyter input
1
sym.solveset(first_equation, a)
# #
b
3
Now to substitute that value for a and solve the second equation for b:
Jupyter input
1
2
192
= −1
b4
Jupyter input
1
sym.solveset(second_equation, b)
#
√ √
√ √
√ √
√ √
4
4
4
4
−2 2 · 3, 2 2 · 3, −2 2 · 3i, 2 2 · 3i
√ √
√ √
4
Recalling the question you know that b > 0, thus b = 2 2 4 3 and a = 2 23 3 . You
will
substitute these values back and finish the question:
Jupyter input
1
2
3
4
5
6
7
expression = expression.subs(
{
a: 2 * sym.sqrt(2) * sym.root(3, 4) / 3,
b: 2 * sym.sqrt(2) * sym.root(3, 4),
}
)
expression
# #
##
#
√ √
√ √
√ √ #
4
4
24x −4x + 2 2·3 3 + −16x + 4 2·3 3 −4x + 2 2 · 4 3
√ √ #4
#
−4x + 2 2 · 4 3
You are using the sym.root command for the generic nth root. You can confirm this:
Calculus ■ 49
Jupyter input
1
Jupyter input
1
−1
Now you will calculate the using sym.limit, this takes three inputs:
• The expression you are taking the limit of.
• The variable that is changing.
Jupyter input
1
sym.limit(expression, x, 0)
√
3
36
Jupyter input
1
sym.limit(expression, x, sym.oo)
0
Now you are going to calculate the indefinite integral using sympy.integrate. This
tool takes two inputs as:
#b
• the first is the expression you’re integrating. This is the f in a f dx.
• the second is the remaining information needed to calculate the integral: x.
50 ■ Python for Mathematics
Jupyter input
1
sym.factor(sym.integrate(expression, x))
√ √ #
#
x 6x − 2 · 4 3
#
#
√ √
√
√
3
12 · 4x3 − 6 2 · 4 3x2 + 6 3x − 2 · 3 4
If you want to calculate a definite integral, then instead of passing the single
variable
you pass a tuple which contains the variables as the bounds of integration:
Jupyter input
1
#
#
√
√
√ √
3
5 −5000 2 · 4 3 − 1200 3 + 75 2 · 3 4 + 119997
− #
√
√
√ √ ##
√
√
√ √ #
3
3
2 −32000 − 120 3 + 2 · 3 4 + 2400 2 · 4 3 −500 − 30 3 + 2 · 3 4 + 150 2 · 4 3
This tutorial has:
• Simplified a rational quotient;
4.2
HOW TO
4.2.1
You can calculate the derivative of an expression using sympy.diff which takes, an
expression, a variable and a degree.
Usage
1
+2x+1
The default value of degree is 1. For example, to compute d(4x dx
:
Calculus ■ 51
Jupyter input
1
2
3
4
5
x = sym.Symbol("x")
expression = 4 * x ** 3 + 2 * x + 1
sym.diff(expression, x)
12x2 + 2
2
Jupyter input
1
sym.diff(expression, x, 2)
24x
4.2.2
Usage
1
sympy.integrate(expression, variable)
Jupyter input
1
4x3 + 2x + 1dx:
sym.integrate(expression, x)
x4 + x2 + x
4.2.3
Calculate the definite integral of an expression.
You can calculate the definite integral of an expression using sympy.integrate. The
first
argument is an expression but instead of passing a variable as the second argument
you
pass a tuple with the variable as well as the upper and lower bounds of
integration.
52 ■ Python for Mathematics
Usage
1
Jupyter input
1
#4
0
4x3 + 2x + 1dx:
276
4.2.4
Use ∞
Usage
1
sympy.oo
For example:
Jupyter input
1
sym.oo
4.2.5
You can calculate using sympy.limit. The first argument is the expression, then the
variable
and finally the expression the variable tends to.
Usage
1
−2(x−h)−1
For example, to compute limh→0 4x +2x+1−4(x−h)
:
h
Calculus ■ 53
Jupyter input
1
2
h = sym.Symbol("h")
expression = (4 * x ** 3 + 2 * x + 1 - 4 * (x - h) ** 3 - 2 * (x - h) #→
1) / h
sym.limit(expression, h, 0)
12x2 + 2
4.3
EXERCISES
2
df d f
1. For each of the following functions calculate dx
, dx2 and
(a) f (x) = x
1
(b) f (x) = x 3
(c) f (x) = 2x(x − 3)(sin(x) − 5)
√
(d) f (x) = 3x3 + 6 x + 3
f (x)dx.
(a) Create a variable turning points which has value the turning points of f (x).
(b) Create variable intersection points which has value of the points where f (x)
and g(x) intersect.
(c) Using your answers to part b. calculate the area of the region between f and g.
Assign this value to a variable area betyouen.
4.4
FURTHER INFORMATION
4.4.1
Usage
1
sympy.plot(expression)
Jupyter input
1
2
3
4
x = sym.Symbol("x")
sym.plot(x ** 2 + 3 * x + 1)
120
100
f x
( )
80
60
40
20
10.0
7.5
5.0
2.5
0
0.0
2.5
5.0
7.5
Jupyter input
1
10.0
Calculus ■ 55
120
100
f x
( )
80
60
40
20
4
This plotting solution is good if you want to take a look at a function quickly
but it is not recommended. The main python library for plotting is called
matplotlib
and is covered in a chapter of the online version of the book.
CHAPTER
Matrices
Here you will see how to instruct a computer to carry out these techniques. In this
chapter you will cover:
• Creating matrices.
• Manipulating matrices.
5.1
TUTORIAL
DOI: 10.1201/9781003451860-5
Matrices ■ 57
(d) a = 3.
sympy is once again the library you will use for this. You will start by defining
the matrix
A:
Jupyter input
1
2
3
4
a = sym.symbol("a")
A = sym.matrix([[a, 1, 1], [1, a, 1], [1, 1, 2]])
You can now create a variable and assign it the value of the determinant of A:
Jupyter input
1
2
determinant = A.det()
determinant
2a2 − 2a
A matrix is singular if it has 0. You can find the values of a for which this
occurs:
Jupyter input
1
sym.solveset(determinant, a)
{0, 1}
Thus, it is not possible to find the inverse of A for a ∈ {0, 1}. However for a =
2:
Jupyter input
1
A.subs({a: 2})
2
1
1
1
2
1
1
1
2
58 ■ Python for Mathematics
Jupyter input
1
A.subs({a: 2}).inv()
3
4
− 1
4
− 14
− 14
3
4
− 14
− 14
− 14
3
4
Jupyter input
1
1
0
0
and for a = 3:
0
1
0
0
0
1
Jupyter input
1
A.subs({a: 3}).inv()
5
12
− 1
12
− 16
− 16
− 16
1
− 12
5
12
− 16
2
3
Jupyter input
1
1
0
0
0
1
0
0
0
1
Matrices ■ 59
5.2
HOW TO
5.2.1
Create a matrix
You create a matrix using the sympy.Matrix tool. Combine this with nested square
brackets
[] so that every row is also inside square brackets.
Usage
1
sympy.Matrix([values])
4
8
12
Jupyter input
1
2
3
4
1
5
9
2
6
10
3
7
11
4
8
12
Jupyter input
1
2
3
4
5
6
7
5.2.2
B = sym.Matrix(
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
)
Usage
1
2
matrix = sympy.Matrix([values])
matrix.det()
Jupyter input
1
2
−24
5.2.3
Usage
1
2
matrix = sympy.Matrix([values])
matrix.inv()
For example, to calculate the inverse of:
#
1/2
5
1
0
Matrices ■ 61
Jupyter input
1
2
1
0
5
1
1 − 10
5.2.4
To multiple a matrix by a scalar use the * operator. For example, to multiply the
following
matrix by 6:
#
#
1/5 1
1
1
Jupyter input
1
2
#6
5
5.2.5
6
6
Jupyter input
1
2
3
1
1
1
1
62 ■ Python for Mathematics
5.2.6
To multiply matrices together you use the @ operator. For example, to compute:
1/5 1
4/5 0
1
1
0
0
Jupyter input
1
matrix @ other_matrix
4
25
4
5
5.2.7
0
0
Create a vector
Jupyter input
1
2
3
2
1
5.2.8
Solve a linear system
To solve a given linear system that can be represented in matrix form, create the
corresponding matrix and vector and the matrix. For example, to solve the following
equations:
x + 2y = 3
3x + y + 2z = 4
−y + z = 1
Matrices ■ 63
Jupyter input
1
2
3
− 53
7
5.3
3
10
3
EXERCISES
i. a = 3
ii. a = 2
a 2
4. The matrix D is given by D = 3 1
0 −1
(a) Find D−1 .
0
2 where a ̸= 2.
1
5.4
FURTHER INFORMATION
5.4.1
In Python it is possible to write statements that are ignored using the # symbol.
This creates
something called a “comment”. For example:
Jupyter input
1
Comments like these often do not add to the readability of the code. In fact they
can
make the code less readable or at worse confusing [4].
In this section of the book there is in fact no need for comments like this as you
are
mainly using tools that are well-documented. Furthermore when using Jupyter
notebooks
you can add far more to the readability of the code by adding prose alongside our
code
instead of using small brief inline comments.
This does not mean that readability of code is not important.
Being able to read and understand written code is important.
In Chapter 12, you will start to write functions and emphasis will be given there
on readability and documenting (as opposed to commenting) the code written. A
specific discussion
about using a tool called a docstring as opposed to a comment will be covered.
In Chapters 15 and 17, there is more information on how to ensure code is readable
and
understandable.
5.4.2
With sympy it is in fact possible to use the * operator for matrix multiplication:
Matrices ■ 65
Jupyter input
1
2
3
4
5
#4
25
4
5
0
0
However, there are other libraries that can be used for linear algebra and in those
libraries
the * does not do matrix multiplication, it does element-wise multiplication
instead. So for
clarity it is preferred to use @ throughout.
5.4.3
numpy is one of the most popular and important libraries in the Python ecosystem.
It is
in fact the best library to use when doing linear algebra as it is computationally
efficient,
however, it cannot handle symbolic variables which is why you are seeing how to use
Sympy
here. An introduction to numpy is covered in a chapter of the online version of the
book.
CHAPTER
Combinatorics
• Directly computing n Pi .
6.1
TUTORIAL
You will solve the following problem using a computer to illustrate how a computer
can be
used to solve combinatorial problems:
The digits 1, 2, 3, 4, and 5 are arranged in random order, to form a five-digit
number.
1. How many different five-digit numbers can be formed?
2. How many different five-digit numbers are:
(a) Odd
(b) Less than 23000
Firstly you are going to get the 5 digits. Python has a tool for this called range
which
directly gives the integers from a given bound to another:
Jupyter input
1
2
66
digits = range(1, 6)
digits
DOI: 10.1201/9781003451860-6
Combinatorics ■ 67
range(1, 6)
At present that is only the instructions for generating the integers from 1 to 5
(the 6 is
a strict upper bound). You can transform this to a tuple, using the tuple tool:
Jupyter input
1
tuple(range(1, 6))
(1, 2, 3, 4, 5)
The question is asking for all the permutations of size 5 of that set. The main
tool for
this is a library specifically designed to iterate over objects in different ways:
itertools.
Jupyter input
1
import itertools
2
3
4
permutations = itertools.permutations(digits)
permutations
<itertools.permutations at 0x103a548b0>
That is again only the set of instructions, to view the actual permutations you
will
transform this into a tuple. You will overwrite the value of permutations to not be
the
instructions but the actual tuple of all the permutations:
Jupyter input
1
2
permutations = tuple(permutations)
permutations
((1, 2, 3, 4, 5),
(1, 2, 3, 5, 4),
(1, 2, 4, 3, 5),
(1, 2, 4, 5, 3),
...
(5, 4, 2, 3, 1),
(5, 4, 3, 1, 2),
(5, 4, 3, 2, 1))
Now to answer the question you need to find out how many tuples are in that tuple.
You do this using the Python len tool which returns the length of something:
68 ■ Python for Mathematics
Jupyter input
1
len(permutations)
120
You can confirm this to be correct as you know that there are 5! ways of arranging
those
numbers. The math library has a factorial tool:
Jupyter input
1
import math
2
3
math.factorial(5)
120
In order to find out how many 5 digit numbers are odd you are going to compute the
following sum:
#
π5 mod 2
π∈Π
Where Π is the set of permutations and π5 denotes the 5th (and last) element of the
permutation. So for example, if the first element of Π was (1, 2, 3, 4, 5) then π5
= 5 and
5 mod 2 = 1. To do this, you use the sum tool in python coupled with the
expressions for
and in. You also access the 5th element of a given permutation using [4] (the first
element
is indexed by 0, so the 5th is indexed by 4):
Jupyter input
1
72
You can again check this theoretically, there are three valid choices for the last
digit of
a given tuple to be odd: 1, 3 and 5. For each of those, there are then four choices
for the
remaining digits:
Jupyter input
1
72
math.factorial(4) * 3
Combinatorics ■ 69
To compute the number of digits that are less than or equal to 23000 you compute a
similar sum except you use the <= operator and also convert the tuple of digits to
a number
in base 10:
(π1 , π2 , π3 , π4 , π5 ) → π1 104 + π2 103 + π3 102 + π4 10 + π5
Thus you are going to compute the following sum:
#
Jupyter input
1
sum(
1
for permutation in permutations
if permutation[0] * 10 ** 4
+ permutation[1] * 10 ** 3
+ permutation[2] * 10 ** 2
+ permutation[3] * 10
+ permutation[4]
<= 23000
2
3
4
5
6
7
8
9
10
30
You can again confirm this theoretically, for a given tuple to be less than 23000
that is
only possible if the first digit is 1 or 2:
• If it is 1, then any of the other 4! permutations of the other digits is valid;
• If it is 2, then the second digit must be 1 and any of the other 3! permutations
of the
other digits is valid.
Jupyter input
1
(math.factorial(4) + math.factorial(3))
30
In this tutorial you have
• Created permutations of a given tuples;
6.2
HOW TO
6.2.1
Create a tuple
Usage
1
For example:
Jupyter input
1
2
6.2.2
If you need to you can access elements of a collection using [] brackets. The first
element
has index 0:
Usage
1
tuple[index]
For example:
Jupyter input
1
basket[1]
'Biscuits'
6.2.3
Jupyter input
1
2
value = 5
other_value = 10
3
4
value == other_value
False
Jupyter input
1
value != other_value
True
Jupyter input
1
True
Jupyter input
1
False
Jupyter input
1
True
Jupyter input
1
False
It is also possible to combine booleans to create new booleans:
• And: first boolean and second boolean
• Or: first boolean or second boolean
Jupyter input
1
True
Jupyter input
1
False
Jupyter input
1
True or False
True
Jupyter input
1
False or False
False
Jupyter input
1
False
not True
Combinatorics ■ 73
Jupyter input
1
not False
True
6.2.4
Usage
1
range(number_of_integers)
For example:
Jupyter input
1
tuple(range(10))
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
range(N) gives the integers from 0 until N − 1 (inclusive).
It is also possible to pass two values as inputs so that you have a different lower
bound:
Usage
1
tuple(range(4, 10))
(4, 5, 6, 7, 8, 9)
It is also possible to pass a third value as a step size:
Jupyter input
1
(4, 7)
6.2.5
The python itertools library has a permutations tool that will generate all
permutations
of a given set.
74 ■ Python for Mathematics
Usage
1
itertools.permutations(iterable)
Jupyter input
1
import itertools
2
3
4
Jupyter input
1
tuple(itertools.permutations(basket, r=2))
(('Bread', 'Biscuits'),
('Bread', 'Coffee'),
('Biscuits', 'Bread'),
('Biscuits', 'Coffee'),
('Coffee', 'Bread'),
('Coffee', 'Biscuits'))
6.2.6
The python itertools library has a combinations tool that will generate all
combinations
of size r of a given set:
Usage
1
itertools.combinations(iterable, r)
For example:
Combinatorics ■ 75
Jupyter input
1
2
6.2.7
You can compute the sum of items in an iterable using the sum tool:
Jupyter input
1
sum((1, 2, 3))
6
You can also directly use the sum without specifically creating the iterable. This
corresponds to the following mathematical notation:
#
f (s)
s∈S
Jupyter input
1
n2
n=0
Jupyter input
1
385
You can compute conditional sums by only summing over elements that meet a given
condition using the following:
76 ■ Python for Mathematics
Usage
1
n2
n=0
if n odd
Jupyter input
1
165
6.2.8
Directly compute n!
Usage
1
math.factorial(N)
Jupyter input
1
import math
2
3
math.factorial(5)
120
6.2.9
Directly compute
#n#
i
Usage
1
scipy.special.comb(n, i)
For example:
Combinatorics ■ 77
Jupyter input
import scipy.special
1
2
scipy.special.comb(3, 2)
3.0
6.2.10
Directly compute n Pi
Usage
1
scipy.special.perm(n, i)
For example:
Jupyter input
1
scipy.special.perm(3, 2)
6.0
6.3
EXERCISES
2. By both generating and directly computing obtain the number of the following:
(a) All permutations of (0, 1, 2, 3, 4, 5).
(b) All permutations of (A, B, C).
(c) Permutations of size 3 of (0, 1, 2, 3, 4, 5).
(d) Permutations of size 2 of (0, 1, 2, 3, 4, 5, 6).
(e) Combinations of size 3 of (0, 1, 2, 3, 4, 5).
(f) Combinations of size 2 of (0, 1, 2, 3, 4, 5).
(g) Combinations of size 5 of (0, 1, 2, 3, 4, 5).
3. A class consists of 3 students from Ashville and 4 from Bewton. A committee of 5
students is chosen at random from the class.
78 ■ Python for Mathematics
(a) Find the number of committees that include 2 students from Ashville and 3 from
Bewton are chosen.
(b) In fact, 2 students from Ashville and 3 from Bewton are chosen. In order to
watch
a video, all 5 committee members sit in a row. In how many different orders can
they sit if no 2 students from Bewton sit next to each other.
4. Three letters are selected at random from the 8 letters of the word COMPUTER,
without
regard to order.
(a) Find the number of possible selections of 3 letters.
(b) Find the number of selections of 3 letters with the letter P.
(c) Find the number of selections of 3 letters where the 3 letters form the word
TOP.
6.4
FURTHER INFORMATION
6.4.1
You have seen in this chapter how to access a single element in a tuple. There are
various
ways of indexing tuples:
1. Indexing (seen in Section 6.2.2).
2. Negative indexing (see Section 12.2.13)
3. Slicing (see Section 12.2.14)
6.4.2
6.4.3
i2
i=1
Given by:
Jupyter input
1
338350
• The sum of the square of the integers from 1 to 100 (inclusive) if they are
prime:
100
#
i2
i=1
if i is prime
Given by:
Jupyter input
1
65796
• The sum of the square of the elements in the collection S if they are prime:
#
i2
i∈S
if i is prime
Given by:
Jupyter input
1
2
42
Probability
Probability is the study of random events. Computers are particularly helpful here
as they
can be used to carry out a number of experiments to confirm and/or explore
theoretic
results.
In practice studying probability will often involve measuring:
• expected chances of an event occurring and
7.1
TUTORIAL
You will solve the following problem using a computer to estimate the expected
probabilities:
An experiment consists of selecting a token from a bag and spinning a coin. The bag
contains 5 red tokens and 7 blue tokens. A token is selected at random from the
bag, its
colour is noted and then the token is returned to the bag.
When a red token is selected, a biased coin with probability 23 of landing heads is
spun.
When a blue token is selected a fair coin is spun.
1. What is the probability of picking a red token?
2. What is the probability of obtaining Heads?
3. If a heads is obtained, what is the probability of having selected a red token.
You will use the random library from the Python standard library to do this. First
start
off by building a Python tuple to represent the bag with the tokens. Assign this to
a variable
bag:
80
DOI: 10.1201/9781003451860-7
Probability ■ 81
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bag = (
"Red",
"Red",
"Red",
"Red",
"Red",
"Blue",
"Blue",
"Blue",
"Blue",
"Blue",
"Blue",
"Blue",
)
bag
('Red',
'Red',
'Red',
'Red',
'Red',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue')
You are using the circular brackets () and the quotation marks ". Those are
important
and cannot be omitted. The choice of brackets () as opposed to {} or [] is
important
as it instructs Python to do different things. You can use " or ’ interchangeably.
Instead of writing every copy of colour you can create a Python list which allows
you
to carry out some basic algebra on the items:
• Create a list with 5 "Red"s.
['Red',
'Red',
'Red',
'Red',
'Red',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue']
Now to sample from that use the random library which has a choice command:
Jupyter input
1
import random
2
3
random.choice(bag)
'Blue'
If you run this many times, you will not always get the same outcome:
Jupyter input
1
random.choice(bag)
Jupyter input
1
'Blue'
Jupyter input
1
bag
Probability ■ 83
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
['Red',
'Red',
'Red',
'Red',
'Red',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue',
'Blue']
In order to answer the first question (what is the probability of picking a red
token)
repeat this many times: Do this by defining a Python function (which is akin to a
mathematical function) that makes repeating code possible:
Jupyter input
1
2
3
4
5
def pick_a_token(container):
"""
A function to randomly sample from a `container`.
"""
return random.choice(container)
You can then call this function, passing bag to it as the container from which to
pick:
Jupyter input
1
pick_a_token(container=bag)
'Blue'
Jupyter input
1
pick_a_token(container=bag)
'Red'
In order to measure the probability of picking a red token repeat this not once or
twice
but tens of thousands of times. You will do this using something called a “list
comprehension” which is akin to the mathematical notation commonly used to create
sets:
84 ■ Python for Mathematics
S1 = {f (x) for x in S2 }
Jupyter input
1
2
number_of_repetitions = 10000
samples = [pick_a_token(container=bag) for repetition in
#→
range(number_of_repetitions)]
samples
Jupyter input
1
2
3
4
5
6
7
8
9
['Red',
'Red',
'Red',
...
'Blue',
'Blue',
'Red',
'Blue',
]
You can confirm that you have the correct number of samples:
Jupyter input
1
len(samples)
10000
len is the Python tool to get the length of a given Python iterable.
Using this you can now use == (double =) to check how many of those samples are
Red:
Jupyter input
1
0.4071
5
:
You have sampled a probability of around .41. The theoretic value is 5+7
Probability ■ 85
Jupyter input
1
5 / (5 + 7)
0.4166666666666667
To answer the second question (What is the probability of obtaining Heads?). You
need
to make use of another Python tool: an if statement. This will let you write a
function
that does precisely what is described in the problem:
• Choose a token;
For the second random selection (flipping a coin) you will not choose from a list
but
instead select a random number between 0 and 1.
Jupyter input
1
2
3
4
def sample_experiment(bag):
"""
This samples a token from a given bag and then
selects a coin with a given probability.
5
6
7
8
9
10
11
12
13
14
15
16
17
if selected_token == "Red":
probability_of_selecting_heads = 2 / 3
else:
probability_of_selecting_heads = 1 / 2
18
19
20
21
22
23
Jupyter input
1
sample_experiment(bag=bag)
('Red', 'Heads')
Jupyter input
1
sample_experiment(bag=bag)
('Red', 'Tails')
You can now find out the probability of selecting heads by carrying out a large
number
of repetitions and checking which ones have a coin that is heads:
Jupyter input
1
0.576
You can compute this theoretically as well, the expected probability is:
Jupyter input
1
2
3
41
72
Jupyter input
1
41 / 72
0.5694444444444444
You can also use the samples to calculate the conditional probability that a token
was
read if the coin is heads. This is done again using the list comprehension notation
but
including an if statement which emulates the mathematical notation:
S3 = {x ∈ S1 | if some property of x holds}
Probability ■ 87
Jupyter input
1
0.49236111111111114
This is given theoretically by:
P (Red|Heads) =
P (Heads|Red)P (Red)
P (Heads)
Jupyter input
1
20
41
Jupyter input
1
20 / 41
0.4878048780487805
In this tutorial you have
• Randomly sampled from an iterable.
7.2
HOW TO
7.2.1
Create a list
To create a list which is an ordered collection of objects that can be changed use
the []
brackets.
88 ■ Python for Mathematics
Usage
1
For example:
Jupyter input
1
2
Jupyter input
1
2
basket.append("Tea")
basket
Jupyter input
1
2
3
other_basket = ["Toothpaste"]
basket = basket + other_basket
basket
Jupyter input
1
As for tuples you can also access elements using their indices:
Jupyter input
1
basket[3]
Probability ■ 89
Jupyter input
1
7.2.2
'Tea'
Define a function
Usage
1
2
3
4
5
6
Jupyter input
1
2
3
4
5
def x_cubed(x):
"""
A function to return x ˆ 3
"""
return x ** 3
It is important to include the docstring as this allows us to make sure our code is
clear.
You can access that docstring using help:
Jupyter input
1
help(x_cubed)
7.2.3
Call a function
Once a function is defined call it using the ():
90 ■ Python for Mathematics
Usage
1
For example:
Jupyter input
1
x_cubed(2)
Jupyter input
1
x_cubed(5)
125
Jupyter input
1
2
3
4
x = sym.Symbol("x")
x_cubed(x)
x3
7.2.4
Jupyter input
1
2
3
4
5
6
7
8
def f(x):
"""
A function that returns x ˆ 3 if x is negative.
Otherwise it returns x ˆ 2.
"""
if x < 0:
return x ** 3
return x ** 2
Jupyter input
1
f(0)
Jupyter input
1
f(-1)
-1
Jupyter input
1
f(3)
9
Here is another example of a function that returns the price of a given item, if
the item
is not specific in the function then the price is 0:
Jupyter input
1
2
3
def get_price_of_item(item):
"""
Returns the price of an item:
4
5
6
7
8
- 'Bread': 2
- 'Biscuits': 3
- 'Coffee': 1.80
- 'Tea': .50
92 ■ Python for Mathematics
- 'Toothpaste': 3.50
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Jupyter input
1
get_price_of_item("Toothpaste")
3.5
Jupyter input
1
get_price_of_item("Biscuits")
Jupyter input
1
get_price_of_item("Rollerblades")
7.2.5
Usage
1
This corresponds to building a set from another set in the usual mathematical
notation:
S2 = {f (x) for x in S1 }
If f (x) = x − 5 and S1 = {2, 5, 10}, then you would have:
S2 = {−3, 0, 5}
In Python this is done as follows:
Jupyter input
1
Jupyter input
1
2
3
[-3, 0, 5]
You can combine this with functions to write succinct efficient code.
For example, you can compute the price of a basket of goods using the following:
Jupyter input
1
2
3
7.2.6
You can compute the sum of items in a list using the sum tool:
Jupyter input
1
sum([1, 2, 3])
6
You can also directly use sum without specifically creating the list. This
corresponds to
the following mathematical notation:
94 ■ Python for Mathematics
f (s)
s∈S
Jupyter input
1
Jupyter input
1
but it is more efficient. Here is an example of getting the total price of a basket
of goods:
Jupyter input
1
2
3
6.5
7.2.7
To randomly sample from any collection of items use the random library and the
choice
tool.
Usage
1
random.choice(collection)
Jupyter input
1
import random
2
3
4
'Toothpaste'
Probability ■ 95
7.2.8
To sample a random number between 0 and 1 use the random library and the random
tool.
Usage
1
random.random()
For example:
Jupyter input
1
import random
2
3
random.random()
0.7558634290782174
7.2.9
The random numbers processes generated by the Python random module are what are
called pseudo random which means that it is possible to get a computer to reproduce
them
by seeding the random process.
Usage
1
random.seed(int)
Jupyter input
1
import random
2
3
4
random.seed(0)
random.random()
0.8444218515250481
Jupyter input
1
random.random()
0.7579544029403025
96 ■ Python for Mathematics
Jupyter input
1
2
random.seed(0)
random.random()
0.8444218515250481
7.3
EXERCISES
1. For each of the following, write a function, and repeatedly use it to simulate
the
probability of an event occurring with the following chances:
(a) 27
1
(b) 10
1
(c) 100
(d) 1
7.4
FURTHER INFORMATION
7.4.1
Two of the most used Python iterables are lists and tuples. In practice they have a
number of similarities, they are both ordered collections of objects that can be
used in list
comprehensions as well as in other ways.
• Tuples are immutable
• Lists are mutable
This means that once created tuples cannot be changed and lists can.
As a general rule of thumb: if you do not need to modify your iterable then use a
tuple
as they are more computationally efficient.
7.4.2
In the tutorial and elsewhere you created a list of booleans and then took the sum.
Here
are some of the steps:
Jupyter input
1
Jupyter input
1
2
Jupyter input
1
sum(booleans)
2
This has in fact counted the True values as 1 and the False values as 0.
Jupyter input
1
int(True)
98 ■ Python for Mathematics
Jupyter input
1
int(False)
7.4.3
In functions you use the return statement. This does two things:
1. Assigns a value to the function run;
2. Ends the function.
The print statement only displays the output.
As an example create the following set:
S = {f (x) for x ∈ {0, π/4, π/2, 3π/4}}
where f (x) = cos2 (x).
The correct way to do this is:
Jupyter input
1
2
3
4
5
6
7
8
def f(x):
"""
Return the square of the cosine of x
"""
return sym.cos(x) ** 2
9
10
11
12
Jupyter input
1
2
3
4
5
def f(x):
"""
Return the square of the cosine of x
"""
print(sym.cos(x) ** 2)
6
7
8
1
1/2
0
1/2
The function has been run and it displays the output.
However, if you look at what S is, you see that the function has not returned
anything:
Jupyter input
1
7.4.4
When using the Python random module, you are in fact generating a pseudo random
process.
True randomness is actually not common.
Pseudo randomness is an important area of mathematics as strong algorithms that
create
unpredictable sequences of numbers are vital to cryptographic security.
The specific algorithm used in Python for randomness is called the Mersenne twister
algorithm and is state of the art.
7.4.5
In Python it is possible to write statements that are ignored using the # symbol.
This creates
something called a “comment”. For example:
Jupyter input
1
2
Jupyter input
1
2
3
def sample_experiment(bag):
# Select a token
selected_token = pick_a_token(container=bag)
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Now if you try to access the help for the function you will not get it:
Jupyter input
1
help(sample_experiment)
Jupyter input
1
2
3
4
def sample_experiment(bag):
"""
This samples a token from a given bag and then
selects a coin with a given probability.
5
6
8
9
10
11
12
13
14
15
16
17
if selected_token == "Red":
probability_of_selecting_heads = 2 / 3
else:
probability_of_selecting_heads = 1 / 2
18
19
20
21
22
23
Sequences
8.1
TUTORIAL
You will solve the following problem using a computer a programming technique
called
recursion.
A sequence a1 , a2 , a3 , . . . is defined by:
#
a1 = k,
an+1 = 2an –7, n ≥ 1,
where k is a constant.
ak :
You will use Python to define a function that reproduces the mathematical
definition of
Jupyter input
1
2
3
4
5
6
7
8
102
a_1 = k
a_n = 2a_{n-1} - 7
"""
if n == 1:
DOI: 10.1201/9781003451860-8
Sequences ■ 103
9
10
return k_value
return 2 * generate_a(k_value, n - 1) - 7
Jupyter input
1
generate_a(k_value=4, n=3)
-5
You can use this to compute a5 for k = 1:
Jupyter input
1
generate_a(k_value=1, n=5)
-89
Finally it is also possible to pass a symbolic value to k value. This allows you to
answer
the first question:
Jupyter input
1
2
3
4
k = sym.Symbol("k")
generate_a(k_value=k, n=2)
2k − 7
Likewise for a3 :
Jupyter input
1
generate_a(k_value=k, n=3)
104 ■ Python for Mathematics
4k − 21
For the last question start by computing the sum:
4
#
ar
r=1
Jupyter input
1
15k − 77
This allows you to create the given equation and solve it:
Jupyter input
1
2
{8}
In this tutorial you have
• Defined a function using recursion.
8.2
HOW TO
8.2.1
• A base case: a particular term that does not need the recursive rule to be
calculated.
Consider the following mathematical expression:
#
a1 = 1,
an = 2an−1 , n > 1,
Sequences ■ 105
Jupyter input
1
2
3
def generate_sequence(n):
"""
Generate the sequence defined by:
4
5
6
a_1 = 1
a_n = 2 a_{n - 1}
7
8
9
10
11
12
Jupyter input
1
2
8.3
EXERCISES
1. Using recursion, obtain the first ten terms of the following sequences:
a1 = 1,
(a)
an = 3an−1 , n > 1
b1 = 3,
(b)
bn = 6bn−1 , n > 1
c1 = 3,
(c)
cn = 6cn−1 + 3, n > 1
d0 = 3,
(d)
dn = dn−1 + 3, n > 0
3. A 40-year building programme for new houses began in Oldtown in the year 1951
(Year 1) and finished in 1990 (Year 40).
The number of houses built each year form an arithmetic sequence with first term a
and common difference d.
Given that 2400 new houses were built in 1960 and 600 new houses were built in
1990,
find:
(a) The value of d.
(b) The value of a.
(c) The total number of houses built in Oldtown over 40 years.
4. A sequence is given by:
#
x1 = 1
xn+1 = xn (p + xn ), n > 1
for p ̸= 0.
8.4
FURTHER INFORMATION
8.4.1
Jupyter input
1
2
3
def generate_sequence(n):
"""
Generate the sequence defined by:
4
5
6
a_1 = 1
a_n = 3 a_{n - 1}
7
8
9
10
11
12
Jupyter input
1
Jupyter input
1
2
3
def calculate_sequence(n):
"""
Calculate the nth term of the sequence defined by:
4
5
6
a_1 = 1
a_n = 3 a_{n - 1}
7
8
9
10
11
12
a_n = 3 ˆ n
"""
return 3 ** (n - 1)
Jupyter input
1
Jupyter input
1
%timeit [generate_sequence(n) for n in range(1, 25)]
19.2 µs ± 246 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
108 ■ Python for Mathematics
Jupyter input
1
5.63 µs ± 44.7 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In practice:
• Using recursion is powerful as it can be used to directly implement recursive
definitions.
• Using iteration is more computationally efficient but it is not always
straightforward
to obtain an iterative formula.
8.4.2
What is caching?
One way to address this is by using caching, where a function stores the result of
a
computation so that if it is called again with the same input, it can quickly
retrieve the
stored value instead of recalculating it. Python has a caching tool available in
the functools
library:
Jupyter input
1
import functools
2
3
4
5
6
def generate_sequence(n):
"""
Generate the sequence defined by:
7
8
9
a_1 = 1
a_n = 3 a_{n - 1}
10
11
12
13
14
15
16
17
18
19
@functools.lru_cache()
def cached_generate_sequence(n):
Sequences ■ 109
20
21
"""
Generate the sequence defined by:
22
23
24
a_1 = 1
a_n = 3 a_{n - 1}
25
26
27
28
29
30
Timing both these approaches confirms a substantial increase in time for the cached
version.
Jupyter input
1
20.5 µs ± 381 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
Jupyter input
1
934 ns ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
CHAPTER
Statistics
9.1
TUTORIAL
You will solve the following problem using a computer to do some of the more
tedious
calculations.
Anna is investigating the relationship between exercise and resting heart rate. She
takes
a random sample of 19 people in her year group and records for each person
• their resting heart rate, h beats per minute.
• The median
• The quartiles
DOI: 10.1201/9781003451860-9
Statistics ■ 111
TABLE 9.1
76.0
72.0
71.0
74.0
71.0
69.0
68.0
68.0
66
64
65
63
63
62
65
63
65
64
64
5
5
21
30
42
20
20
35
80.0
120.0
140.0
180.0
205.0
225.0
237.0
280.0
300.0
356.0
360.0
74
72
70
68
66
64
62
0
Figure 9.1
50
100
150
200
250
Minutes of exercise: m
300
350
• The maximum
• The minimum
y = ln(h)
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
h = (
76.0,
72.0,
71.0,
74.0,
71.0,
69.0,
68.0,
68.0,
66.0,
64.0,
65.0,
63.0,
63.0,
62.0,
65.0,
63.0,
65.0,
64.0,
64.0,
)
m = (
5,
5,
21,
30,
42,
20,
20,
35,
80,
120,
Statistics ■ 113
140,
180,
205,
225,
237,
280,
300,
356,
360,
33
34
35
36
37
38
39
40
41
42
The main tool you are going to use for this problem is statistics.
Jupyter input
1
import statistics as st
Jupyter input
1
st.mean(h)
67.0
Jupyter input
1
st.mean(m)
140.05263157894737
To calculate the median:
Jupyter input
1
65.0
st.median(h)
114 ■ Python for Mathematics
Jupyter input
1
st.median(h)
120
To calculate the quartiles, use statistics.quantiles and specify that you want to
separate the data into n = 4 quarters.
Jupyter input
1
st.quantiles(h, n=4)
Jupyter input
1
st.quantiles(m, n=4)
Jupyter input
1
st.stdev(h)
4.123105625617661
Jupyter input
1
st.stdev(m)
124.46662813970593
To calculate the sample variance:
Jupyter input
1
17.0
st.variance(h)
Statistics ■ 115
Jupyter input
1
st.variance(m)
15491.941520467837
To compute the maximum:
Jupyter input
1
max(h)
76.0
Jupyter input
1
max(m)
360
To compute the minimum:
Jupyter input
1
min(h)
62.0
Jupyter input
1
min(m)
5
To compute the Pearson Coefficient of correlation use statistics.correlation:
Jupyter input
1
st.correlation(h, m)
-0.7686142969026402
116 ■ Python for Mathematics
Jupyter input
1
2
3
import math
x = [math.log(value) for value in m]
y = [math.log(value) for value in h]
Jupyter input
1
Jupyter input
1
slope
-0.03854770754231997
The intercept is:
Jupyter input
1
intercept
4.368415819445762
Recall the transformation of the variables:
x = ln(m)
y = ln(h)
Jupyter input
1
2
3
4
5
6
7
8
h = sym.Symbol("h")
m = sym.Symbol("m")
a = sym.Symbol("a")
b = sym.Symbol("b")
x = sym.ln(m)
y = sym.ln(h)
Jupyter input
1
2
Jupyter input
1
sym.exp(line.lhs)
Jupyter input
1
sym.expand(sym.exp(line.rhs))
eb ea log (m)
Which can be rewritten as:
eb ma
Substituting the values for the slope and intercept into these expressions gives
the
required relationship:
118 ■ Python for Mathematics
Jupyter input
1
78.9185114479915
m0.03854770754232
Figure 9.2 is a plot that shows this relationship.
Data collected by Anne
76
74
72
70
68
66
64
62
0
Figure 9.2
50
100
150
200
250
Minutes of exercise: m
300
350
A scatter plot of the data collected by Anne with the fitted relationship.
9.2
HOW TO
9.2.1
9.2.1.1
Calculate a mean
You can calculate the mean of a set of data using statistics.mean which takes an
iterable.
Statistics ■ 119
Usage
1
statistics.mean(data)
For example, to calculate the mean of (1, 5, 10, 12, 13, 20):
Jupyter input
1
import statistics as st
2
3
4
10.166666666666666
9.2.1.2
Calculate a median
You can calculate the median of a set of data using statistics.median which takes
an
iterable.
Usage
1
statistics.median(data)
For example, to calculate the median of (1, 5, 10, 12, 13, 20):
Jupyter input
1
import statistics as st
2
3
4
11.0
9.2.1.3
You can calculate the population standard deviation of a set of data using
statistics.pstdev which takes an iterable.
Usage
1
statistics.pstdev(data)
120 ■ Python for Mathematics
For example, to calculate the population standard deviation of (1, 5, 10, 12, 13,
20):
Jupyter input
1
import statistics as st
2
3
4
6.039223643997813
9.2.1.4
You can calculate the sample standard deviation of a set of data using
statistics.stdev
which takes an iterable.
Usage
1
statistics.stdev(data)
For example, to calculate the sample standard deviation of (1, 5, 10, 12, 13, 20):
Jupyter input
1
import statistics as st
2
3
4
6.6156380392723015
9.2.1.5
Usage
1
statistics.pvariance(data)
For example, to calculate the population variance of (1, 5, 10, 12, 13, 20):
Statistics ■ 121
Jupyter input
1
import statistics as st
2
3
4
36.47222222222222
9.2.1.6
You can calculate the sample variance of a set of data using statistics.variance
which
takes an iterable.
Usage
1
statistics.variance(data)
For example, to calculate the sample variance of (1, 5, 10, 12, 13, 20):
Jupyter input
1
import statistics as st
2
3
4
43.766666666666666
9.2.1.7
You can calculate the maximum of a set of data using max which takes an iterable:
Usage
1
max(data)
For example, to calculate the maximum of (1, 5, 10, 12, 13, 20):
Jupyter input
1
2
data = (1, 5, 10, 12, 13, 20)
max(data)
122 ■ Python for Mathematics
20
9.2.1.8
You can calculate the minimum of a set of data use min which takes an iterable:
Usage
1
min(data)
For example, to calculate the minimum of (1, 5, 10, 12, 13, 20):
Jupyter input
1
2
1
9.2.1.9
Calculate quantiles
To calculate cut points dividing data into n intervals of equal probability you can
use
statistics.quantiles which takes an iterable and a number of intervals.
Usage
1
statistics.quantiles(data, n)
For example, to calculate the cut points that divide (1, 5, 10, 12, 13, 20) into
four intervals
of equal probability (in this case the quantiles are called quartiles):
Jupyter input
1
import statistics as st
2
3
4
9.2.2
Usage
1
statistics.covariance(first_data_set, second_data_set)
For example, to calculate the sample covariance of x = (1, 5, 10, 12, 13, 20) and y
=
(3, −3, 6, −2, 1, 2):
Jupyter input
1
import statistics as st
2
3
4
5
1.1666666666666674
9.2.3
To calculate the correlation coefficient of two data sets you can use
statistics.correlation
which takes two iterables.
Usage
1
statistics.correlation(first_data_set, second_data_set)
For example, to calculate the correlation coefficient of x = (1, 5, 10, 12, 13, 20)
and y =
(3, −3, 6, −2, 1, 2):
Jupyter input
1
import statistics as st
2
3
4
5
0.05325222181462787
9.2.4
To carry out linear regression to fit a line of best fit between two data sets you
can use
statistics.linear regression which takes two iterables and returns a tuple with the
slope
and the intercept of the line.
124 ■ Python for Mathematics
Usage
1
statistics.linear_regression(first_data_set, second_data_set)
For example, to calculate the correlation coefficient of x = (1, 5, 10, 12, 13, 20)
and y =
(−3, −14, −31, −6, −40, −70):
Jupyter input
1
import statistics as st
2
3
4
5
LinearRegression(slope=-3.2338156892612333, intercept=5.543792840822537)
30
40
50
60
70
2.5
5.0
7.5
Figure 9.3
9.2.5
10.0
12.5
15.0
17.5
20.0
A line of best fit.
A normal distribution with mean µ and standard deviation σ can be created using
statistics.NormalDist:
Statistics ■ 125
Usage
1
statistics.NormalDist(mu, sigma)
Jupyter input
1
import statistics as st
2
3
4
NormalDist(mu=3.0, sigma=0.5)
9.2.6
Usage
1
2
For example, to find the probability that X < 2 for a normally distributed random
variable with µ = 3 and σ = .5:
Jupyter input
1
import statistics as st
2
3
4
0.02275013194817921
9.2.7
Use the inverse cumulative distribution function of a normal distribution
For an instance of a normal distribution with mean µ and σ, the inverse cumulative
distribution function which for a given p gives x such that p = P (X < x) can be
accessed using
statistics.NormaDist.inv cdf.
126 ■ Python for Mathematics
Usage
1
2
For example, to find the value of X for which a normally distributed random
variable
with µ = 3 and σ = .5 will be less than with probability .7.
Jupyter input
1
import statistics as st
2
3
4
3.2622002563540202
9.3
EXERCISES
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data_set_1 = (
74,
-7,
58,
82,
60,
3,
49,
85,
24,
99,
73,
76,
11,
-4,
61,
87,
93,
13,
1,
Statistics ■ 127
28,
21
22
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data_set_2 = (
65,
59,
81,
81,
76,
93,
91,
88,
55,
97,
86,
94,
79,
54,
63,
56,
58,
77,
85,
88,
)
data_set_3 = (
0.31,
-0.13,
0.19,
0.46,
-0.27,
-0.06,
0.20,
0.42,
-0.07,
0.11,
128 ■ Python for Mathematics
-0.11,
-0.43,
-0.36,
0.45,
-0.42,
0.11,
0.08,
0.31,
0.48,
0.17,
12
13
14
15
16
17
18
19
20
21
22
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data_set_4 = (
2,
4,
2,
2,
2,
2,
2,
3,
2,
2,
2,
4,
2,
4,
2,
2,
3,
4,
3,
4,
)
Calculate:
• The mean,
• The median,
• The max,
• The min,
• The population standard deviation,
• The sample standard deviation,
Statistics ■ 129
2. Calculate the sample covariance and the correlation coefficient for the
following pairs
of data sets from question 1:
(a) data set 1 and data set 4
(b) data set 3 and data set 4
(c) data set 2 and data set 3
(d) data set 1 and data set 2
3. For each of the data sets from question 1 obtain the covariance and correlation
coefficient for the data set with itself.
4. Obtain a for the pairs of data sets from question 2.
5. Given a collection of 250 individuals whose height is normally distributed with
mean
165 and standard deviation 5. What is the expected number of individuals with
height
between 150 and 160?
6. Consider a class test where the scores are normally distributed with mean 65 and
standard deviation 5.
(a) What is the probability of failing the class test (a score less than 40)?
(b) What proportion of the class gets a first class mark (a score above 70)?
(c) What is the mark that only 10% of the class would expect to get more than?
9.4
FURTHER INFORMATION
9.4.1
What is the difference between the sample and the population variance and
standard deviation?
σ2
The population standard deviation is given by:
#
#N
2
i=1 (xi − x̄ )
σ=
N
The population variance is given by:
σ2
130 ■ Python for Mathematics
The population standard deviation and/or variance should be used when the data set
in question is for the entire population.
The sample standard deviation and/or variance should be used when the data set in
question is a sample of the entire population. The modification in the calculation
is to
counteract a potential bias.
9.4.2
The main library for plotting is called matplotlib. Below is some code to plot the
data and
regression line for two collections of data. Figure 9.4 gives the output.
4
2
0
2
4
6
0
Figure 9.4
Jupyter input
1
2
3
4
5
x = (0, 2, 2, 3, 4, 5.6)
y = (-1, -3, -4, -5, 4, -7)
6
7
8
9
10
11
12
13
14
plt.figure()
plt.scatter(x, y)
Statistics ■ 131
15
16
17
9.4.3
The statsmodels library allows for a wider breadth of statistical analysis. The
scikit-learn
library is arguably one of the most popular python libraries. It is technically a
library for
machine learning and not statistics.
9.4.4
In a lot of cases the difference here is more question of vocabulary than actual
tangible
differences.
For example, the scikit-learn library has a tool for linear regression as does the
statsmodels and the statistics library.
In practice statistics is often more descriptive, for example, using linear
regression to
understand the relationship between two variables. Whereas machine learning is more
predictive, for example using liner regression to predict one variable value from
another.
A lot of modern applied mathematics uses tools such as neural networks which are
considered to be from the field of machine learning.
CHAPTER
10
Differential Equations
10.1
TUTORIAL
You will solve the following problem using a computer to do some of the more
tedious
calculations.
A container has volume V of liquid which is poured in at a rate proportional to e−t
(where t is some measurement of time). Initially the container is empty and after t
= 3 time
units the volume of liquid is 15.
3
−t
1. Show that V (t) = −15e
1−e3 (1 − e ).
You first need to create the differential equation described in the question:
Jupyter input
1
2
3
4
5
t = sym.Symbol("t")
k = sym.Symbol("k")
V = sym.Function("V")
6
7
132
DOI: 10.1201/9781003451860-10
Differential Equations ■ 133
d
V (t) = ke−t
dt
In order to solve the differential equation write:
Jupyter input
1
sym.dsolve(differential_equation, V(t))
V (t) = C1 − ke−t
Note that the question gives an initial condition: “initially the container is
empty” which
corresponds to V (0) = 0. You can pass this to the call to solve the differential
equation:
Jupyter input
1
2
condition = {V(0): 0}
particular_solution = sym.dsolve(differential_equation, V(t),
#→
ics=condition)
sym.simplify(particular_solution)
V (t) = k − ke−t
You also know that V (3) = 15 which corresponds to the following equation:
Jupyter input
1
2
k
− 3 + k = 15
e
You can solve this equation to find a value for k:
Jupyter input
1
sym.simplify(sym.solveset(equation, k))
134 ■ Python for Mathematics
15e3
1 − e3
Jupyter input
limit = sym.limit((-15 * sym.exp(3) / (1- sym.exp(3))) *
#→
sym.exp(-t)), t, sym.oo)
limit
−
This is approximately:
15e3
1 − e3
Jupyter input
float(limit)
15.78593544736884
In this tutorial you have
• Created a differential equation.
10.2
HOW TO
10.2.1
Usage
1
sympy.Function("y")
For example:
(1 -
Differential Equations ■ 135
Jupyter input
1
2
3
4
y = sym.Function("y")
y
y
You can pass symbolic variables to this symbolic function:
Jupyter input
1
2
x = sym.Symbol("x")
y(x)
y(x)
Now, you can create the derivative of a symbolic function:
Jupyter input
1
sym.diff(y(x), x)
d
y(x)
dx
10.2.2
Usage
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
Jupyter input
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
8
9
lhs = sym.diff(y(x), x)
rhs = sym.cos(x) * y(x)
differential_equation = sym.Eq(lhs, rhs)
differential_equation
d
y(x) = y(x) cos (x)
dx
10.2.3
Usage
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
dy
For example, to solve the differential equation: dx
= cos(x)y write:
Jupyter input
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
8
9
lhs = sym.diff(y(x), x)
rhs = sym.cos(x) * y(x)
differential_equation = sym.Eq(lhs, rhs)
sym.dsolve(differential_equation, y(x))
10.2.4
Usage
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
dy
For example, to solve the differential equation: dx
= cos(x)y with the condition y(5) = π
write:
Jupyter input
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
8
lhs = sym.diff(y(x), x)
rhs = sym.cos(x) * y(x)
differential_equation = sym.Eq(lhs, rhs)
9
10
11
condition = {y(5): sym.pi}
sym.dsolve(differential_equation, y(x), ics=condition)
10.3
EXERCISES
dy
50
(c) dx
= x−
10
138 ■ Python for Mathematics
dy
(d) dx
= y 2 ln(x)
dy
(e) dx
= (1 + y)2
10.4
FURTHER INFORMATION
10.4.1
You can solve it using sym.dsolve but instead of passing a single differential
equation,
pass an iterable of multiple equations:
Differential Equations ■ 139
Jupyter input
1
2
3
4
5
y = sym.Function("y")
x = sym.Function("x")
6
7
8
9
t = sym.Symbol("t")
alpha = sym.Symbol("alpha")
beta = sym.Symbol("beta")
10
11
12
13
14
15
system_of_equations = (
sym.Eq(sym.diff(y(t), t), alpha * x(t)),
sym.Eq(sym.diff(x(t), t), beta * y(t)),
)
conditions = {y(0): 250, y(1): 300}
16
17
18
# √
# √
# √
# √
√
50β 6e αβ − 5 et αβ
50β 5e αβ − 6 e αβ e−t αβ
#
#
+
x(t) = −
√ # 2√αβ
√ # 2√αβ
αβ e
−1
αβ e
−1
Jupyter input
1
y_solution
# √
# √
# √
# √
√
50 · 6e αβ − 5 et αβ
50 · 5e αβ − 6 e αβ e−t αβ
√
√
+
y(t) =
e2 αβ − 1
e2 αβ − 1
10.4.2
Jupyter input
1
2
3
4
y = sym.Function("y")
x = sym.Symbol("x")
5
6
7
which is a linear combination of Ai and Bi which are special functions called the
Airy
functions of the first and second kind.
Using scipy.integrate it is possible to solve this differential equation
numerically.
dy
so that the second-order differential equation can be
First, define a new variable u = dx
expressed as a system of single-order differential equations:
#
du
dx = xy
dy
dx = u
Now define a python function that returns the right hand side of that system of
equations:
Jupyter input
1
2
3
4
5
6
You can pass this to scipy.integrate.odeint which is a tool that carries out
numerical
integration of differential equations. Note, that it is incapable of dealing with
symbolic
variables, thus an initial numeric value of (u, y) is required.
Jupyter input
1
2
import numpy as np
import scipy.integrate
3
4
5
6
7
8
xs = np.linspace(0, 1, 50)
states = scipy.integrate.odeint(diff, y0=initial_state, t=xs)
Differential Equations ■ 141
Here, you make use of numpy to create a collection of x values over which to carry
out
the numerical integration.
This returns an array of values of states corresponding to (u, y).
Jupyter input
1
states
array([[ 0.1
, -0.5
],
[ 0.09989617, -0.49795991],
[ 0.09958578, -0.49592403],
[ 0.09907053, -0.49389658],
...
[-0.09525243, -0.46835567],
[-0.10414704, -0.47038996],
[-0.11327831, -0.47260818],
[-0.12265169, -0.47501521],
[-0.13227299, -0.47761605]])
Figure 10.1 shows a plot of the above with a comparison to the exact expected
values
(obtained using the of the first and second kind).
numeric
closed form
0.46
0.47
0.48
0.49
0.50
0.0
Figure 10.1
0.2
0.4
0.6
0.8
1.0
http://taylorandfrancis.com
III
Building Tools
143
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
CHAPTER
11
In the previous chapters you have explored a number of tools that allow you to use
mathematical knowledge more efficiently. In this part of the book you will start to
gain the
knowledge necessary to build such tools.
In this chapter you will cover:
• Creating variables.
11.1
TUTORIAL
You will here use a computer to gain some evidence to help tackle the following
problem.
Consider the following polynomial:
p(n) = n2 + n + 41
1. Verify that p(n) is for n ∈ Z up until n = 20.
Jupyter input
1
2
3
4
5
def p(n):
"""
Return the value of n ˆ 2 + n + 41 for a given value of n.
"""
return n ** 2 + n + 41
DOI: 10.1201/9781003451860-11
145
146 ■ Python for Mathematics
Jupyter input
1
2
3
sym.isprime(3)
True
Jupyter input
1
sym.isprime(4)
False
Now to answer the first question you will use a list comprehension to create a list
of
boolean variables that confirm if p(n) is prime.
This is similar to what was done in Chapter 7.
Jupyter input
1
2
[True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True,
True]
You can use the all tool to check if all the boolean values are true:
Jupyter input
1
all(checks)
True
Using list comprehensions is a mathematical way of repeating code but at times it
might prove useful to repeat code in a different way using a standard for
statement.
In that case you can essentially repeat the previous exercise using:
Jupyter input
1
2
3
4
5
6
checks = []
for n in range(21):
value = p(n)
is_prime = sym.isprime(value)
checks.append(is_prime)
all(checks)
True
The main difference between the two approaches is that you can include multiple
lines
of indented code to be repeated for every value of n in range(21).
A for loop or a list comprehension should be used when you know how many
repetitions
are necessary.
To answer the second question you will repeat the code until the value of p(n) is
no
longer prime.
Jupyter input
1
2
3
4
n = 0
while sym.isprime(p(n)):
n += 1
n
40
A while loop should be used when you do not know how many times a repetition
should be made but you know under what conditions it should be made.
Indeed for n = 40 you have:
148 ■ Python for Mathematics
Jupyter input
1
p(n)
1681
and
Jupyter input
1
sym.isprime(p(n))
False
sympy can also factor the number:
Jupyter input
1
sym.factorint(p(n))
{41:2}
Jupyter input
1
41 ** 2
Indeed:
Jupyter input
1
41 ** 2
1681
11.2
HOW TO
11.2.1
To define an integer variable use the = operator which is the assignment operator.
Create
the name of the variable, then the assignment operator followed by the integer
value.
Usage
1
name_of_variable = int_value
Variables, Conditionals and Loops ■ 149
For example:
Jupyter input
year = 2020
year
1
2
2020
When choosing a variable name there are some rules to follow:
• No spaces, use
instead.
• Use explicit names that clearly describe what the variable is. Try not to use i,
a
unless those refer to specific mathematical variables.
• Do not use CamelCase but use snake case when combining words. This follows
the Python convention called PEP8.
11.2.2
To define a float variable use the = operator which is the assignment operator.
Create the
name of the variable, then the assignment operator followed by the real value.
Usage
1
name_of_variable = float_value
For example:
Jupyter input
1
2
cms_in_an_inch = 2.54
cms_in_an_inch
2.54
11.2.3
To define a string variable use the = operator which is the assignment operator.
Create
the name of the variable, then the assignment operator followed by the string which
is a
combination of characters between quotation marks.
150 ■ Python for Mathematics
Usage
1
name_of_variable = string_value
For example:
Jupyter input
1
2
capital_of_dominica = "roseau"
capital_of_dominica
'roseau'
11.2.4
Usage
1
name_of_variable = boolean_value
For example:
Jupyter input
1
2
john_nash_has_a_nobel = True
john_nash_has_a_nobel
Jupyter input
1
True
Section 6.2.3 gives an overview of how to create boolean variables from other
variables.
11.2.5
You can get the type of a variable using the type tool.
Variables, Conditionals and Loops ■ 151
Usage
1
type(object)
Jupyter input
1
2
year = 2020
type(year)
int
Jupyter input
1
2
cms_in_an_inch = 2.54
type(cms_in_an_inch)
float
Jupyter input
1
2
capital_of_dominica = "roseau"
type(capital_of_dominica)
str
If a numeric variable is given with any decimal part (including 0), then it is
considered
to be a float.
11.2.6
3. Multiplication, 3 × 5: 3 * 5;
4. Division, 20/5: 20 / 5;
5. Exponentiation, 24 : 2 ** 4;
6. Integer remainder, 5 mod 2: 5 % 2; and
3
Jupyter input
1
2
3
cms_in_an_inch = 2.54
average_male_height_in_cms = 170
average_male_height_in_inches = average_male_height_in_cms /
#→
cms_in_an_inch
average_male_height_in_inches
66.92913385826772
This is similar to what what is shown in Section 2.3.6.
Some languages, including Python have a shortcut to manipulate a variable “in
place”.
The following takes the variable money and replaces it by 3 times money:
Jupyter input
1
money *= 3
Jupyter input
1
11.2.7
money = money * 3
Variables can be used in strings using string formatting. There are numerous ways
this
can be done in Python but the current best practice is to use f-strings.
Usage
1
f"{variable}"
For example, the following creates a string that uses a random number:
Jupyter input
1
import random
2
3
4
5
6
random.seed(0)
random_number = random.random()
string = f"Here is a random number: {random_number}"
string
Variables, Conditionals and Loops ■ 153
11.2.8
Usage
1
all(iterable)
Usage
1
any(iterable)
For example:
Jupyter input
1
2
False
Jupyter input
1
any(iterable)
True
11.2.9
Usage
1
2
3
4
5
if boolean:
code to run if boolean is true
else:
code to run if boolean is false
code to run after either of two previous code blocks are run.
Jupyter input
1
import random
2
3
4
5
6
7
8
9
10
random.seed(0)
random_number = random.randint(0, 100)
is_even = random_number % 2 == 0
if is_even:
message = f"The random number ({random_number}) is even."
else:
message = f"The random number ({random_number}) is odd."
message
11.2.10
Given an iterable, it is possible to repeat some code for every item in the
iterable. This
is done using what is called a for loop. Following the for a placeholder variable
is given,
then followed by the in keyword and the iterable. After that the indented code that
will be
repeated for every value of the iterable.
Usage
1
2
For example, the following will print a message for every given value in the
iterable:
Variables, Conditionals and Loops ■ 155
Jupyter input
1
2
3
4
5
11.2.11
To repeat code while a condition holds a while loop should be used. Similarly to
the if
statement, following a while, a boolean variable is expected, if that boolean is
True, then
the indented code that follows is repeated. After it is run, the boolean is checked
once more.
When the boolean is False the indented code is skipped.
Usage
1
2
3
while boolean:
code to repeat before checking boolean once more
code to run once boolean is False
Here is some code that repeatedly selects a random integer until that number is
even.
156 ■ Python for Mathematics
Jupyter input
1
import random
2
3
4
5
6
7
8
9
random.seed(4)
selected_integer = random.randint(0, 10)
number_of_selections = 1
while selected_integer % 2 == 1:
selected_integer = random.randint(0, 10)
number_of_selections += 1
number_of_selections
11.2.12
To create a new iterable of pairs of items from two separate iterables use zip:
Usage
1
zip(iterable_1, iterable_2)
For example:
Jupyter input
1
2
3
4
[('Carrots', 4),
('Potatoes', 2),
('Strawberries', 6),
('Juice', 3),
('Ice cream', 10)]
11.2.13
Usage
1
enumerate(iterable)
Variables, Conditionals and Loops ■ 157
For example:
Jupyter input
1
2
[(0, 'Carrots'),
(1, 'Potatoes'),
(2, 'Strawberries'),
(3, 'Juice'),
(4, 'Ice cream')]
11.3
EXERCISES
1. Using a for loop print the types of the variables in each of the following
iterables:
(a) iterable = (1, 2, 3, 4)
(b) iterable = (1, 2.0, 3, 4.0)
(c) iterable = (1, "dog", 0, 3, 4.0)
2. Consider the following polynomial:
3n3 − 183n2 + 3318n − 18757
(a) Use the sympy.isprime function to find the lowest positive integer value of n
for
which the absolute value of that polynomial is not prime?
(b) How many unique primes up until the first non-value are there? (Hint: the set
tool might prove useful here.)
3. Check the following identity for each value of n ∈ {0, 10, 100, 2000}:
n
#
i=0
i=
n(n + 1)
2
4. Check the following identity for all positive integer values of n less than
5000:
n
#
i=0
i2 =
n(n + 1)(2n + 1)
6
5. Repeat the experiment of selecting a random integer between 0 and 10 until it is
even
1000 times. What is the average number of times taken to select an even number?
158 ■ Python for Mathematics
11.4
FURTHER INFORMATION
11.4.1
The for loop allows you to iterate over any selection of objects. Some languages do
not
have a generic for loop. In some cases it is only possible to iterate over a set of
integers
(similar to the for i in range(n) pattern) or to only use a while loop.
Because of this, it is often the case that you will see code that uses while loops
instead
of for loops. For example:
Jupyter input
1
2
3
4
5
6
7
8
number_of_seasons = len(seasons)
i = 0
while i < number_of_seasons:
season = seasons[i]
print(season)
i += 1
Winter
Spring
Summer
Autumn
The above code is equivalent to:
Jupyter input
1
2
3
Winter
Spring
Summer
Autumn
While it is possible to use a while loop instead of a for loop there are no
advantages to
doing that and in fact only disadvantages:
• Using the while loop requires iterating over the iterable twice: the first time
when
counting the length of it using len and the second time during the while statement
itself.
• There is more potential for error in the code: it would not be unlikely to have
an off
by one error in the boolean condition.
• It is less readable.
Variables, Conditionals and Loops ■ 159
• Use a while loop when only know a specific condition under which you should
iterate.
11.4.2
Jupyter input
1
2
boolean = False
boolean == True
False
Thus when using if or while statements you might sometimes see things like the
following:
Jupyter input
1
import random
2
3
4
5
6
7
8
9
random.seed(4)
selected_integer = random.randint(0, 10)
number_of_selections = 1
while (selected_integer % 2 == 1) == True:
selected_integer = random.randint(0, 10)
number_of_selections += 1
number_of_selections
2
or:
Jupyter input
1
2
3
4
5
6
7
random.seed(4)
selected_integer = random.randint(0, 10)
number_of_selections = 1
while (selected_integer % 2 == 1):
selected_integer = random.randint(0, 10)
number_of_selections += 1
number_of_selections
2
However, this is not best practice. A better approach is to use is instead of ==:
160 ■ Python for Mathematics
Jupyter input
1
import random
2
3
4
5
6
7
8
9
random.seed(4)
selected_integer = random.randint(0, 10)
number_of_selections = 1
while (selected_integer % 2 == 1) is True:
selected_integer = random.randint(0, 10)
number_of_selections += 1
number_of_selections
2
This is due to the fact that when using == variables that are not booleans will be
converted to booleans and this might not be the expected behaviour.
For example:
Jupyter input
1
2
number = 0
number == False
True
however:
Jupyter input
1
False
number is False
CHAPTER
12
In the previous chapters you have explored a number of tools that allow you to use
your
mathematical knowledge more efficiently. In this chapter you continue to gain the
knowledge
necessary to build these tools covering the following topics:
In this chapter you will cover:
• Defining and using functions.
12.1
TUTORIAL
Similarly to Chapter 11, you will use a computer to gain numerical evidence for a
problem.
Consider the following sequence:
a0 = 0,
a1 = 1,
an = an−1 + an−2 , n ≥ 2
Verify that the following identity holds for n ≤ 500:
n
i=0
ai = an+2 − 1
DOI: 10.1201/9781003451860-12
161
162 ■ Python for Mathematics
Jupyter input
1
import functools
2
3
4
5
6
7
8
@functools.lru_cache()
def get_fibonacci(n):
"""
A function to give the nth Fibonacci number using the recursive
definition.
9
10
11
12
13
14
15
Parameters
---------n: int
The index of the Fibonacci number
16
17
18
19
20
21
22
23
24
25
26
Returns
------int
The nth Fibonacci number
"""
if n == 0:
return 0
if n == 1:
return 1
return get_fibonacci(n - 1) + get_fibonacci(n - 2)
This uses caching in the function definition with lru cache. This is not necessary
but
makes the code more efficient. Caching is covered in Section 8.4.2.
You will print the first ten numbers to ensure everything is working correctly:
Jupyter input
1
2
0
1
1
2
3
5
8
13
for n in range(10):
print(get_fibonacci(n))
Functions and Data Structures ■ 163
21
34
Now write a function that returns a boolean: True if the equation holds for a given
value
of n, False otherwise.
Jupyter input
1
2
3
4
def check_theorem(n):
"""
A function that generate the lhs and rhs of the
following relationship:
5
6
7
8
9
10
11
12
13
14
15
Parameters
---------n: int
The index n for which the theorem is to be verified.
16
17
18
19
20
21
22
23
Returns
------bool
Whether or not the theorem holds for a given n.
"""
sum_of_fibonacci = sum(get_fibonacci(i) for i in range(n + 1))
return sum_of_fibonacci == get_fibonacci(n + 2) - 1
Generate checks for n ≤ 500:
Jupyter input
1
2
[True,
True,
True,
...
True,
True,
True]
Confirm that all the booleans in checks are True:
164 ■ Python for Mathematics
Jupyter input
1
all(checks)
True
12.2
HOW TO
Two important data structures have already been seen in previous chapters:
• Tuples: Section 6.2.1.
• Lists: Section 7.2.1.
12.2.1
Define a function
12.2.2
Write a docstring
Usage
1
2
3
4
5
6
7
8
9
10
11
Parameters
---------parameter1 : <type of parameter1>
<description of parameter1>
parameter2 : <type of parameter2>
<description of parameter2>
...
12
13
14
15
16
Returns
------<type of what the function returns>
<description of what the function returns>
17
18
19
20
"""
INDENTED BLOCK OF CODE
return output
For example, here is how to write a function that returns x3 for a given x:
Functions and Data Structures ■ 165
Jupyter input
1
2
3
4
def x_cubed(x):
"""
Calculates and returns the cube of x. Does this by using Python
exponentiation.
5
6
7
8
9
Parameters
---------x : float
The value of x to be raised to the power 3
10
11
12
13
14
15
16
12.2.3
Returns
------float
The cube.
"""
return x ** 3
Create a tuple
12.2.4
Create a list
12.2.5
12.2.6
Combine lists
Given two lists it is possible to combine them to create a new list using the +
operator:
Usage
1
first_list + other_list
Jupyter input
1
2
3
4
first_list = [1, 2, 3]
other_list = [5, 6, 100]
combined_list = first_list + other_list
combined_list
[1, 2, 3, 5, 6, 100]
12.2.7
Usage
1
a_list.append(element)
Jupyter input
1
2
3
12.2.8
Usage
1
a_list.remove(element)
Jupyter input
1
2
3
12.2.9
Sort a list
Usage
1
a_list.sort()
Here is an example:
Jupyter input
1
2
3
Jupyter input
1
2
names.sort(reverse=True)
names
12.2.10
Usage
1
sorted(iterable)
Here is an example:
Jupyter input
1
2
12.2.11
12.2.12
Usage
1
iterable.index(element)
Here is an example:
Jupyter input
1
2
2
Recall that python uses 0-based indexing. The first element in an iterable has
index 0.
12.2.13
Usage
1
iterable[-index_from_end]
Jupyter input
1
2
'Juice'
12.2.14
Slice an iterable
To create a new iterable from an iterable use [] and specify a start (inclusive)
and end
(exclusive) pair of indices.
Usage
1
iterable[include_start_index: exclusive_end_index]
For example:
Jupyter input
1
2
12.2.15
Usage
1
len(iterable)
For example:
170 ■ Python for Mathematics
Jupyter input
1
2
12.2.16
Create a set
A set is a collection of distinct objects. This can be created in Python using the
set command
on any iterable. If there are non-distinct objects in the iterable, then this is an
efficient way
to remove duplicates.
Usage
1
set(iterable)
Jupyter input
1
2
3
{1, 2, 3, 4, 10}
12.2.17
Do set operations
Jupyter input
1
2
3
4
set_1 | set_2
{1, 2, 3, 4, 5, 6, 7, 8, 9}
Jupyter input
1
{4, 5}
Jupyter input
1
set_1 - set_2
{1, 2, 3}
Jupyter input
1
False
12.2.18
Lists and tuples allow us to immediately recover a value given its position. Hash
tables
allow us to create arbitrary key value pairs so that given any key you can
immediately
recover the value. This is called a dictionary in Python and is created using {}
which takes
a collection of key: value pairs.
Usage
1
For example, the following dictionary maps pet names to their ages:
172 ■ Python for Mathematics
Jupyter input
1
2
Jupyter input
1
ages["Riggins"]
4
If a key is used to recover the value with [] but the key is not in the dictionary,
then
an error will be raised.
12.2.19
Usage
1
dictionary[key]
It is also possible to use the get method. The get method can also be passed the
value
of a default variable to return when the key is not in the hash table:
Jupyter input
1
dictionary.get(key, default)
For example:
Jupyter input
1
2
-1
12.2.20
To iterate over the keys in a hash table, use the keys() method:
Usage
1
dictionary.keys()
For example:
Jupyter input
1
2
12.2.21
To iterate over the values in a hash table, use the values() method:
Usage
1
dictionary.values()
For example:
Jupyter input
1
2
Jupyter input
1
dict_values([4, 7, 7])
12.2.22
To iterate over pairs of keys and values in a hash table, use the items() method:
174 ■ Python for Mathematics
Usage
1
dictionary.items()
For example:
Jupyter input
1
2
12.3
EXERCISES
n(n + 1)
2
3. Verify the following that the following identify holds for positive integer
values n ≤
500:
n
#
i=0
Ti =
n(n + 1)(n + 2)
6
12.4
FURTHER INFORMATION
12.4.1
The format used to write a docstring described in Section 12.2.2. is the one
specified by the
Numpy project.
Amongst other things you can see how to specify further functionality:
• How to indicate if a parameter is optional.
12.4.2
The darglint library can be used to check if docstrings match a given format.
12.4.3
Apart from removing duplicates and set operations what are the advantages
of using set?
Jupyter input
1
2
numbers = list(range(100000))
%timeit 100000 in numbers
474 µs ± 2.51 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Jupyter input
1
2
numbers = set(range(100000))
%timeit 100000 in numbers
15.2 ns ± 0.121 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
CHAPTER
13
Object-Oriented
Programming
In the first part of this book you covered a number of tools that allow you to
carry out
mathematical techniques. One example of this is the sympy.Symbol object that
creates a
symbolic variable. In this chapter you will see how to define similar mathematical
objects.
In this chapter you will cover:
• Creating objects.
13.1
TUTORIAL
You will write some code to create and manipulate expressions. With sympy this is
not
necessary as all functionality required is available within sympy; however, this
will be a
good exercise in understanding how to build such functionality.
Consider the following quadratics:
f (x) = 5x2 + 2x − 7
g(x) = −4x2 − 3x + 12
h(x) = f (x) + g(x)
Without using sympy, obtain the roots for all the quadratics.
Start by defining an object to represent a quadratic. This is called a class.
176
DOI: 10.1201/9781003451860-13
Object-Oriented Programming ■ 177
Jupyter input
1
import math
2
3
4
5
class QuadraticExpression:
"""A class for a quadratic expression"""
6
7
8
9
10
11
12
13
14
15
def get_roots(self):
"""
Return the real valued roots of the quadratic expression
16
17
18
19
20
21
22
23
24
25
26
Returns
------array
The roots of the quadratic
"""
if self.discriminant >= 0:
x1 = -(self.b + math.sqrt(self.discriminant)) / (2 * self.a)
x2 = -(self.b - math.sqrt(self.discriminant)) / (2 * self.a)
return x1, x2
return ()
27
28
29
30
31
32
33
34
def __repr__(self):
"""A magic method: changes the default way an instance is
#→
displayed"""
return f"Quadratic expression: {self.a} x ˆ 2 + {self.b} x +
#→
{self.c}"
178 ■ Python for Mathematics
• get roots: this returns the two real valued roots if the is positive.
•
•
repr : a magic function that gives the string representation of the instance.
Now use this class to solve the specified problem. First create instances of the
class that
correspond to f and g. This is using the init function in the background.
Jupyter input
1
2
You can now take a look at both of these instances. This is using the
in the background:
repr
function
Jupyter input
1
Quadratic expression: 5 x ˆ 2 + 2 x + -7
Jupyter input
1
Quadratic expression: -4 x ˆ 2 + -3 x + 12
Now you are going to create h(x) = f (x) + g(x). This is using the
the background:
add
function in
Jupyter input
1
2
h = f + g
h
Quadratic expression: 1 x ˆ 2 + -1 x + 5
You can now iterate over the quadratics and find the roots. This is using the get
roots
function in the background:
Object-Oriented Programming ■ 179
Jupyter input
1
2
Jupyter input
1
h.discriminant
-19
You are going to create a new class from QuadraticExpression replacing the get
roots
function with a new one that can handle imaginary roots and update the add function
to return an instance of the new class.
Jupyter input
1
2
3
class QuadraticExpressionWithAllRoots(QuadraticExpression):
"""
A class for a quadratic expression that can return imaginary roots
4
5
6
7
The `get_roots` function returns two tuples of the form (re, im)
#→
where re is
the real part and im is the imaginary part.
"""
8
9
10
11
def get_roots(self):
"""
Return the real valued roots of the quadratic expression
12
13
14
15
16
17
18
19
20
21
Returns
------array
The roots of the quadratic
"""
if self.discriminant >= 0:
x1 = -(self.b + math.sqrt(self.discriminant)) / (2 * self.a)
x2 = -(self.b - math.sqrt(self.discriminant)) / (2 * self.a)
return (x1, 0), (x2, 0)
22
23
24
25
26
27
28
29
30
31
32
Now define the quadratics once again but using this new class:
Jupyter input
1
2
3
Jupyter input
1
Quadratic expression: 5 x ˆ 2 + 2 x + -7
Jupyter input
1
Quadratic expression: -4 x ˆ 2 + -3 x + 12
Jupyter input
1
Jupyter input
1
Quadratic expression: 1 x ˆ 2 + -1 x + 5
Object-Oriented Programming ■ 181
Jupyter input
1
class QuadraticExpressionWithAllRoots(QuadraticExpression):
You can now get all the roots for the quadratics:
Jupyter input
1
2
13.2
HOW TO
13.2.1
Define a class
Usage
1
2
3
4
5
class Name:
"""
A docstring between triple quotation to describe what the class
#→
represents
"""
INDENTED BLOCKS OF CODE
Jupyter input
1
2
3
4
13.2.2
class Country:
"""
A class to represent a country
"""
Usage
1
Name()
For example:
Jupyter input
1
2
first_country = Country()
first_country
<__main__.Country at 0x7f22a8f76e00>
Jupyter input
1
2
second_country = Country()
second_country
<__main__.Country at 0x7f22a8f76e30>
The at < main Country at 0x7f22a8f76e30> is a pointer to the location of the
instance
in memory. If you re-run the code that location will change.
13.2.3
Create an attribute
Attributes are variables that belong to instances of classes. They can be created
and accessed
using .name of variable.
For example, the following creates the attributes name and amount of magic:
Jupyter input
1
2
first_country.name = "narnia"
first_country.amount_of_magic = 500
Jupyter input
1
first_country.name
'Narnia'
Object-Oriented Programming ■ 183
Jupyter input
1
first_country.amount_of_magic
500
You can manipulate them in place:
Jupyter input
1
2
first_country.amount_of_magic += 100
first_country.amount_of_magic
600
13.2.4
Methods are functions that belong to classes. Define a function using the def
keyword (short
for define). The first variable of a method is always the specific instance that
will call the
method (it is passed implicitly).
Usage
1
2
3
4
5
6
7
class Name:
"""
A docstring between triple quotation to describe what the class
#→
represents
"""
def name(self, parameter1, parameter2, ...):
"""
<A description of what the method is.>
8
9
10
11
12
13
14
15
Parameters
---------parameter1 : <type of parameter1>
<description of parameter1>
parameter2 : <type of parameter2>
<description of parameter2>
...
16
17
18
19
20
Returns
------<type of what the function returns>
<description of what the function returns>
21
22
"""
184 ■ Python for Mathematics
23
24
For example, let us create a class for a country that has the ability to “spend”
magic:
Jupyter input
1
2
3
4
class Country:
"""
A class to represent a country
"""
5
6
7
8
Parameters
---------amount_spent : float
The amount of mana used.
"""
self.amount_of_magic -= amount_spent
10
11
12
13
14
15
Jupyter input
1
2
3
4
5
first_country = Country()
first_country.name = "Narnia"
first_country.amount_of_magic = 500
first_country.spend_magic(amount_spent=100)
first_country.amount_of_magic
400
Even though the method is defined as taking two variables as inputs: self and
amount spent you only have to explicitly pass it amount spent. The first variable
in
a method definition always corresponds to the instance on which the method exists.
13.2.5
These are referred to as dunder methods as they are all in between two underscores:
.
The method that is called when an instance is created is called init (for
initialised).
For example:
Usage
1
2
3
4
class Country:
"""
A class to represent a country
"""
6
7
8
Now instead of creating an instance and then creating the attributes you can do
those
two things at the same time, by passing the variables to the class itself (which in
turn passes
them to the init method):
Jupyter input
1
2
'Narnia'
Jupyter input
1
first_country.amount_of_magic
500
The method that returns a representation of an instance is
Jupyter input
1
2
3
4
class Country:
"""
A class to represent a country
"""
5
6
7
8
9
10
11
12
def __repr__(self):
"""Returns a string representation of the instance"""
return f"{self.name} with {self.amount_of_magic} magic."
Jupyter input
1
2
13.2.6
add
Use inheritance
Inheritance is a tool that allows you to create one class based on another. This is
done by
passing the Old class to the New class.
Usage
1
class New(Old)
method but
Jupyter input
1
2
3
4
5
class MuggleCountry(Country):
"""
A class to represent a country with no magic. It only requires the
#→
name on
initialisation.
"""
6
7
8
9
init
repr
Jupyter input
1
2
other_country = MuggleCountry("Wales")
other_country
13.3
EXERCISES
1. Use the class created in Section 13.1 to find the roots of the following
quadratics:
(a) f (x) = −4x2 + x + 6
2. Write a class for a and use it to find the roots of the following expressions:
(a) f (x) = 2x + 6
(b) g(x) = 3x − 6
3. If rain drops were to fall randomly on a square of side length 2r, the
probability of
the drops landing in an inscribed circle of radius r would be given by:
P =
πr2
π
Area of circle
= 2 =
Area of square
4r
4
Jupyter input
1
2
3
4
5
class Drop:
"""
A class used to represent a random rain drop falling on a
#→
square of
length r.
"""
6
7
8
9
10
Note that the above uses the following equation for a circle centred at (0, 0) of
radius
r:
x2 + y 2 ≤ r2
188 ■ Python for Mathematics
To approximate P create N = 1000 instances of Drops and count the number of those
that are in the circle. Use this to approximate π.
#1
4. In a similar fashion to question 3, approximate the integral 0 1 − x2 dx. Recall
that
the integral corresponds to the area under a curve.
13.4
FURTHER INFORMATION
13.4.1
13.4.2
init
or
repr
is pronounced “dun-
In methods the first variable is used to refer to the instance of a given class. It
is conventional
to use self.
As an example consider this class:
Jupyter input
1
2
3
class PetDog:
"""
A class for a Pet.
4
5
6
7
8
9
10
11
12
def __init__(self):
self.toys = []
13
14
15
16
17
18
def bark(self):
"""
Returns the string Woof.
"""
return "Woof"
19
20
21
22
23
24
Jupyter input
1
2
auraya = PetDog()
riggins = PetDog()
Jupyter input
1
auraya.toys
[]
Jupyter input
1
riggins.toys
[]
Now when you want to give riggins a toy you need to specify which of those two
empty
lists to update:
Jupyter input
1
2
riggins.give_toy("ball")
riggins.toys
['ball']
However, auraya still has no toys:
Jupyter input
1
auraya.toys
[]
When running riggins.give toy("ball"), internally the give toy method is taking
self to be riggins and so the line self.toys.append(toy) in fact is running as
riggins.toys.append(toy).
The variable name self is a convention and not a functional requirement. If you
modify
it (for example by using inheritance as explained in Section 13.2.6):
190 ■ Python for Mathematics
Jupyter input
1
2
3
class OtherPetDog(PetDog):
"""
A class for a Pet.
5
6
7
8
9
10
11
12
13
14
15
Jupyter input
1
2
riggins = OtherPetDog()
riggins.toys
[]
Jupyter input
1
2
riggins.give_toy("ball")
riggins.toys
['ball']
Indeed the line the dog in question.toys.append(toy) is run as
riggins.toys.append(toy).
You should however use self as it is convention and helps with readability of your
code.
13.4.3
Why use CamelCase for classes but snake case for functions?
13.4.4
A method is a function defined on a class and always takes a first parameter which
is the
specific instance from which the method is called.
CHAPTER
14
In the first part of this book you used Jupyter notebooks as an interface to
Python. This
has a number of advantages, the strongest of which is the ability to include both
code and
prose in the same document. From this part of the book onwards you will explore
another
approach to using Python which is to use a code editor and a command line tool as a
direct
interface to your operating system.
In this chapter you will cover:
• Using the command line.
• Using an editor.
14.1
TUTORIAL
You will here consider a problem you have already solved in Chapter 3 but use a
different
interface to do so than Jupyter. The code itself will be the same. The way you run
it will
differ.
1
Rationalise the denominator of √2+1
Open a command line tool:
1. On Windows search for Anaconda Prompt (it should be available to you after
installing
Anaconda). See Chapter 2.
2. On OS X search for terminal. See Chapter 2.
Whether or not you are using the Windows or MacOS operating system changes the
commands you need to type. First, list the directory you are currently in:
On Windows:
$ dir
On MacOS:
DOI: 10.1201/9781003451860-14
191
192 ■ Python for Mathematics
$ ls
This is similar to using your file explorer to view the contents in a given
directory.
Similarly to the way you double click on a directory in the file explorer you can
navigate to
a directory in the command line.
Throughout this book, when there are commands to be typed in a command line tool
they will be prefixed with a $. Do not type the $.
To do this you use the same command on both operating systems cd.
You will do this to navigate to your pfm directoy. For example if, as in Chapter 3,
the
pfm directory was put on the Desktop directory, you would run the following:
$ cd Desktop
$ cd pfm
The two statements are written under each other to denote that they are run one
after
the other.
You will now create a new directory:
$ mkdir scripts
Inside this directory you will run the same command as before to see the contents:
On Windows:
$ dir
On MacOS:
$ ls
Using a Command Line and an Editor ■ 193
Figure 14.1
If you have followed the steps described in Chapter 2, you will see something
similar to
Figure 14.1 or Figure 14.2.
Before continuing with this directory you are going to install a powerful code
editor.
1. Navigate to https://code.visualstudio.com.
2. Download the installer making sure it is the correct one for your operating
system
(Windows, MacOS or Linux).
3. Run the installer.
Figure 14.2
This code editor will offer you a different way to write Python code.
Open VS code and create a new file.
In it write the following (which corresponds to the solution of our problem):
194 ■ Python for Mathematics
Python file
1
2
3
4
"""
This script displays the solution to the problem considered.
"""
import sympy as sym
5
6
7
8
print("Question 1:")
expression = 1 / (sym.sqrt(2) + 1)
print(sym.simplify(expression))
Figure 14.3
Now save this as algebra.py inside the scripts directory created earlier as shown
in
Figure 14.4.
VScode now recognises the Python language and adds syntax colouring. It also
suggests
a plugin specific for the Python language as shown in Figure 14.5. There is more
information
about plugins in Section 14.2.6.
All you have done so far is write the code. You now need to tell Python to run it.
To do
this you will use the command line and run the same command on both operating
systems:
$ cd scripts
Figure 14.4
Figure 14.5
On Windows:
$ dir
On MacOS:
$ ls
$ python algebra.py
Figure 14.6
14.2
HOW TO
14.2.1
In the command line the cd command (short for “change directory”) can be used to
enter
a given directory.
Using a Command Line and an Editor ■ 197
Usage
1
$ cd <directory>
The target directory must be contained in the directory you are currently in.
For example, to change directory into a directory called pfm:
$ cd pfm
14.2.2
$ cd ..
In the command line the mkdir command (short for “make directory“) can be used to
create
a new directory.
Usage
1
$ mkdir <directory>
14.2.3
$ mkdir scripts
In the command line you can see the contents of the current directory:
• On Windows using dir
• On OS X using ls
198 ■ Python for Mathematics
14.2.4
To run code in a file type python followed by the name of the file in the command
line.
Usage
1
$ python <file.py>
14.2.5
$ python main.py
At the command line if you type python without passing a filename this will create
a prompt
in which you can directly write Python code.
$ python
When doing this, you see a prompt appear with >>>, you can directly type python
code
in there and press enter:
>>> 2 + 2
4
This interface to Python is called a Read-Eval-Print-Loop and is often referred to
as a
REPL.
Using python is the simplest of python REPLs, there are others (for example
ipython).
This interface to Python is quite limited and should only be used for quick access
to
Python as a way to run simple commands.
14.2.6
VScode is a powerful editor with a number of plugins for different languages and
functionalities.
To install a particular plugin in the menu bar, click on Code > Preferences >
Extensions.
From there you can search for a specific plugin and install it by clicking on the
install
button.
Using a Command Line and an Editor ■ 199
14.3
EXERCISES
+2
(d) 42×5
− 52
(d) Count the number of ways of picking 2 letters from “ABCD” where order does
not matter.
(e) Simulate the probability of picking a red token from a bag with 3 red tokens, 5
blue tokens and a yellow token.
(f) Obtain the first five terms of the sequence defined by:
a0 = 0,
a1 = 2,
an = 3an−1 + an−2 , n ≥ 2
4. Install the Markdown all in one plugin for markdown in VScode and then:
(a) Create a new file main.md.
(b) Write some basic markdown in it.
(c) Use the plugin to preview the rendered markdown.
14.4
FURTHER INFORMATION
14.4.1
When using a Jupyter notebook, the last line of a cell corresponds to the output of
the cell
and is automatically displayed. When running code written in an editor directly
through the
Python interpreter there is nowhere for code to be output to. Thus, you need to
specifically
tell it to display the code which is what the print statement does.
14.4.2
Can you use a Python plugin to run my code from inside my editor?
When using the Python plugin, buttons become available that let you run code
without
using the command line. Before using those buttons it is good to become comfortable
using
a command line tool to fully understand what the underlying process is.
Furthermore, at
times when debugging sometimes the user interface might be at fault.
200 ■ Python for Mathematics
14.4.3
When using the Python plugin it is actually possible to use Jupyter notebooks from
within
VScode. The notebooks will not look exactly the same but have the same
functionality as
shown in Figure 14.7.
Figure 14.7
14.4.4
A notebook in VScode.
14.4.5
When using Jupyter notebooks (see Section 2.5.7) or the markdown preview feature in
VScode, the single $ and $$ must be used as delimiters for mathematics.
CHAPTER
15
Modularisation
This is the first of three chapters that aim to move from writing code that works
to writing
software. In this particular chapter you will consider how to write your code in a
structured
way.
In this chapter you will cover:
• Importing code from python files.
15.1
TUTORIAL
You will here consider a specific problem of a general type. You will not
concentrate too
much on the writing of the code itself. Instead this chapter concentrates on how
you can
write the code as software that will do more than just solve the specific problem.
It will be
able to be used for further problems of the same type.
Consider a chain model of the Board Game “Snakes and Ladders”:
1. What is the shortest number of turns that are possible to win?
2. What is the average number of turns?
To solve this problem you will make use of the Python library numpy to carry out
efficient
numerical calculations.
The problem you are considering is in fact an application of a mathematical object
from
probability called a Chain which not be covered here; however the relevant ideas
are that
the probability of being in the 100th square after k turns can be written down as:
(πP k )100
where
π = (1, 0, . . . , 0)
# ## #
100
and P ∈ R100×100 ; Pij represents the probability of being in the ith square and
going
to the jt h square after rolling the dice.
There are snakes and ladders between the squares as given in Table 15.1.
The matrix P will look like:
DOI: 10.1201/9781003451860-15
201
202 ■ Python for Mathematics
TABLE 15.1
0
0
P = .
..
Ladder
Ladder
Ladder
Ladder
Ladder
Ladder
Ladder
Ladder
Ladder
Snake
Snake
Snake
Snake
Snake
Snake
Snake
Snake
Snake
3
15
22
25
41
53
63
76
84
11
18
28
36
77
47
83
92
99
1/6 1/6
0
1/6 1/6 1/6
..
..
..
.
.
.
0
0
...
19
37
42
64
73
74
86
91
98
7
13
12
34
16
26
39
75
70
0
0
..
.
0
...
...
..
.
0
0
0
..
.
1
Note that because of the ladder on square 3: P14 = 0 and P1,20 = 1/6. The first
row/column of P corresponds to the state of not being on the board.
A csv file containing this matrix P can be found at
https://zenodo.org/record/4236275.
To be able to answer the first question you will write a function to compute πP k
for
arbitrary π, k and P :
Python file
1
2
3
5
6
7
8
9
10
11
12
13
Parameters
---------pi : array
Starting state vector.
k : int
Number of iterations.
Modularisation ■ 203
14
15
P : array
Transition matrix
16
17
18
19
20
21
22
Returns
------array
The state vector after k iterations
"""
return pi @ np.linalg.matrix_power(P, k)
For the second question you are going make use of a theoretic result which is that
if P
is of the form:
P =
#
Q
0
R
I
Python file
1
2
3
def compute_t(P):
"""
For an absorbing Markov chain with transition rate matrix this
#→
computes the
vector t which gives the expected number of steps until absorption.
5
6
7
8
9
10
11
12
Parameters
---------P : array
Transition matrix
204 ■ Python for Mathematics
13
14
15
16
17
18
19
Returns
------array
Number of steps until absorption
"""
indices_without_1_in_diagonal = np.where(P.diagonal() != 1)[0]
Q = P[indices_without_1_in_diagonal.reshape(-1, 1),
indices_without_1_in_diagonal]
#→
20
21
22
23
number_of_rows, _ = Q.shape
N = np.linalg.inv(np.eye(number_of_rows) - Q)
return N @ np.ones(number_of_rows)
You are in fact going to modularise that function. It does three things:
• Extracts the matrix Q from P ;
• Computes N ;
• Computes t.
All of those tasks could be useful in their own right so you are going to break up
that
function into three separate functions:
Python file
1
2
3
def extract_Q(P):
"""
For an absorbing Markov chain with transition rate matrix P this
#→
computes the
matrix Q.
5
6
7
Note that this does not assume that P is in the required format. It
identifies the rows and columns that have a 1 in the diagonal and
#→
removes
them.
9
10
11
12
13
Parameters
---------P : array
Transition matrix
14
15
16
17
18
19
20
Returns
------array
The matrix Q
"""
indices_without_1_in_diagonal = np.where(P.diagonal() != 1)[0]
Modularisation ■ 205
21
22
Q = P[indices_without_1_in_diagonal.reshape(-1, 1),
#→
indices_without_1_in_diagonal]
return Q
23
24
25
26
27
28
def compute_N(Q):
"""
For an absorbing Markov chain with transition rate matrix P that
#→
gives
matrix Q this computes the fundamental matrix N.
29
30
31
32
33
Parameters
---------Q : array
The matrix Q obtained from P
34
35
36
37
38
39
40
41
42
Returns
------array
The funamental matrix N
"""
number_of_rows, _ = Q.shape
N = np.linalg.inv(np.eye(number_of_rows) - Q)
return N
Python file
1
2
3
def compute_t(P):
"""
For an absorbing Markov chain with transition rate matrix this
#→
computes the
vector t which gives the expected number of steps until absorption.
5
6
7
8
9
10
11
All the code you have written so far is generic in nature so would be better placed
somewhere that it could be used for any other project.
You are going to put these three functions (and the necessary import numpy as np
statement) in an absorption.py file as can be seen in Figure 15.1.
206 ■ Python for Mathematics
Figure 15.1
Jupyter input
1
import absorption
You will also import numpy and use it to read the data file:
Modularisation ■ 207
Jupyter input
1
import numpy as np
2
3
P = np.loadtxt("main.csv", delimiter=",")
The above commands work because the 3 files are all in the same directory.
Now to compute the shortest number of turns:
Jupyter input
1
2
3
4
5
6
k = 1
pi = np.zeros(101)
pi[0] = 1
while absorption.get_long_run_state(pi, k, P)[-1] == 0:
k += 1
k
Jupyter input
1
2
t = absorption.compute_t(P)
t[0]
43.49196169497175
15.2
HOW TO
15.2.1
Given a <file.py> file in a directory any other python process in the same
directory can
import that file as it would a normal library.
Usage
1
import <file>
At this stage it is possible to uses any python object (a function, a class, a
variable)
by referring to the <file.py> as a library:
208 ■ Python for Mathematics
Usage
1
2
3
15.2.2
<file>.function
<file>.class
<file>.variable
The aim of Modularising code is to identify specific components of the code that
can be
isolated from the rest. In practice this means writing multiple functions that use
the correct
inputs and outputs in sequence for an overall goal.
Often this allows you to write a more comprehensive docstring that explains
specific
parts of the implemented process. As an example, consider the problem of wanting to
pay
a shared bill after applying a tip, the following function will do this:
Jupyter input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Parameters
---------total : float
The total amount of the bill
tip_proportion : float
The proportion of the bill that should be added as a tip (a
#→
number
between 0 and 1)
number_of_payers : int
The number of people sharing the bill
15
16
17
18
19
20
21
22
23
Returns
------float
The amount each person should contribute
"""
tip_amount = tip_proportion * total
total += tip_amount
return total / number_of_payers
Modularisation ■ 209
Jupyter input
1
add_tip_and_get_bill_share(total=100, tip_proportion=0.2,
#→
number_of_payers=6)
20.0
An improvement of the above would be:
Jupyter input
1
2
3
4
5
8
9
10
11
12
13
14
15
Parameters
---------total : float
The total amount of the bill
tip_proportion : float
The proportion of the bill that should be added as a tip (a
#→
number
between 0 and 1)
16
17
18
19
20
21
22
23
Returns
------float
The total value of the bill (including tip)
"""
tip_amount = tip_proportion * total
return total + tip_amount
24
25
26
27
28
29
30
31
32
Parameters
----------
210 ■ Python for Mathematics
33
34
35
36
total : float
The total amount of the bill
number_of_payers : int
The number of people sharing the bill
37
38
39
40
41
42
43
Returns
------float
The amount each person should contribute
"""
return total / number_of_payers
Then to use the above you would be able to explicitly write out each step which
ensures
that there is clarity in what is being done:
Jupyter input
1
2
20.0
15.3
EXERCISES
1. Use the code written in Section 15.1 to obtain the average time until absorption
from
the first state of the chain with the following transition matrices:
1/2 1/2
(a) P =
0
1
1/2 1/4 1/4
(b) P = 1/3 1/3 1/3
0
0
1
1
0
(c) P =
1/2 1/2
1/2 1/4 1/4
(d) P = 1/3 1/3 1/3
1/5 0
4/5
2. Modularise the following code by creating a function flip coin that takes a
probability of selecting heads variable.
Modularisation ■ 211
Jupyter input
1
import random
2
3
4
5
6
def sample_experiment(bag):
"""
This samples a token from a given bag and then
selects a coin with a given probability.
7
8
9
10
11
12
13
14
15
16
17
18
19
if selected_token == "Red":
probability_of_selecting_heads = 4 / 5
else:
probability_of_selecting_heads = 2 / 5
20
21
22
23
24
25
Jupyter input
1
import math
2
3
4
5
def is_prime(N):
"""
Checks if a number N is prime by checking all that positive
integers
#→
numbers less sqrt(N) than it that divide it.
7
8
N
212 ■ Python for Mathematics
10
11
12
13
14
15
16
"""
if N <= 1 or type(N) is not int:
return False
for potential_divisor in range(2, int(math.sqrt(N)) + 1):
if (N % potential_divisor) == 0:
return False
return True
#N
i=1 xi
Use your functions to compute the mean and population variance of the following
collections of numbers:
(a) S1 = (1, 2, 3, 4, 4, 4, 4, 4)
(b) S1 = (1)
(c) S1 = (1, 1, 1, 2, 3, 4, 4, 4, 4, 4)
15.4
FURTHER INFORMATION
15.4.1
Why modularise?
Best practice when writing code is breaking up of code into modular parts. Here is
a guiding
principle described in [3]:
“Code should be obvious. When someone needs to make a change, they should
be able to find the code to be changed easily and make the change quickly
without introducing any errors.”
While this guiding principle is ambiguous and all concepts related to clean code
writing
and refactoring are not things that can be covered in this book, one specific
principle is the
one referred to in [4]:
“Functions should do one thing. They should do it well. They should do it only.”
In some texts on code architecture you will see arbitrary rules about how many
lines of
code should be in a given function. Having a function with 10 or more lines of code
might
indicate that it can be modularised. However, it is not recommended to follow such
rules
strictly. Sometimes they might add more complexity than they remove. Make your code
clear and ensure your functions do one thing well and one thing only.
Modularisation ■ 213
15.4.2
The most probable explanation for this is that you are importing a file that is not
in the
same directory or that you have not imported the file with the correct name.
As an example, if your code is in a library directory but that your notebook is in
a
different directory then you will get an error as shown below:
Jupyter input
1
import library
--------------------------------------------------------------------------
ModuleNotFoundError
Traceback (most recent call last)
Cell In[1], line 1
----> 1 import library
ModuleNotFoundError: No module named 'library'
Similarly if you perhaps incorrectly saved your library.py file with a typo in the
name
such as: librery.py then you would get the same error.
15.4.3
This falls under the subject matter of “packaging”. This is not covered in this
book.
CHAPTER
16
Documentation
This is the second of three chapters that aims to move from writing code that works
to
writing software. In this particular chapter you will consider how to write
documentation
for your code.
In this chapter you will cover:
• Using the Diátaxis framework for documentation [9].
16.1
TUTORIAL
In this tutorial you will write documentation for the code you wrote in Section
15.1.
You start by creating a new file in VScode called README.md.
You will be writing your documentation in markdown.
Start by writing the title of your library and a quick single sentence description.
Markdown input
1
# Absorption
2
3
16.1.1
Writing a tutorial
214
DOI: 10.1201/9781003451860-16
Documentation ■ 215
Markdown input
1
2
5
6
7
8
9
10
11
12
13
14
$$
p = \begin{pmatrix}
1/2 & 1/4 & 1/4\\
1/3 & 1/3 & 1/3\\
0
& 0
& 1
\end{pmatrix}
$$
15
16
17
18
We will start by seeing how the chain evolves over time by starting
#→
with an
initial vector $\pi=(1,0,0)$. In the next code snippet we will import
#→
the
necessary libraries and create both $P$ and $\pi$:
19
20
21
```python
import numpy as np
22
23
import absorption
24
25
26
27
pi = np.array([1, 0, 0])
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0, 1]])
```
28
29
30
31
32
33
34
```python
for k in range(10):
print(absorption.get_long_run_state(pi, k, P))
```
35
36
37
38
39
40
41
42
```
[1. 0. 0.]
[0.5 0.25 0.25]
[0.33333333 0.20833333 0.45833333]
[0.23611111 0.15277778 0.61111111]
216 ■ Python for Mathematics
43
44
45
46
47
48
49
50
51
52
53
54
55
We can also use `absorption` to get the average number of steps until
absorption from each state:
56
57
58
59
```python
absorption.compute_t(P)
```
60
61
This gives:
62
63
64
65
```
array([3.66666667, 3.33333333])
```
66
67
68
We see that the expected amounts of steps from the first state is
#→
slightly more
than from the second.
This tutorial section allows newcomers to your code to see how it is intended to be
used.
16.1.2
In the next section you will write a series of how to guides, this is targeted at
someone
who has perhaps worked through the tutorial already and wants to directly know how
to
do a specific task.
Directly underneath what you have written so far write:
Markdown input
1
2
## How to guides
3
4
### How to compute the long run state of a system after a given number
#→
of steps
Documentation ■ 217
Given a transition matrix $P$ and a state vector $\pi$, the state of
#→
the system
after $k$ steps is given by:
8
9
10
```python
import numpy as np
11
12
import absorption
13
14
15
16
17
pi = np.array([0, 1, 0])
P = np.array([[1 / 3, 1 / 3, 1 / 3], [1 / 4, 1 / 4, 1 / 2], [0, 0, 1]])
absorption.get_long_run_state(pi=pi, k=10, P=P)
```
18
19
This gives:
20
21
22
23
```
array([0.0019552, 0.0019552, 0.9960896])
```
24
25
### How to extract the transitive state transition sub matrix $Q$
26
27
28
29
Given a transition matrix $P$, the sub matrix $Q$ that
corresponds to the transitions between transitive (i.e. not absorbing)
#→
states can
be extracted:
30
31
32
```python
import numpy as np
33
34
import absorption
35
36
37
38
39
40
This gives:
41
42
43
44
45
```
array([[0.33333333, 0.33333333],
[0.25
, 0.5
]])
```
46
47
48
49
50
51
52
53
```python
import numpy as np
54
55
import absorption
56
57
58
59
60
61
62
This gives:
63
64
65
66
67
```
array([[2.
[1.
```
, 1.33333333],
, 2.66666667]])
68
69
70
71
72
Given a transition matrix $P$ and a state vector $\pi$, the average
#→
number of
steps until absorption from all states can be obtained:
73
74
75
```python
import numpy as np
76
77
import absorption
78
79
80
81
82
83
This gives:
84
85
86
87
```
array([3.33333333, 3.66666667])
```
16.1.3
In the next section you will write the explanations which aims to give more in
depth
understanding not necessarily directly related to the code.
Documentation ■ 219
Markdown input
1
## Explanation
2
3
4
5
7
8
For example:
9
10
11
12
13
14
15
16
$$
P = \begin{pmatrix}
1 / 3 & 1 / 3 & 1 / 3 \\
0
& 1
& 0
\\
1 / 4 & 1 / 4 & 1 / 2
\end{pmatrix}
$$
17
18
19
20
21
22
23
24
25
26
27
In general:
28
29
$$
P_{ij} > 0 \text{ for all }ij
30
31
$$
32
33
$$
\sum_{j=0}ˆ{|P|} P_{ij} = 1 \text{ for all }i
34
35
$$
36
37
38
39
40
41
be measured.
42
43
44
45
46
47
Given a vector that describes the state of the system $\pi$ and a
#→
transition
matrix $P$, the state of the system after $k$ iterations will be given
#→
by the
following vector:
48
49
$$
\pi P ˆ k
50
51
$$
52
53
54
55
56
57
58
$$
P =
\left(\begin{array}{c|c}
Q & R \\\hline
0 & I
\end{array}\right)
59
60
61
62
63
64
$$
65
66
67
68
69
70
$$
\begin{pmatrix}
1 / 3 & 1 / 3 & 1 / 3 \\
1 / 4 & 1 / 2 & 1 / 4 \\
0
& 0
& 1
\\
\end{pmatrix}
71
72
73
74
75
76
$$
77
78
79
80
81
82
83
84
85
$$
Q = \begin{pmatrix}
1 / 3 & 1 / 3 \\
1 / 4 & 1 / 2
\end{pmatrix}
$$
Documentation ■ 221
86
87
88
89
90
91
$$N = (I - Q) ˆ{-1}$$
92
93
94
95
96
97
98
99
100
101
102
$$
t = N \mathbb{1}
$$
103
104
This explanations section gives background reading as to how the code works.
16.1.4
Markdown input
1
## Reference
2
3
4
5
6
7
8
9
10
- `get_long_run_state`
- `extract_Q`
- `compute_N`
- `compute_t`
11
12
### Bibliography
13
14
15
16
17
18
19
20
Figure 16.1 shows the start of the markdown file in VScode alongside the preview.
Note that the Markdown all in one plugin ensures that the mathematics is rendered,
see
Section 14.2.6 for information on installing plugins.
Figure 16.1 The README.md file in VScode with the rendered preview (using the
Markdown
all in one plugin).
16.2
HOW TO
16.2.1
Write documentation
16.2.1.1
Write a tutorial
A tutorial should include step by step instructions with expected behaviours. This
should
not focus on any deeper explanation.
An analogy of this is teaching a young toddler to build a toy train track. They do
not
need to know the physics related to how the train will go through the track. They
need only
to see how to lay the track pieces.
16.2.1.2
A how to guide should provide a quick and to the point description of how to solve
a specific
problem.
An analogy of this would be a recipe. The recipe will not necessarily explain how
to
chop an onion and/or why you are chopping an onion. It will tell you how to chop an
onion
as a step of cooking a particular meal.
16.2.1.3
The explanation section should provide a deeper understanding of the concepts under
the
code.
An analogy of this again related to a recipe would be a book on the chemistry of
taste
and why a chopped onion adds a specific type of flavour to a meal.
16.2.1.4
The reference section should provide an overview of the specific tools, commands
and indeed
place for background reading as well.
16.2.2
Write markdown
16.2.2.1
Markdown input
1
# Section
For example:
Markdown input
1
2
3
16.2.2.2
To include code in markdown use three ‘ marks followed by the name of the language:
Markdown input
1
```<language>
2
3
4
<code>
```
For example:
Markdown input
1
2
```python
import sympy as sym
3
4
5
x = sym.Symbol(''x'')
```
Markdown input
1
3
4
x = sym.Symbol(''x'')
Using an indented block does not allow you to specify the language and can lead to
mistake when combining with other nested statement.
16.2.2.3
How to include a hyperlink in markdown
Markdown input
1
[text](url)
For example:
Documentation ■ 225
Markdown input
The [Online Encyclopedia of Integer Sequences](https://oeis.org) is a
#→
good resources for studying
resources.
16.2.2.4
Markdown input

For example:
Markdown input
Here is an image:
1
2

If the image file is not located in the same directory as the markdown file, the
path to
the file must be correct.
16.3
EXERCISES
Write documentation for the statistics.py file written in the exercises of Chapter
15.
16.4
FURTHER INFORMATION
16.4.1
What is documentation?
• External documentation which includes things like the README.md and other
separate documentation.
For a software project to be well documented, it needs both internal and external
documentation.
In [5] there are four properties of documentation:
• Reliable: it needs to be accurate.
• Low effort: it should require minimal effort to update when changes are made to
the
code base.
• Collaborative: it should be a tool from which collaboration can occur.
• Insightful: it should give information not only to be able to use the code but
also to
understand specific reasons why certain decisions have been made as to its design.
16.4.2
As discussed in [9]:
“Tutorials are lessons that take the reader by the hand through a series of steps
to complete a project of some kind. They are what your project needs in order
to show a beginner that they can achieve something with it.”
“How-to guides take the reader through the steps required to solve a real-world
problem”’
“Reference guides are technical descriptions of the machinery and how to operate
it.”
“Explanation, or discussions, clarify and illuminate a particular topic. They
broaden the documentation’s coverage of a topic.”
It is natural when describing a project for the boundaries between these four
topics to
become fuzzy. Thus, having them explicitly in four separate sections ensures the
reader is
able to specifically find what they need.
16.4.3
17
Testing
This is the third and last chapter that shows how to move from writing code that
works to
writing software. In this particular chapter you will consider how to write
automated tests
for your software.
In this chapter you will cover:
• Assert statements to test code.
• Testing documentation.
17.1
TUTORIAL
In this tutorial you will write code to ensure the correctness of the software you
have written
in the tutorials of Chapters 15 and 16.
The software for absorption.py is in fact across two separate files:
• absorption.py: the source code. You will check this using unit tests
• README.md: the documentation. You will check this using doctests.
17.1.1
Recalling the code written in absorption.py in Section 15.1, there are four
functions that
need to be tested:
• get long run state
• extract Q
• compute N
• compute t
In the directory that contains absorption.py create a new Python file called:
test absorption.py.
Write the following functions to test each of the functions in absorption.py:
DOI: 10.1201/9781003451860-17
227
228 ■ Python for Mathematics
Python file
1
import numpy as np
2
3
import absorption
4
5
6
7
8
9
10
11
12
def test_long_run_state_for_known_number_of_states():
"""
This tests the `long_run_state` for a small example matrix
"""
pi = np.array([1, 0, 0])
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
#→
1]])
pi_after_5_steps = absorption.get_long_run_state(pi=pi, k=5, P=P)
assert np.array_equal(pi_after_5_steps, pi @
np.linalg.matrix_power(P, 5)), "Did not get expected result
#→
for pi after 5 steps"
#→
13
14
15
16
17
def test_long_run_state_when_starting_in_absorbing_state():
"""
This tests the `long_run_state` for a small example matrix.
18
19
20
21
22
23
24
25
In this test we start in the absorbing state, the state vector
#→
should not
change.
"""
pi = np.array([0, 0, 1])
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
#→
1]])
pi_after_5_steps = absorption.get_long_run_state(pi=pi, k=5, P=P)
assert np.array_equal(pi_after_5_steps, pi)
26
27
28
29
test_long_run_state_for_known_number_of_states()
test_long_run_state_when_starting_in_absorbing_state()
The two functions you have written do not include a return statement but instead
include an assert statement. An assert is followed by two values separated by a
comma:
1. A boolean that is to be True or False.
2. A string that is output if the boolean is “False”.
To run those tests, run the script at the command line:
Testing ■ 229
$ python test_absorption.py
When running the tests if everything has been done correctly, there will be no
output:
the 2 functions have been called and the assertions have passed. See Figure 17.1.
Figure 17.1
For each of the 4 functions in absorption.py, you can now add further tests and
ensure
they are also called at the end. The full test absorption.py file should look like:
Python file
1
import numpy as np
2
3
import absorption
4
5
6
7
8
9
10
11
12
def test_long_run_state_for_known_number_of_states():
"""
This tests the `long_run_state` for a small example matrix
"""
pi = np.array([1, 0, 0])
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
1]])
#→
pi_after_5_steps = absorption.get_long_run_state(pi=pi, k=5, P=P)
assert np.array_equal(pi_after_5_steps, pi @
#→
np.linalg.matrix_power(P, 5)), "Did not get expected result
#→
for pi after 5 steps"
230 ■ Python for Mathematics
13
14
15
16
17
def test_long_run_state_when_starting_in_absorbing_state():
"""
This tests the `long_run_state` for a small example matrix.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def test_extract_Q():
"""
This tests that the submatrix Q can be extracted from a given
#→
matrix P.
"""
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
#→
1]])
Q = absorption.extract_Q(P)
expected_Q = np.array([[1 / 2, 1 / 4], [1 / 3, 1 / 3]])
assert np.array_equal(Q, expected_Q), f"The expected Q did not
#→
match, the code obtained {Q}"
36
37
38
39
40
41
42
43
44
45
46
def test_compute_N():
"""
This tests the computation of the fundamental matrix N
"""
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
#→
1]])
Q = absorption.extract_Q(P)
N = absorption.compute_N(Q)
expected_N = np.array([[8 / 3, 1], [4 / 3, 2]])
assert np.allclose(N, expected_N), f"The expected N did not match,
#→
the code obtained {N}"
47
48
49
50
51
52
def test_compute_t():
"""
This tests the computation of the number of steps until absorption
#→
t.
"""
Testing ■ 231
53
54
55
56
57
58
59
60
61
62
63
test_long_run_state_for_known_number_of_states()
test_long_run_state_when_starting_in_absorbing_state()
test_extract_Q()
test_compute_N()
test_compute_t()
The numpy.array equal and numpy.allclose compare equality of arrays. They return
True or False depending on whether the two passed arrays are equal or approximately
equal (respectively).
numpy.allclose should be used when comparing numpy arrays that might be different
due to numerical imprecision.
You can experiment by changing some of the code or the tests and see the
way the tests fail. See Figure 17.2 where the following specific error has been
introduced
into absorption.py: P.diagonal() == 1 is incorrect and should be P.diagonal() != 1.
As and when you add more features to absorption.py you will also add tests.
Software is compromised of both code and documentation. So far you have tested your
code, now you will test your documentation.
17.1.2
Testing documentation
To be able to check the Python code written in the documentation (see Chapter 16)
is
correct, you need to write the code using a specific format:
• >>> to denote python code
Figure 17.2
Markdown input
1
2
```python
import numpy as np
3
4
import absorption
5
6
7
8
pi = np.array([1, 0, 0])
P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0, 1]])
```
9
10
11
12
13
14
15
16
```python
for k in range(10):
print(absorption.get_long_run_state(pi, k, P))
```
Testing ■ 233
17
18
19
20
21
22
23
24
25
26
27
28
29
```
[1. 0. 0.]
[0.5 0.25 0.25]
[0.33333333 0.20833333 0.45833333]
[0.23611111 0.15277778 0.61111111]
[0.16898148 0.1099537 0.72106481]
[0.12114198 0.0788966 0.79996142]
[0.08686986 0.05658436 0.85654578]
[0.06229638 0.04057892 0.8971247 ]
[0.0446745 0.0291004 0.9262251]
[0.03203738 0.02086876 0.94709386]
30
31
```
Markdown input
1
2
3
4
5
```python
>>> import numpy as np
>>> import absorption
>>> pi = np.array([1, 0, 0])
>>> P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
1]])
#→
6
7
```
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
```python
>>> for k in range(10):
...
print(absorption.get_long_run_state(pi, k, P))
[1. 0. 0.]
[0.5 0.25 0.25]
[0.33333333 0.20833333 0.45833333]
[0.23611111 0.15277778 0.61111111]
[0.16898148 0.1099537 0.72106481]
[0.12114198 0.0788966 0.79996142]
[0.08686986 0.05658436 0.85654578]
[0.06229638 0.04057892 0.8971247 ]
[0.0446745 0.0291004 0.9262251]
[0.03203738 0.02086876 0.94709386]
24
25
```
234 ■ Python for Mathematics
To test the documentation gives the results that are written, run the following at
the
command line:
When testing the documentation, if there are no errors there will be no output as
shown
in Figure 17.3.
Figure 17.3
Markdown input
1
# Absorption
2
3
4
5
## Tutorial
6
7
Figure 17.4
recommend: <https://en.wikipedia.org/wiki/Absorbing_Markov_chain>.
10
11
12
13
14
15
16
17
18
19
$$
p = \begin{pmatrix}
1/2 & 1/4 & 1/4\\
1/3 & 1/3 & 1/3\\
0
& 0
& 1
\end{pmatrix}
$$
20
21
22
23
We will start by seeing how the chain evolves over time by starting
#→
with an
initial vector $\pi=(1,0,0)$. In the next code snippet we will import
#→
the
necessary libraries and create both $P$ and $\pi$:
24
25
26
27
28
29
30
```python
>>> import numpy as np
>>> import absorption
>>> pi = np.array([1, 0, 0])
>>> P = np.array([[1 / 2, 1 / 4, 1 / 4], [1 / 3, 1 / 3, 1 / 3], [0, 0,
#→
1]])
236 ■ Python for Mathematics
31
```
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
```python
>>> for k in range(10):
...
print(absorption.get_long_run_state(pi, k, P))
[1. 0. 0.]
[0.5 0.25 0.25]
[0.33333333 0.20833333 0.45833333]
[0.23611111 0.15277778 0.61111111]
[0.16898148 0.1099537 0.72106481]
[0.12114198 0.0788966 0.79996142]
[0.08686986 0.05658436 0.85654578]
[0.06229638 0.04057892 0.8971247 ]
[0.0446745 0.0291004 0.9262251]
[0.03203738 0.02086876 0.94709386]
48
49
```
50
51
52
53
54
55
We can also use `absorption` to get the average number of steps until
absorption from each state:
56
57
58
59
```python
>>> absorption.compute_t(P)
array([3.66666667, 3.33333333])
60
61
```
62
63
64
17.1.3
We see that the expected amounts of steps from the first state is
#→
slightly more
than from the second.
Finally it is important to document how to run the tests. The reference section is
an
appropriate place to put this. Add the following to the README.md file:
Testing ■ 237
Markdown input
1
2
3
4
5
6
7
```
$ python test_absorption.py
```
8
9
10
11
12
13
```
$ python -m doctest README.md
```
17.2
HOW TO
17.2.1
Usage
1
Jupyter input
1
2
3
4
5
def add_one(a):
"""
Returns a + 1
"""
return a + 1
Jupyter input
1
Note that if you change the function to include an error, for example, here adding
2 and
not 1, and run the same assert you get an error as well as the specified string.
238 ■ Python for Mathematics
Jupyter input
1
2
3
4
5
def add_one(a):
"""
Returns a + 1
"""
return a + 2
6
7
8
--------------------------------------------------------------------------
AssertionError
Traceback (most recent call last)
Cell In[3], line 8
2
"""
3
Returns a + 1
4
"""
5
return a + 2
----> 8 assert add_one(5) == 6, "The function gave the wrong answer."
AssertionError: The function gave the wrong answer.
17.2.2
When making an assertion about code that behaves in a random manner, use seeding as
described in Section 7.2.9.
For example:
Jupyter input
1
import random
2
3
4
5
6
7
8
def roll_a_dice():
"""
Pick a random integer between 1 and 6 (inclusive)
"""
return random.choice(range(1, 7))
Jupyter input
1
2
3
4
5
6
7
8
random.seed(0)
assert roll_a_dice() == 4, "The 0 seed did not give the expected
#→
result"
random.seed(1)
assert roll_a_dice() == 2, "The 1 seed did not give the expected
#→
result"
random.seed(2)
assert roll_a_dice() == 1, "The 2 seed did not give the expected
#→
result"
random.seed(3)
assert roll_a_dice() == 2, "The 3 seed did not give the expected
#→
result"
Jupyter input
1
2
3
4
random.seed(0)
samples = [roll_a_dice() for repetition in range(1000)]
all_values = {1, 2, 3, 4, 5, 6}
assert set(samples) == all_values, "Not all values have been obtained
#→
over 1000 repetitions"
You can also confirm that the count of a given value is as expected:
Jupyter input
1
The last assertion used the count method on a list that counts a given number of
items
in a list.
17.2.3
To write tests, assertion statements should be put into a file separate from the
code in
functions.
For example, if the dice.py file contained:
240 ■ Python for Mathematics
Python file
1
import random
2
3
4
5
6
7
8
def roll_a_dice():
"""
Pick a random integer between 1 and 6 (inclusive)
"""
return random.choice(range(1, 7))
Then a separate test dice.py file with the following would be written:
Python file
1
import dice
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test_roll_a_dice_with_specific_values():
"""
Check the roll a dice function gives specific numbers for a number
#→
of seeds.
"""
random.seed(0)
assert dice.roll_a_dice() == 4, "The 0 seed did not give the
#→
expected result"
random.seed(1)
assert dice.roll_a_dice() == 2, "The 1 seed did not give the
#→
expected result"
random.seed(2)
assert dice.roll_a_dice() == 1, "The 2 seed did not give the
#→
expected result"
random.seed(3)
assert dice.roll_a_dice() == 2, "The 3 seed did not give the
#→
expected result"
16
17
18
19
20
21
22
23
24
25
def test_roll_a_dice_for_a_large_sample():
"""
Collect a sample of 1000 rolls and confirm that we have expected
#→
results.
"""
random.seed(0)
samples = [dice.roll_a_dice() for repetition in range(1000)]
all_values = {1, 2, 3, 4, 5, 6}
assert set(samples) == all_values, "Not all values have been
#→
obtained over 1000 repetitions"
Testing ■ 241
26
27
28
29
30
test_roll_a_dice_with_specific_values()
test_roll_a_dice_for_a_large_sample()
To run the tests you would then type the following at the command line:
17.2.4
$ python test_dice.py
Format doctests
When writing code in documentation if you write it using a specific format, then
python
can be used to confirm that the code and its output is correct.
Markdown input
1
2
3
4
5
>>> <python_code>
<expected_output
>>> <python_code_over_multiples_lines>
... <python_code_over_multiple_lines>
<expected_output>
Python file
1
import random
2
3
4
5
6
7
8
def roll_a_dice():
"""
Pick a random integer between 1 and 6 (inclusive)
"""
return random.choice(range(1, 7))
242 ■ Python for Mathematics
Markdown input
1
2
3
4
17.2.5
Run doctests
Given a file with doctests, to run them type the following at the command line:
Usage
1
For example:
17.3
EXERCISES
Write tests for the statistics.py file written in the exercises of Chapters 15 and
16.
Run the tests.
17.4
FURTHER INFORMATION
17.4.1
In Section 17.1 you wrote all the tests using assert statements inside of
functions. Technically this is not necessary as you could write a single script
with all the assert statements
one after the other.
This is not recommended: by using functions you directly have a place to document
the test (in the docstring) and the tests themselves are modularised. Furthermore,
this is
actually how to write the tests when using a more efficient way of running tests as
described
in Section 17.4.2.
17.4.2
Writing tests as a script and directly running them has one immediate problem: once
the
first assert statement fails, the rest of them are not run.
There is a Python library for running tests called pytest [7].
Testing ■ 243
17.4.3
The short answer to this is that all software should be tested and that software is
compromised of documentation and code.
Note that it is often not sufficient to test a function in a single way. For
example, consider
a function that does two different things depending on the parity of some input:
Python file
1
2
3
4
5
6
7
8
9
10
11
12
13
In this case you would need to write at least three tests that check the three
behaviours.
In practice there might be some functionality that is not tested but this should be
made
clear and explicit. Documentation explaining the lack of tests should be written.
17.4.4
17.4.5
What is test driven development?
Test driven development is the development process of writing the test before you
write the
code. While this might seem counter-intuitive, it is in fact an efficient approach
to writing
robust code.
In practice the process is as follows:
1. Write a test for some new functionality.
2. Run the tests to confirm that it fails (as the functionality is not yet
written).
3. Write the functionality.
4. Run the test.
5. Modify the test and/or the functionality
Steps 4 and 5 might be repeated many times.
A good overview of test driven development is given in [8].
244 ■ Python for Mathematics
17.4.6
In Chapters 15, 16, and 17, three concepts that move from writing code that works
to
writing software that is reliable have been discussed:
• Modularisation.
• Documentation.
• Testing.
In reality all three of these concepts are closely related and fundamental to good
software. Figure 17.5 shows this.
Figure 17.5
245
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
CHAPTER
18
This chapter include some specific information about the book itself.
This book is written using Jupyterbook [2].
The source files are written in the myst format which is a plain text format for
Jupyter
notebooks. This ensures:
• That the notebooks are version controlled effectively.
• The output of the code is an actual computation.
The computations carried out in this book were carried out with python version
3.10.14.
The library versions used are:
• jupyter version 1.0.0.
• matching version 1.4.3.
• matplotlib version 3.9.0.
• numpy version 1.26.4.
• scipy version 1.13.0.
• sympy version 1.12.
DOI: 10.1201/9781003451860-18
247
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
Bibliography
[1] José Carlos Bautista. Mathematics and Python Programming. Lulu.com, 2014.
[2] Executable Books Community. Jupyter book, February 2021.
[3] Martin Fowler. Refactoring: improving the design of existing code. Addison-
Wesley
Professional, 2018.
[4] Robert C Martin. Clean code: a handbook of agile software craftsmanship.
Pearson
Education, 2009.
[5] Cyrille Martraire. Living documentation: continuous knowledge sharing by
design.
Addison-Wesley, Boston, 2019.
[6] Sam Morley. Applying Math with Python: Over 70 practical recipes for solving
realworld computational math problems. Packt Publishing Ltd, 2022.
[7] Bruno Oliveira. pytest Quick Start Guide: Write better Python code with simple
and
maintainable tests. Packt Publishing Ltd, 2018.
[8] Harry Percival. Test-driven development with Python: obey the testing goat:
using
Django, Selenium, and JavaScript. “O’Reilly Media, Inc.”, 2014.
[9] Daniele Procida. Diátaxis documentation framework.
[10] Amit Saha. Doing Math with Python: Use Programming to Explore Algebra,
Statistics,
Calculus, and More! No Starch Press, 2015.
[11] Steve Selvin. Monty hall problem. American Statistician, 29(3):134–134, 1975.
[12] Guido van Rossum, Barry Warsaw, and Nick Coghlan. Style guide for Python code.
PEP 8, 2001.
249
Taylor & Francis
Taylor & Francis Group
http://taylorandfrancis.com
Index
Airy functions, 140, 141
Bayes’ theorem, 87
bivariate, 110, 118
Fibonacci, 105
random numbers, 80
random sample, 80, 110
recursion, 102
integrating, 45, 49
inverse, 56, 57, 60, 62, 63
251