Scientific Computing Python 3
User Manual: scientific-computing-python-3
Open the PDF directly: View PDF
.
Page Count: 322
| Download | |
| Open PDF In Browser | View PDF |
Scientific Computing with
Python 3
An example-rich, comprehensive guide for all of your Python
computational needs
Claus Führer
Jan Erik Solem
Olivier Verdier
BIRMINGHAM - MUMBAI
Scientific Computing with Python 3
Copyright © 2016 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or
transmitted in any form or by any means, without the prior written permission of the
publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the
information presented. However, the information contained in this book is sold without
warranty, either express or implied. Neither the authors, nor Packt Publishing, and its
dealers and distributors will be held liable for any damages caused or alleged to be caused
directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the
companies and products mentioned in this book by the appropriate use of capitals.
However, Packt Publishing cannot guarantee the accuracy of this information.
First published: December 2016
Production reference: 1141216
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.
ISBN 978-1-78646-351-7
www.packtpub.com
Credits
Authors
Copy Editor
Claus Führer
Vikrant Phadkay
Jan Erik Solem
Sneha Singh
Olivier Verdier
Reviewers
Project Coordinator
Helmut Podhaisky
Nidhi Joshi
Commissioning Editor
Proofreader
Veena Pagare
Safis Editing
Acquisition Editor
Indexer
Sonali Vernekar
Mariammal Chettiyar
Content Development Editor
Graphics
Aishwarya Pandere
Disha Haria
Technical Editor
Production Coordinator
Karan Thakkar
Arvindkumar Gupta
About the Authors
Claus Führer is a professor of scientific computations at Lund University, Sweden. He has
an extensive teaching record that includes intensive programming courses in numerical
analysis and engineering mathematics across various levels in many different countries and
teaching environments. Claus also develops numerical software in research collaboration
with industry and received Lund University’s Faculty of Engineering Best Teacher Award
in 2016.
Jan Erik Solem is a Python enthusiast, former associate professor, and currently the CEO of
Mapillary, a street imagery computer vision company. He has previously worked as a face
recognition expert, founder and CTO of Polar Rose, and computer vision team leader at
Apple. Jan is a World Economic Forum technology pioneer and won the Best Nordic Thesis
Award 2005-2006 for his dissertation on image analysis and pattern recognition. He is also
the author of "Programming Computer Vision with Python" (O'Reilly 2012).
Olivier Verdier began using Python for scientific computing back in 2007 and received a
PhD in mathematics from Lund University in 2009. He has held post-doctoral positions in
Cologne, Trondheim, Bergen, and Umeå and is now an associate professor of mathematics
at Bergen University College, Norway.
About the Reviewer
Helmut Podhaisky works in the Institute of Mathematics at the Martin Luther University in
Halle-Wittenberg, where he teaches mathematics and scientific computing. He has coauthored a book on numerical methods for ordinary differential equations as well as several
research papers on numerical methods. For work and fun, he uses Python, Fortran, Octave,
Mathematica, and Haskell.
www.PacktPub.com
For support files and downloads related to your book, please visit www.PacktPub.com.
Did you know that Packt offers eBook versions of every book published, with PDF and
ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a
print book customer, you are entitled to a discount on the eBook copy. Get in touch with us
at service@packtpub.com for more details.
At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a
range of free newsletters and receive exclusive discounts and offers on Packt books and
eBooks.
https://www.packtpub.com/mapt
Get the most in-demand software skills with Mapt. Mapt gives you full access to all Packt
books and video courses, as well as industry-leading tools to help you plan your personal
development and advance your career.
Why subscribe?
Fully searchable across every book published by Packt
Copy and paste, print, and bookmark content
On demand and accessible via a web browser
Acknowledgement
We want to acknowledge the competent and helpful comments and suggestions by Helmut
Podhaisky, Halle University, Germany. To have such a partner in the process of writing a
book is a big luck and chance for the authors.
We would also like to express our gratitude towards the reviewers of the first edition of this
book, [7], Linda Kann, KTH Stockholm, Hans Petter Langtangen, Simula Research
Laboratory, and Alf Inge Wang, NTNU Trondheim.
A book has to be tested in teaching. And here we had fantastic partners: the teaching
assistants from the course "Beräkningsprogramering med Python" during the years and the
colleagues involved in teaching: Najmeh Abiri, Christian Andersson, Dara Maghdid, Peter
Meisrimel, Fatemeh Mohammadi, Azahar Monge, Anna-Maria Persson, Alexandros
Sopasakis, Tony Stillfjord, Lund University. Najmeh Abiri also tested most of the Jupyter
notebook material which you find on the book's webpage.
A book has not only to be written, it has to be published, and in this process Aishwarya
Pandere and Karan Thakkar, PACKT Publishing, were always constructive, friendly and
helpful partners bridging different time zones and different text processing tools. Thanks.
Claus Führer, Jan-Erik Solem, Olivier Verdier Lund, Bergen 2016
Table of Contents
Preface
Chapter 1: Getting Started
Installation and configuration instructions
Installation
Anaconda
Configuration
Python Shell
Executing scripts
Getting Help
Jupyter – Python notebook
Program and program flow
Comments
Line joining
Basic types
Numbers
Strings
Variables
Lists
Operations on lists
Boolean expressions
Repeating statements with loops
Repeating a task
Break and else
Conditional statements
Encapsulating code with functions
Scripts and modules
Simple modules – collecting functions
Using modules and namespaces
Interpreter
Summary
Chapter 2: Variables and Basic Types
Variables
Numeric types
Integers
1
10
11
11
12
13
13
14
14
14
15
16
16
17
17
17
18
18
19
19
20
20
21
21
22
23
23
24
24
25
26
26
27
28
Plain integers
Floating point numbers
Floating point representation
Infinite and not a number
Underflow – Machine Epsilon
Other float types in NumPy
Complex numbers
Complex Numbers in Mathematics
The j notation
Real and imaginary parts
Booleans
Boolean operators
Boolean casting
Automatic Boolean casting
Return values of and and or
Boolean and integer
Strings
Operations on strings and string methods
String formatting
Summary
Exercises
Chapter 3: Container Types
28
29
29
30
31
32
33
33
33
34
36
36
37
38
38
39
39
41
42
43
43
47
Lists
Slicing
Strides
Altering lists
Belonging to a list
List methods
In–place operations
Merging lists – zip
List comprehension
Arrays
Tuples
Dictionaries
Creating and altering dictionaries
Looping over dictionaries
Sets
Container conversions
Type checking
Summary
[ ii ]
47
48
50
51
51
52
52
53
53
54
56
56
57
58
58
60
61
62
Exercises
62
Chapter 4: Linear Algebra – Arrays
65
Overview of the array type
Vectors and matrices
Indexing and slices
Linear algebra operations
Solving a linear system
Mathematical preliminaries
Arrays as functions
Operations are elementwise
Shape and number of dimensions
The dot operations
The array type
Array properties
Creating arrays from lists
Accessing array entries
Basic array slicing
Altering an array using slices
Functions to construct arrays
Accessing and changing the shape
The shape function
Number of dimensions
Reshape
Transpose
Stacking
Stacking vectors
Functions acting on arrays
Universal functions
Built-in universal functions
Create universal functions
Array functions
Linear algebra methods in SciPy
Solving several linear equation systems with LU
Solving a least square problem with SVD
More methods
Summary
Exercises
Chapter 5: Advanced Array Concepts
Array views and copies
Array views
65
65
67
67
68
69
69
69
70
71
73
73
74
75
75
77
77
78
78
79
79
81
82
82
83
83
83
84
86
87
87
89
90
91
91
94
94
94
[ iii ]
Slices as views
Transpose and reshape as views
Array copy
Comparing arrays
Boolean arrays
Checking for equality
Boolean operations on arrays
Array indexing
Indexing with Boolean arrays
Using where
Performance and Vectorization
Vectorization
Broadcasting
Mathematical view
Constant functions
Functions of several variables
General mechanism
Conventions
Broadcasting arrays
The broadcasting problem
Shape mismatch
Typical examples
Rescale rows
Rescale columns
Functions of two variables
Sparse matrices
Sparse matrix formats
Compressed sparse row
Compressed Sparse Column
Row-based linked list format
Altering and slicing matrices in LIL format
Generating sparse matrices
Sparse matrix methods
Summary
Chapter 6: Plotting
95
95
96
96
96
97
98
99
99
100
101
102
103
103
104
105
105
107
107
107
109
110
110
110
110
112
113
113
115
115
116
116
117
118
119
Basic plotting
Formatting
Meshgrid and contours
Images and contours
Matplotlib objects
The axes object
119
125
128
132
134
135
[ iv ]
Modifying line properties
Annotations
Filling areas between curves
Ticks and ticklabels
Making 3D plots
Making movies from plots
Summary
Exercises
136
137
138
140
141
145
146
147
Chapter 7: Functions
149
Basics
Parameters and arguments
Passing arguments – by position and by keyword
Changing arguments
Access to variables defined outside the local namespace
Default arguments
Beware of mutable default arguments
Variable number of arguments
Return values
Recursive functions
Function documentation
Functions are objects
Partial application
Using Closures
Anonymous functions – the lambda keyword
The lambda construction is always replaceable
Functions as decorators
Summary
Exercises
Chapter 8: Classes
149
150
150
151
152
153
154
154
156
157
159
160
160
161
161
162
163
164
165
167
Introduction to classes
Class syntax
The __init__ method
Attributes and methods
Special methods
Reverse operations
Attributes that depend on each other
The property function
Bound and unbound methods
Class attributes
[v]
168
169
169
170
172
174
176
177
178
178
Class methods
Subclassing and inheritance
Encapsulation
Classes as decorators
Summary
Exercises
179
181
184
185
188
188
Chapter 9: Iterating
190
The for statement
Controlling the flow inside the loop
Iterators
Generators
Iterators are disposable
Iterator tools
Generators of recursive sequences
Arithmetic geometric mean
Convergence acceleration
List filling patterns
List filling with the append method
List from iterators
Storing generated values
When iterators behave as lists
Generator expression
Zipping iterators
Iterator objects
Infinite iterations
The while loop
Recursion
Summary
Exercises
Chapter 10: Error Handling
190
191
192
193
194
194
196
196
198
200
200
200
201
202
202
203
204
205
205
206
207
207
210
What are exceptions?
Basic principles
Raising exceptions
Catching exceptions
User-defined exceptions
Context managers — the with statement
Finding Errors: Debugging
Bugs
The stack
[ vi ]
210
212
212
213
215
216
218
218
218
The Python debugger
Overview – debug commands
Debugging in IPython
Summary
219
221
222
223
Chapter 11: Namespaces, Scopes, and Modules
Namespace
Scope of a variable
Modules
Introduction
Modules in IPython
224
224
225
227
227
229
229
229
230
230
The IPython magic command
The variable __name__
Some useful modules
Summary
Chapter 12: Input and Output
231
File handling
Interacting with files
Files are iterable
File modes
NumPy methods
savetxt
loadtxt
Pickling
Shelves
Reading and writing Matlab data files
Reading and writing images
Summary
Chapter 13: Testing
231
231
233
233
234
234
234
235
236
237
237
238
239
Manual testing
Automatic testing
Testing the bisection algorithm
Using unittest package
Test setUp and tearDown methods
Parameterizing tests
Assertion tools
Float comparisons
Unit and functional tests
Debugging
[ vii ]
239
240
241
243
244
245
247
247
249
250
Test discovery
Measuring execution time
Timing with a magic function
Timing with the Python module timeit
Timing with a context manager
Summary
Exercises
Chapter 14: Comprehensive Examples
Polynomials
Theoretical background
Tasks
The polynomial class
Newton polynomial
Spectral clustering
Solving initial value problems
Summary
Exercises
250
250
251
252
253
254
254
256
256
256
258
259
263
265
269
273
273
Chapter 15: Symbolic Computations - SymPy
274
What are symbolic computations?
Elaborating an example in SymPy
Basic elements of SymPy
Symbols – the basis of all formulas
Numbers
Functions
274
276
278
278
279
279
280
281
282
283
284
285
287
289
290
292
292
294
Undefined functions
Elementary Functions
Lambda – functions
Symbolic Linear Algebra
Symbolic matrices
Examples for Linear Algebra Methods in SymPy
Substitutions
Evaluating symbolic expressions
Example: A study on the convergence order of Newton's Method
Converting a symbolic expression into a numeric function
A study on the parameter dependency of polynomial coefficients
Summary
Appendix: References
Index
295
298
[ viii ]
Preface
Python can be used for more than just general-purpose programming. It is a free, open
source language and environment that has tremendous potential for use within the domain
of scientific computing. This book presents Python in tight connection with mathematical
applications and demonstrates how to use various concepts in Python for computing
purposes, including examples with the latest version of Python 3. Python is an effective tool
to use when coupling scientific computing and mathematics and this book will teach you
how to use it for linear algebra, arrays, plotting, iterating, functions, polynomials, and much
more.
What this book covers
Chapter 1, Getting Started, addresses the main language elements of Python without going
into detail. Here we make a brief tour through all. It is a good starting point for those who
want to start directly. It is a quick reference for those readers who want in a later chapter
understand an example which uses might use constructs like functions before functions
were explained in deep .
Chapter 2, Variables and Basic Types, presents the most important and basic types in Python.
Float is the more important datatype in scientific computing together with the special
numbers nan and inf. Booleans, integers, complex, and strings are other basic datatypes,
which will be used throughout this book.
Chapter 3, Container Types, explains how to work with container types, mainly lists.
Dictionaries and tuples will be explained as well as indexing and looping, through
container objects. Occasionally, one uses even sets as a special container type.
Chapter 4, Linear Algebra, works with the most important objects in linear algebra--vectors
and matrices. This book chooses NumPy array as the central tool for describing matrices
and even higher order tensors. Arrays have many advanced features and allows also for
universal functions acting on matrices or vectors elementwise. The book emphasizes on
array indexing, slices, and the dot product as the basic operation in most computing tasks.
Some linear algebra examples are worked out to demonstrate the use of SciPy's submodule
linalg.
Preface
Chapter 5, Advanced Array Concepts, explains some more advanced aspects of arrays. The
difference between array copies and views is explained extensively as views make
programs using arrays very fast but are often a source for errors, which are hard to debug.
The use of Boolean arrays to write effective, compact, and readable code is shown and
demonstrated. Finally, the technique of array broadcasting-- a unique feature of NumPy
arrays -- is explained by its analogy to operations performed on functions.
Chapter 6, Plotting, shows how to make plots, mainly classical x/yplots but also 3D plots
and histograms. Scientific computing requires good tools for visualizing the results.
Python's module matplotlib is introduced starting from the handy plotting commands in its
submodule pyplot. Finetuning and modifying plots becomes possible by creating graphical
objects such as axes. We show how attributes of these objects can be changed and
annotations can be made.
Chapter 7, Functions, form the fundamental building block in programming, which is
probably nearest to underlying mathematical concepts. Function definition and function
calls are explained as the different ways to set function arguments. Anonymous lambda
functions are introduced and used in various examples throughout the book.
Chapter 8, Classes, defines objects as instances of classes, which we provide with methods
and attributes. In mathematics, class attributes often depend on each other, which requires
special programming techniques for setter and getter functions. Basic mathematical
operations such as + can be defined for special mathematic datatypes. Inheritance and
abstraction are mathematical concepts which are reflected by object oriented programming.
We demonstrate the use of inheritance by a simple solver class for ordinary differential
equations.
Chapter 9, Iterating, presents iteration using loops and iterators. There is now a chapter in
this book without loops and iterations, but here we come to principles of iterators and
create own generator objects. In this chapter, you learn why a generator can be exhausted
and how infinite loops can be programmed. Python's module itertools is a useful
companion for this chapter.
Chapter 10, Error Handling, covers errors and exceptions and how to find and fix them. An
error or an exception is an event, which breaks the execution of a program unit. This
chapter shows what to do then, that is, how an exception can be handled. You learn to
define your own exception classes and how to provide valuable information, which can be
used for catching these exceptions. Error handling is more than printing an error message.
[2]
Preface
Chapter 11, Namespaces, Scopes and Modules, covers Python modules. What are local and
global variables? When is a variable known and when is it unknown to a program unit?
This is discussed in this chapter. A variable can be passed to a function by a parameter list
or tacitly injected by making use of its scope. When should this technique be applied and
when not? This chapter tries to give an answer to this central question.
Chapter 12, Input and Output, covers some options for handling data files. Data files are
used for storing and providing data for a given problem, often large scale measurements.
This chapter describes how this data can be accessed and modified using different formats.
Chapter 13, Testing, focuses on testing for scientific programming. The key tool is unittest,
which allows for automatic testing and parametrized tests. By considering the classical
bisection algorithm in numerical mathematics, we exemplify different steps to design
meaningful tests, which as a side effect also deliver a documentation of the use of a piece of
code. Careful testing provides test protocols which can be later helpful when debugging a
complex code often written by many different programmers.
Chapter 14, Comprehensive Examples, presents some comprehensive and longer examples
together with a brief introduction to the theoretical background and their complete
implementation. These examples make use of all constructs shown in the book so far and
put them in a larger and more complex context. They are open for extensions by the reader.
Chapter 15, Symbolic Computations - SymPy, speaks about symbolic computations. Scientific
computing is mainly numeric computations with inexact data and approximative results.
This is contrasted by symbolic computations often formal manipulation, which aims for
exact solutions in a closed form expression. In this last chapter of the book, we introduce
this technique in Python, which is often used for deriving and verifying theoretically
mathematical models and numerical results. We emphasize on high precision floating point
evaluation of symbolic expressions.
What you need for this book
You would need Pyhon3.5 or higher, SciPy, NumPy, Matplotlib, IPython shell (we
recommend strongly to install Python and its packages through Anaconda). The examples
of the book do not have any special hardware requirements on memory and graphics.
[3]
Preface
Who this book is for
This book is the outcome of a course on Python for scientific computing which is taught at
Lund University since 2008. The course expanded over the years, and condensed versions
of the material were taught at universities in Cologne, Trondheim, Stavanger, Soran,
Lappeenranta and also in computation oriented companies.
Our belief is that Python and its surrounding scientific computing ecosystem — SciPy,
NumPY and matplotlib — represent a tremendous progress in scientific computing
environment. Python and the aforementioned libraries are free and open source. What’s
more, is a modern language featuring all the bells and whistles that this adjective entails:
object oriented programming, testing, advanced shell with IPython, etc. When writing this
book we had two groups of readers in mind:
The reader who chooses Python as his or her first programming language will
use this book in a teacher-led course. The book guides into the different topics
and offers background reading and experimenting. A teacher typically selects
and orders the material from this book in such a way, that it fits to the specific
learning outcomes of an introductory course.
The reader who already has some experience in programming, and some taste for
scientific computing or mathematics will use this book as a companion when
diving into the world of Scipy and Numpy. Programming in Python can be quite
different from programming in MATLAB, say. The book wants to point out the
"pythonic" way of programming, which makes programming a pleasure.
Our goal is to explain the steps to get started with Python in the context of scientific
computing. The book may be read either from the first page to the last, or by picking the
bits that seem most interesting. Needless to say, as improving one’s programming skills
requires considerable practice, it is highly advisable to experiment and play with the
examples and the exercises in the book.
We hope that the readers will enjoy programming with Python, SciPy, NumPY and
matplotlib as much as we do.
[4]
Preface
Python vs Other Languages
When it comes to deciding what language to use for a book on scientific computing many
factors come in to play. The learning threshold of the language itself is important for
newcomers, here scripting languages usually provide the best options. A wide range of
modules for numerical computing is necessary, preferably with a strong developer
community. If these core modules are built on a well-tested, optimized foundation of fast
libraries like e.g. LAPACK, even better. Finally, if the language is also usable in a wider
setting and a wider range of applications, the chance of the reader using the skills learned
from this book outside an academic setting is greater. Therefore the choice of Python was a
natural one.
In short, Python is
free and open source
a scripting language, meaning that it is interpreted
a modern language (object oriented, exception handling, dynamic typing etc.)
concise, easy to read and quick to learn
full of freely available libraries, in particular scientific ones (linear algebra,
visualization tools, plotting, image analysis, differential equations solving,
symbolic computations, statistics etc.)
useful in a wider setting: scientific computing, scripting, web sites, text parsing,
etc.
widely used in industrial applications
There are other alternatives to Python. Some of them and the differences to Python are
listed here.
Java, C++ : Object oriented, compiled languages. More verbose and low level compared to
Python. Few scientific libraries.
C, FORTRAN : Low level compiled languages. Both languages are extensively used in
scientific computing, where computational time matters. Nowadays these languages are
often combined with Python wrappers.
PHP, Ruby, other interpreted languages. PHP is web oriented. Ruby is as flexible as Python
but has few scientific libraries.
[5]
Preface
MATLAB, Scilab, Octave : MATLAB is a tool for matrix computation that evolved for
scientific computing. The scientific library is huge. The language features are not as
developed as those of Python. Neither free nor open source. SciLab and Octave are open
source tools which are syntactically similar to MATLAB.
Haskell : Haskell is a modern functional language and follows different programming
paradigms than Python. There are some common constructions like list comprehension.
Haskell is rarely used in scientific computing. See also [12].
Other Python literature
Here we give some hints to literature on Python which can serve as complementary sources
or as texts for parallel reading. Most introductory books on Python are devoted to teach this
language as a general purpose tool. One excellent example which we want to mention here
explicitly is [19]. It explains the language by simple examples, e.g. object oriented
programming is explained by organizing a pizza bakery.
There are very few books dedicated to Python directed towards scientific computing and
engineering. Among these few books we would like to mention the two books by
Langtangen which combine scientific computing with the modern "pythonic" view on
programming, [16,17].
This "pythonic" view is also the guiding line of our way of teaching programming of
numerical algorithms. We try to show how many well-established concepts and
constructions in computer science can be applied to problems within scientific computing.
The pizza-bakery example is replaced by Lagrange polynomials, generators become time
stepping methods for ODEs, and so on.
Finally we have to mention the nearly infinite amount of literature on the web. The web
was also a big source of knowledge when preparing this book. Literature from the web
often covers things that are new, but can also be totally outdated. The web also presents
solutions and interpretations which might contradict each other. We strongly recommend
to use the web as additional source, but we consider a "traditional" textbook with the web
resources "edited" as the better entry point to a rich new world.
[6]
Preface
Conventions
In this book, you will find a number of text styles that distinguish between different kinds
of information. Here are some examples of these styles and an explanation of their meaning.
Code words in text, database table names, folder names, filenames, file extensions,
pathnames, and user input are shown as follows: "install additional packages with conda
install within your virtual environment"
A block of code is set as follows:
from scipy import *
from matplotlib.pyplot import *
Any command-line input or output is written as follows:
jupyter notebook
New terms and important words are shown in bold. Words that you see on the screen, for
example, in menus or dialog boxes, appear in the text like this: "The Jupyter notebook is a
fantastic tool for demonstrating your work."
Warnings or important notes appear in a box like this.
Tips and tricks appear like this.
Reader feedback
Feedback from our readers is always welcome. Let us know what you think about this
book-what you liked or disliked. Reader feedback is important for us as it helps us develop
titles that you will really get the most out of. To send us general feedback, simply email feedback@packtpub.com, and mention the book's title in the subject of your
message. If there is a topic that you have expertise in and you are interested in either
writing or contributing to a book, see our author guide at www.packtpub.com/authors.
[7]
Preface
Customer support
Now that you are the proud owner of a Packt book, we have a number of things to help you
to get the most from your purchase.
Downloading the example code
You can download the example code files for this book from your account at http://www.p
acktpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.c
om/supportand register to have the files e-mailed directly to you.
You can download the code files by following these steps:
1.
2.
3.
4.
5.
6.
7.
Log in or register to our website using your e-mail address and password.
Hover the mouse pointer on the SUPPORT tab at the top.
Click on Code Downloads & Errata.
Enter the name of the book in the Search box.
Select the book for which you're looking to download the code files.
Choose from the drop-down menu where you purchased this book from.
Click on Code Download.
Once the file is downloaded, please make sure that you unzip or extract the folder using the
latest version of:
WinRAR / 7-Zip for Windows
Zipeg / iZip / UnRarX for Mac
7-Zip / PeaZip for Linux
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPubl
ishing/Scientific-Computing-with-Python-3. We also have other code bundles from
our rich catalog of books and videos available at https://github.com/PacktPublishing/.
Check them out!
Downloading the color images of this book
We also provide you with a PDF file that has color images of the screenshots/diagrams used
in this book. The color images will help you better understand the changes in the output.
You can download this file from https://www.packtpub.com/sites/default/files/down
loads/ScientificComputingwithPython3_ColorImages.pdf.
[8]
Preface
Errata
Although we have taken every care to ensure the accuracy of our content, mistakes do
happen. If you find a mistake in one of our books-maybe a mistake in the text or the codewe would be grateful if you could report this to us. By doing so, you can save other readers
from frustration and help us improve subsequent versions of this book. If you find any
errata, please report them by visiting http://www.packtpub.com/submit-errata, selecting
your book, clicking on the Errata Submission Form link, and entering the details of your
errata. Once your errata are verified, your submission will be accepted and the errata will
be uploaded to our website or added to any list of existing errata under the Errata section of
that title.
To view the previously submitted errata, go to https://www.packtpub.com/books/conten
t/supportand enter the name of the book in the search field. The required information will
appear under the Errata section.
Piracy
Piracy of copyrighted material on the Internet is an ongoing problem across all media. At
Packt, we take the protection of our copyright and licenses very seriously. If you come
across any illegal copies of our works in any form on the Internet, please provide us with
the location address or website name immediately so that we can pursue a remedy.
Please contact us at copyright@packtpub.com with a link to the suspected pirated
material.
We appreciate your help in protecting our authors and our ability to bring you valuable
content.
Questions
If you have a problem with any aspect of this book, you can contact us
at questions@packtpub.com, and we will do our best to address the problem.
[9]
1
Getting Started
In this chapter, we will give a brief overview of the principal syntactical elements of Python.
Readers who have just started learning programming are guided through the book in this
chapter. Every topic is presented here in a how-to way and will be explained later in the
book in a deeper conceptual manner and will also be enriched with many applications and
extensions.
Readers who are already familiar with another programming language will come across, in
this chapter, the Python way of doing classical language constructs. It offers them a quick
start to Python programming.
Both types of readers are encouraged to take this chapter as a brief guideline when
zigzagging through the book. However, before we start we have to make sure that
everything is in place and you have the correct version of Python installed together with the
main modules for Scientific Computing and tools, such as a good editor and a shell, which
helps in code developing and testing.
Read the following section, even if you already have access to a computer with Python
installed. You might want to adjust things to have a working environment conforming to
the presentation in this book.
Getting Started
Installation and configuration instructions
Before diving into the subject of the book you should have all the relevant tools installed on
your computer. We will give you some advice and recommend tools that you might want to
use. We only describe public domain and free tools.
Installation
There are currently two major versions of Python; the 2.x branch and the new 3.x branch.
There are language incompatibilities between these branches and one has to be aware of
which one to use. This book is based on the 3.x branch, considering the language up to
release 3.5.
For this book you need to install the following:
The interpreter: Python 3.5 (or later)
The modules for scientific computing: SciPy with NumPy
The module for graphical representation of mathematical results: matplotlib
The shell: IPython
A Python related editor: Spyder (refer to the following Figure 1.1, Spyder), Geany
The installation of these is eased by the so-called distribution packages. We recommend that
you use Anaconda. The default screen of Spyder consists of an editor window on left, a
console window in the lower right corner which gives access to an IPython shell and a help
window in the upper right corner as shown in the following figure:
[ 11 ]
Getting Started
Figure 1.1: The default screen of Spyder consists of an editor window on left, a
console window in the lower right corner which gives access to an IPython shell
and a help window in the upper right corner.
Anaconda
Even if you have Python pre-installed on your computer, we recommend that you create
your personal Python environment that allows you to work without the risk of accidentally
affecting the software on which your computer's functionality might depend. With a virtual
environment, such as Anaconda, you are free to change language versions and install
packages without the unintended side-effects.
If the worst happens and you screw things up totally, just delete the Anaconda directory
and start again. Running the Anaconda installer will install Python, a Python development
environment and editor (Spyder), the shell IPython, and the most important packages for
numerical computations, for example SciPy, NumPy, and matplotlib.
[ 12 ]
Getting Started
You can install additional packages with conda install within your virtual environment
created by Anaconda (refer for official documentation from[2]) .
Configuration
Most Python codes will be collected in files. We recommend that you use the following
header in all your Python files:
from scipy import *
from matplotlib.pyplot import *
With this, you make sure that all standard modules and functions used in this book, such as
SciPy, are imported. Without this step, most of the examples in the book would raise errors.
Many editors, such as Spyder, provide the possibility to create a template for your files.
Look for this feature and put the preceding header into a template.
Python Shell
The Python shell is good but not optimal for interactive scripting. We therefore recommend
using IPython instead (refer to [26] for the official documentation). IPython can be started
in different ways:
In a terminal shell by running the following command: ipython
By directly clicking on an icon called Jupyter QT Console
When working with Spyder you should use an IPython console (refer to Figure
1.1, Spyder).
[ 13 ]
Getting Started
Executing scripts
You often want to execute the contents of a file. Depending on the location of the file on
your computer, it is necessary to navigate to the correct location before executing the
contents of a file.
Use the command cd in IPython in order to move to the directory where your file
is located.
To execute the contents of a file named myfile.py, just run the following
command in the IPython shell
run myfile
Getting Help
Here are some tips on how to use IPython:
To get help on an object, just type ? after the object's name and then return.
Use the arrow keys to reuse the last executed commands.
You may use the Tab key for completion (that is, you write the first letter of a
variable or method and IPython shows you a menu with all the possible
completions).
Use Ctrl+D to quit.
Use IPython's magic functions. You can find a list and explanations by applying
%magic on the command prompt.
You can find out more about IPython in its online documentation, [15].
Jupyter – Python notebook
The Jupyter notebook is a fantastic tool for demonstrating your work. Students might want
to use it to make and document homework and exercises and teachers can prepare lectures
with it, even slides and web pages.
[ 14 ]
Getting Started
If you have installed Python via Anaconda, you already have everything for Jupyter in
place. You can invoke the notebook by running the following command in the terminal
window:
jupyter notebook
A browser window will open and you can interact with Python through your web browser.
Program and program flow
A program is a sequence of statements that are executed in a top-down order. This linear
execution order has some important exceptions:
There might be a conditional execution of alternative groups of statements
(blocks), which we refer to as branching.
There are blocks that are executed repetitively, which is
called looping (refer to the following Figure 1.2, Program flow).
There are function calls that are references to another piece of code, which is
executed before the main program flow is resumed. A function call breaks the
linear execution and pauses the execution of a program unit while it passes the
control to another unit–a function. When this gets completed, its control is
returned to the calling unit.
Figure 1.2: Program flow
[ 15 ]
Getting Started
Python uses a special syntax to mark blocks of statements: a keyword, a colon, and an
indented sequence of statements, which belong to the block (refer to the following Figure
1.3, Block command).
Figure 1.3: Block command
Comments
If a line in a program contains the symbol #, everything following on the same line is
considered as a comment:
# This is a comment of the following statement
a = 3 # ... which might get a further comment here
Line joining
A backslash \ at the end of the line marks the next line as a continuation line, that is, explicit
line joining. If the line ends before all the parentheses are closed, the following line will
automatically be recognized as a continuation line, that is, implicit line joining.
[ 16 ]
Getting Started
Basic types
Let's go over the basic data types that you will encounter in Python.
Numbers
A number may be an integer, a real number, or a complex number. The usual operations
are:
addition and subtraction, + and multiplication and division, * and /
power, **
Here is an example:
2 ** (2 + 2) # 16
1j ** 2 # -1
1. + 3.0j
The symbol for complex numbers
j is a symbol to denote the imaginary part of a complex number.
It is a syntactic element and should not be confused with multiplication by
a variable. More on complex numbers can be found in section Numeric
Types of Chapter 2, Variables and Basic Types.
Strings
Strings are sequences of characters, enclosed by simple or double quotes:
'valid string'
"string with double quotes"
"you shouldn't forget comments"
'these are double quotes: ".." '
You can also use triple quotes for strings that have multiple lines:
"""This is
a long,
long string"""
[ 17 ]
Getting Started
Variables
A variable is a reference to an object. An object may have several references. One uses the
assignment operator = to assign a value to a variable:
x =
y =
del
del
[3,
x #
x #
y #
4] # a list object is created
this object now has two labels: x and y
we delete one of the labels
both labels are removed: the object is deleted
The value of a variable can be displayed by the print function:
x = [3, 4] # a list object is created
print(x)
Lists
Lists are a very useful construction and one of the basic types in Python. A Python list is an
ordered list of objects enclosed by square brackets. One can access the elements of a list
using zero-based indexes inside square brackets:
L1 = [5, 6]
L1[0] # 5
L1[1] # 6
L1[2] # raises IndexError
L2 = ['a', 1, [3, 4]]
L2[0] # 'a'
L2[2][0] # 3
L2[-1] # last element: [3,4]
L2[-2] # second to last: 1
Indexing of the elements starts at zero. One can put objects of any type inside a list, even
other lists. Some basic list functions are as follows:
list(range(n))} creates a list with n elements, starting with zero:
print(list(range(5))) # returns [0, 1, 2, 3, 4]
len gives the length of a list:
len(['a', 1, 2, 34]) # returns 4
[ 18 ]
Getting Started
append is used to append an element to a list:
L = ['a', 'b', 'c']
L[-1] # 'c'
L.append('d')
L # L is now ['a', 'b', 'c', 'd']
L[-1] # 'd'
Operations on lists
The operator + concatenates two lists:
L1 = [1, 2]
L2 = [3, 4]
L = L1 + L2 # [1, 2, 3, 4]
As one might expect, multiplying a list with an integer concatenates the list with
itself several times:
n*L is equivalent to making n additions.
L = [1, 2]
3 * L # [1, 2, 1, 2, 1, 2]
Boolean expressions
A Boolean expression is an expression that may have the value True or False. Some
common operators that yield conditional expressions are as follow:
Equal, ==
Not equal, !=
Less than, Less than or equal to, < , <=
Greater than, Greater than or equal to, > , >=
[ 19 ]
Getting Started
One combines different Boolean values with or and and.
The keyword not , gives the logical negation of the expression that follows. Comparisons
can be chained so that, for example, x < y < z is equivalent to x < y and y < z. The
difference is that y is only evaluated once in the first example.
In both cases, z is not evaluated at all when the first condition, x < y, evaluates to False:
2 >= 4 # False
2 < 3 < 4 # True
2 < 3 and 3 < 2 # False
2 != 3 < 4 or False # True
2 <= 2 and 2 >= 2 # True
not 2 == 3 # True
not False or True and False # True!
Precedence rules
The <, >, <=, >=, !=, and == operators have higher precedence than not.
The operators and, or have the lowest precedence. Operators with higher
precedence rules are evaluated before those with lower.
Repeating statements with loops
Loops are used to repetitively execute a sequence of statements while changing a variable
from iteration to iteration. This variable is called the index variable. It is successively
assigned to the elements of a list, (refer to Chapter 9, Iterating):
L = [1, 2, 10]
for s in L:
print(s * 2) # output: 2 4 20
The part to be repeated in the for loop has to be properly indented:
for elt in my_list:
do_something
something_else
print("loop finished") # outside the for block
Repeating a task
One typical use of a for loop is to repeat a certain task a fixed number of times:
n = 30
for iteration in range(n):
do_something # this gets executed n times
[ 20 ]
Getting Started
Break and else
The for statement has two important keywords: break and else. break quits the for loop
even if the list we are iterating is not exhausted:
for x in x_values:
if x > threshold:
break
print(x)
The finalizing else checks whether the for loop was broken with the break keyword. If it
was not broken, the block following the else keyword is executed:
for x in x_values:
if x > threshold:
break
else:
print("all the x are below the threshold")
Conditional statements
This section covers how to use conditions for branching, breaking, or otherwise controlling
your code. A conditional statement delimits a block that will be executed if the condition is
true. An optional block, started with the keyword else will be executed if the condition is
not fulfilled (refer to Figure 1.3, Block command diagram). We demonstrate this by printing
|x|, the absolute value of x:
The Python equivalent is as follows:
x = ...
if x >= 0:
print(x)
else:
print(-x)
Any object can be tested for the truth value, for use in an if or while statement. The rules
for how the truth values are obtained are explained in section Boolean of Chapter 2,
Variables and Basic Types.
[ 21 ]
Getting Started
Encapsulating code with functions
Functions are useful for gathering similar pieces of code in one place. Consider the
following mathematical function:
The Python equivalent is as follows:
def f(x):
return 2*x + 1
In Figure 1.4 Anatomy of a function the elements of a function block are explained.
The keyword def tells Python we are defining a function.
f is the name of the function.
x is the argument, or input of the function.
What is after return is called the output of the function.
Figure 1.4: Anatomy of a function
Once the function is defined, it can be called using the following code:
f(2) # 5
f(1) # 3
[ 22 ]
Getting Started
Scripts and modules
A collection of statements in a file (which usually has a py extension), is called a script.
Suppose we put the contents of the following code into a file named smartscript.py:
def f(x):
return 2*x + 1
z = []
for x in range(10):
if f(x) > pi:
z.append(x)
else:
z.append(-1)
print(z)
In a Python or IPython shell, such a script can then be executed with the exec command
after opening and reading the file. Written as a one-liner it reads:
exec(open('smartscript.py').read())
The IPython shell provides the magic command %run as a handy alternative way to execute
a script:
%run smartscript
Simple modules – collecting functions
Often one collects functions in a script. This creates a module with additional Python
functionality. To demonstrate this, we create a module by collecting functions in a single
file, for example smartfunctions.py:
def f(x):
return 2*x + 1
def g(x):
return x**2 + 4*x - 5
def h(x):
return 1/f(x)
These functions can now be used by any external script or directly in the IPython
environment.
Functions within the module can depend on each other.
Grouping functions with a common theme or purpose gives modules that can be
shared and used by others.
[ 23 ]
Getting Started
Again, the command exec(open('smartfunctions.py').read()) makes these
functions available to your IPython shell (note that there is also the IPython magic function
run). In Python terminology, one says that they are put into the actual namespace.
Using modules and namespaces
Alternatively, the modules can be imported by the command import. It creates a
named namespace. The command from puts the functions into the general namespace:
import smartfunctions
print(smartfunctions.f(2))
# 5
from smartfunctions import g
print(g(1)) # 0
#import just this function
from smartfunctions import *
print(h(2)*f(2))
#import all
# 1.0
Import
The commands import and from import the functions only once into the
respective namespace. Changing the functions after the import has no
effect for the current Python session. More on modules can be found in
section Modules of Chapter 11, Namespaces, Scopes and Modules.
Interpreter
The Python interpreter executes the following steps:
First, run the syntax.
Then execute the code line by line.
Code inside a function or class declaration is not executed (but checked for
syntax).
def f(x):
return y**2
a = 3
# here both a and f are defined
[ 24 ]
Getting Started
You can run the preceding program because there are no syntactical errors. You get an error
only when you call the function f.
f(2) # error, y is not defined
Summary
In this chapter, we briefly addressed the main language elements of Python without going
into detail.
You should now be able to start playing with small pieces of code and to test different
program constructs. All this is intended as an appetizer for the following chapters in which
we will give you the details, examples, exercises, and more background information.
[ 25 ]
2
Variables and Basic Types
In this chapter, we will present the most important and basic types in Python. What is a
type? It is a set consisting of data content, its representation, and all possible operations.
Later in this book, we will make this definition much more precise, when we introduce the
concepts of a class in Chapter 8, Classes.
Variables
Variables are references to Python objects. They are created by assignments, for example:
a = 1
diameter = 3.
height = 5.
cylinder = [diameter, height] # reference to a list
Variables take names that consist of any combination of capital and small letters, the
underscore _ , and digits. A variable name must not start with a digit. Note that variable
names are case sensitive. A good naming of variables is an essential part of documenting
your work, so we recommend that you use descriptive variable names.
Variables and Basic Types
Python has some reserved keywords, which cannot be used as variable names (refer to
following table, Table 2.1). An attempt to use such a keyword as variable name would raise
a syntax error.
Table 2.1: Reserved Python keywords.
As opposed to other programming languages, variables require no type declaration. You
can create several variables with a multiple assignment statement:
a = b = c = 1
# a, b and c get the same value 1
Variables can also be altered after their definition:
a = 1
a = a + 1 # a gets the value 2
a = 3 * a
# a gets the value 6
The last two statements can be written by combining the two operations with an assignment
directly by using increment operators:
a += 1
a *= 3
# same as a = a + 1
# same as a = 3 * a
Numeric types
At some point, you will have to work with numbers, so we start by considering different
forms of numeric types in Python. In mathematics, we distinguish between natural
numbers (ℕ), integers (ℤ), rational numbers (ℚ), real numbers (ℝ) and complex numbers
(ℂ). These are infinite sets of numbers. Operations differ between these sets and may even
not be defined. For example, the usual division of two numbers in ℤ might not result in an
integer — it is not defined on ℤ.
[ 27 ]
Variables and Basic Types
In Python, like many other computer languages, we have numeric types:
The numeric type int, which is at least theoretically the entire ℤ
The numeric type float, which is a finite subset of ℝ and
The numeric type complex, which is a finite subset of ℂ
Finite sets have a smallest and a largest number and there is a minimum spacing between
two numbers; refer to the section on Floating Point Representation for further details.
Integers
The simplest numerical type is the integer type.
Plain integers
The statement k = 3 assigns the variable k to an integer.
Applying an operation of the type +, -, or * to integers returns an integer. The division
operator, //, returns an integer, while / may return a float:
6 // 2
7 // 2
7 / 2
# 3
# 3
# 3.5
The set of integers in Python is unbounded; there is no largest integer. The limitation here is
the computer’s memory rather than any fixed value given by the language.
If the division operator (/) in the example returns 3, you might not have
installed the correct Python version.
[ 28 ]
Variables and Basic Types
Floating point numbers
If you execute the statement a = 3.0 in Python, you create a floating-point number
(Python type: float). These numbers form a subset of rational numbers, ℚ.
Alternatively the constant could have been given in exponent notation as a = 30.0e-1 or
simply a = 30.e-1. The symbol e separates the exponent from the mantissa, and the
expression reads in mathematical notation a = 30.0 × 10−1. The name floating-point number
refers to the internal representation of these numbers and reflects the floating position of the
decimal point when considering numbers over a wide range.
Applying the elementary mathematical operations +, -, *, and / to two floating-point
numbers or to an integer and a floating-point number returns a floating-point number.
Operations between floating-point numbers rarely return the exact result expected from
rational number operations:
0.4 - 0.3 # returns 0.10000000000000003
This facts matters, when comparing floating point numbers:
0.4 - 0.3 == 0.1 # returns False
Floating point representation
Internally, floating-point numbers are represented by four quantities: the sign, the mantissa,
the exponent sign, and the exponent:
with β ϵ ℕ and x0≠ 0, 0 ≤ xi≤ β
x0…xt-1 is called the mantissa, β the basis and e the exponent |e| ≤ U . t is called the mantissa
length. The condition x0 ≠ 0 makes the representation unique and saves, in the binary case (β
= 2), one bit.
There exist two-floating point zeros +0 and -0, both represented by the mantissa 0.
On a typical Intel processor, β = 2 . To represent a number in the float type 64 bits are
used, namely 2 bits for the signs, t = 52 bits for the mantissa and 10 bits for the exponent
10
|e|. The upper bound U for the exponent is consequently 2 -1 = 1023.
[ 29 ]
Variables and Basic Types
With this data the smallest positive representable number is
flmin = 1.0 × 2-1023≈ 10-308 and the largest is flmax = 1.111…1 × 21023≈ 10308.
Note that floating-point numbers are not equally spaced in [0, flmax]. There is in particular a
gap at zero (refer to [29]). The distance between 0 and the first positive number is 2-1023,
while the distance between the first and the second is smaller by a factor 2-52≈ 2.2 × 10-16. This
effect, caused by the normalization x0 ≠ 0, is visualized in Figure 2.1.
This gap is filled equidistantly with subnormal floating-point numbers to which such a
result is rounded. Subnormal floating-point numbers have the smallest possible exponent
and do not follow the convention that the leading digit x0 has to differ from zero; refer to
[13].
Infinite and not a number
There are in total
floating-point numbers. Sometimes a numerical
algorithm computes floating-point numbers outside this range.
This generates number over- or underflow. In SciPy the special floating-point number inf
is assigned to overflow results:
exp(1000.) # inf
a = inf
3 - a
# -inf
3 + a
# inf
Working with inf may lead to mathematically undefined results. This is indicated in
Python by assigning the result another special floating-point number, nan. This stands for
not-a-number, that is, an undefined result of a mathematical operation:
a + a # inf
a - a # nan
a / a # nan
[ 30 ]
Variables and Basic Types
There are special rules for operations with nan and inf. For instance, nan compared to
anything (even to itself) always returns False:
x
x
x
x
= nan
< 0 # False
> 0 # False
== x # False
See Exercise 4 for some surprising consequences of the fact that nan is never equal to itself.
The float inf behaves much more as expected:
0 < inf
#
inf <= inf #
inf == inf #
-inf < inf #
inf - inf
#
exp(-inf)
#
exp(1 / inf)
True
True
True
True
nan
0
# 1
One way to check for nan and inf is to use the isnan and isinf functions. Often, one
wants to react directly when a variable gets the value nan or inf. This can be achieved by
using the NumPy command seterr. The following command
seterr(all = 'raise')
would raise an error if a calculation were to return one of those values.
Underflow – Machine Epsilon
Underflow occurs when an operation results in a rational number that falls into the gap at
zero; refer to Figure 2.1.
Figure 2.1: The floating point gap at zero, here t = 3, U = 1
The machine epsilon or rounding unit is the largest number ε such that float(1.0 + ε) = 1.0.
[ 31 ]
Variables and Basic Types
Note that ε ≈ β1-t/2 = 1.1102 × 10-16 on most of today’s computers. The value that is valid on
the actual machine you are running your code on is accessible using the following
command:
import sys
sys.float_info.epsilon # 2.220446049250313e-16 (something like that)
The variable sys.float_info contains more information about the internal
representation of the float type on your machine.
The function float converts other types to a floating-point number—if possible. This
function is especially useful when converting an appropriate string to a number:
a = float('1.356')
Other float types in NumPy
NumPy also provides other float types, known from other programming languages as
double-precision and single-precision numbers, namely float64 and float32:
a = pi
a1 = float64(a)
a2 = float32(a)
a - a1
a - a2
#
#
#
#
#
returns
returns
returns
returns
returns
3.141592653589793
3.1415926535897931
3.1415927
0.0
-8.7422780126189537e-08
The second last line demonstrates that a and a1 do not differ in accuracy. In the first two
lines, they only differ in the way they are displayed. The real difference in accuracy is
between a and its single-precision counterpart, a2.
The NumPy function finfo can be used to display information on these floating-point
types:
f32 = finfo(float32)
f32.precision
# 6 (decimal digits)
f64 = finfo(float64)
f64.precision
# 15 (decimal digits)
f = finfo(float)
f.precision
# 15 (decimal digits)
f64.max
# 1.7976931348623157e+308 (largest number)
f32.max
# 3.4028235e+38 (largest number)
help(finfo)
# Check for more options
[ 32 ]
Variables and Basic Types
Complex numbers
Complex numbers are an extension of the real numbers frequently used in many scientific
and engineering fields.
Complex Numbers in Mathematics
Complex numbers consist of two floating-point numbers, the real part a of the number and
its imaginary part b. In mathematics, a complex number is written as z=a+bi, where i defined
by i2 = -1 is the imaginary unit. The conjugate complex counterpart of z is
.
If the real part a is zero, the number is called an imaginary number.
The j notation
In Python, imaginary numbers are characterized by suffixing a floating-point number with
the letter j, for example, z = 5.2j. A complex number is formed by the sum of a floatingpoint number and an imaginary number, for example, z = 3.5 + 5.2j.
While in mathematics the imaginary part is expressed as a product of a real number b with
the imaginary unit i, the Python way of expressing an imaginary number is not a product: j
is just a suffix to indicate that the number is imaginary.
This is demonstrated by the following small experiment:
b
z
z
z
=
=
=
=
5.2
bj
# returns a NameError
b*j # returns a NameError
b*1j # is correct
The method conjugate returns the conjugate of z:
z = 3.2 + 5.2j
z.conjugate() # returns (3.2-5.2j)
[ 33 ]
Variables and Basic Types
Real and imaginary parts
One may access the real and imaginary parts of a complex number z using the real and
imag attributes. Those attributes are read-only:
z = 1j
z.real
z.imag
z.imag = 2
# 0.0
# 1.0
# AttributeError: readonly attribute
It is not possible to convert a complex number to a real number:
z = 1 + 0j
z == 1
# True
float(z)
# TypeError
Interestingly, the real and imag attributes as well as the conjugate method work just as
well for complex arrays (Chapter 4, Linear Algebra – Arrays). We demonstrate this by
computing the Nth roots of unity which are
, that is, the N solutions
of the equation
:
N = 10
# the following vector contains the Nth roots of unity:
unity_roots = array([exp(1j*2*pi*k/N) for k in range(N)])
# access all the real or imaginary parts with real or imag:
axes(aspect='equal')
plot(unity_roots.real, unity_roots.imag, 'o')
allclose(unity_roots**N, 1) # True
[ 34 ]
Variables and Basic Types
The resulting figure (Figure 2.2) shows the roots of unity together with the unit circle. (For
more details on how to make plots, refer Chapter 6, Plotting.)
Figure 2.2: Roots of unity together with a unit circle
It is of course possible to mix the previous methods, as illustrated by the following
examples:
z = 3.2+5.2j
(z + z.conjugate()) / 2.
# returns (3.2+0j)
((z + z.conjugate()) / 2.).real
# returns 3.2
(z - z.conjugate()) / 2.
# returns 5.2j
((z - z.conjugate()) / 2.).imag
# returns 5.2
sqrt(z * z.conjugate())
# returns (6.1057350089894991+0j)
[ 35 ]
Variables and Basic Types
Booleans
Boolean is a datatype named after George Boole (1815-1864). A Boolean variable can take
only two values, True or False. The main use of this type is in logical expressions. Here are
some examples:
a = True
b = 30 > 45
# b gets the value False
Boolean expressions are often used in conjunction with the if statement:
if x > 0:
print("positive")
else:
print("nonpositive)
Boolean operators
Boolean operations are performed using the and, or, and not keywords in Python:
True and False # False
False or True # True
(30 > 45) or (27 < 30) # True
not True # False
not (3 > 4) # True
The operators follow some precedence rules (refer to section Executing scripts in Chapter 1,
Getting started) which would make the parentheses in the third line and in the last obsolete
(it is a good practice to use them anyway to increase the readability of your code). Note that
the and operator is implicitly chained in the following Boolean expressions:
a < b < c
a == b == c
# same as: a < b and b < c
# same as: a == b and b == c
[ 36 ]
Variables and Basic Types
Rules of Conversion to Booleans:
Table 2.2 : Rule of conversion to Boolean
Boolean casting
Most objects Python may be converted to Booleans; this is called Boolean casting. The built-in
function bool performs that conversion. Note that most objects are cast to True, except 0,
the empty tuple, the empty list, the empty string, or the empty array. These are all cast to
False.
It is not possible to cast arrays into Booleans unless they contain no or only one element; this
is explained further in Chapter 5, Advanced Array Concepts. The previous table contains
summarized rules for Boolean casting. Some usage examples:
bool([])
# False
bool(0)
# False
bool(' ')
# True
bool('')
# False
bool('hello')
# True
bool(1.2)
# True
bool(array([1]))
# True
bool(array([1,2]))
# Exception raised!
[ 37 ]
Variables and Basic Types
Automatic Boolean casting
Using an if statement with a non-Boolean type will cast it to a Boolean. In other words, the
following two statements are always equivalent:
if a:
...
if bool(a): # exactly the same as above
...
A typical example is testing whether a list is empty:
# L is a list
if L:
print("list not empty")
else:
print("list is empty")
An empty array, list, or tuple will return False. You can also use a variable in the if
statement, for example, an integer:
# n is an integer
if n % 2:
print("n is odd")
else:
print("n is even")
Note that we used % for the modulo operation, which returns the remainder of an integer
division. In this case, it returns 0 or 1 as the remainder after modulo 2.
In this last example, the values 0 or 1 are cast to bool. Boolean operators or,and , and not
will also implicitly convert some of their arguments to a Boolean.
Return values of and and or
Note that the operatorsand and or do not necessarily produce Boolean values. The
expression x and y is equivalent to:
def and_as_function(x,y):
if not x:
return x
else:
return y
[ 38 ]
Variables and Basic Types
And the expression x or y is equivalent to:
def or_as_function(x,y):
if x:
return x
else:
return y
Interestingly, this means that when executing the statement True or x, the variable x need
not even be defined! The same holds for False and x.
Note that, unlike their counterparts in mathematical logic, these operators are no longer
commutative in Python. Indeed, the following expressions are not equivalent:
[1] or 'a' # produces [1]
'a' or [1] # produces 'a'
Boolean and integer
In fact, Booleans and integers are the same. The only difference is in the string
representation of 0 and 1 which is in the case of Booleans False and True respectively. This
allows constructions like this (for the format method refer section on string formatting):
def print_ispositive(x):
possibilities = ['nonpositive', 'positive']
return "x is {}".format(possibilities[x>0])
We note for readers already familiar with the concept of subclasses, that the type bool is a
subclass of the type int (refer to Chapter 8, Classes). Indeed, all four inquiries
isinstance(True, bool), isinstance(False, bool), isinstance(True, int),
and isinstance(False, int) return the value True (refer to section Type Checking in
Chapter 3, Container Types).
Even rarely used statements such as True+13 are syntactically correct.
Strings
The type string is a type used for text:
name = 'Johan Carlsson'
child = "Åsa is Johan Carlsson's daughter"
book = """Aunt Julia
and the Scriptwriter"""
[ 39 ]
Variables and Basic Types
A string is enclosed either by single or double quotes. If a string contains several lines, it has
to be enclosed by three double quotes """ or three single quotes '''.
Strings can be indexed with simple indexes or slices (refer to Chapter 3, Container Types, for
a comprehensive explanation on slices):
book[-1] # returns 'r'
book[-12:] # returns 'Scriptwriter'
Strings are immutable; that is, items cannot be altered. They share this property with tuples.
The command book[1] = 'a' returns:
TypeError: 'str' object does not support item assignment
The string '\n' is used to insert a line break and 't' inserts a horizontal tabulator (TAB)
into the string to align several lines:
print('Temperature:\t20\tC\nPressure:\t5\tPa')
These strings are examples of escape sequences. Escape sequences always start with a
backslash, \ . A multi line string automatically includes escape sequences:
a="""
A multiline
example"""
a # returns '\nA multiline \nexample'
A special escape sequence is "", which represents the backslash symbol in text:
latexfontsize="\\tiny"
The same can be achieved by using a raw string instead:
latexfs=r"\tiny"
# returns "\tiny"
latexfontsize == latexfs # returns True
Note that in raw strings, the backslash remains in the string and is used to escape some
special characters:
r"\"\"
r"\\"
r"\"
# returns '\\"'
# returns '\\\\'
# returns an error
[ 40 ]
Variables and Basic Types
Operations on strings and string methods
Addition of strings means concatenation:
last_name = 'Carlsson'
first_name = 'Johanna'
full_name = first_name + ' ' + last_name
# returns 'Johanna Carlsson'
Multiplication is just repeated addition:
game = 2 * 'Yo' # returns 'YoYo'
When strings are compared, lexicographical order applies and the uppercase form precedes
the lowercase form of the same letter:
'Anna' > 'Arvi' # returns false
'ANNA' < 'anna' # returns true
'10B' < '11A'
# returns true
Among the variety of string methods, we will mention here only the most important ones:
Splitting a string: This method generates a list from a string by using a single or
multiple blanks as separators. Alternatively, an argument can be given by
specifying a particular string as a separator:
text = 'quod erat
demonstrandum'
text.split() # returns ['quod', 'erat', 'demonstrandum']
table = 'Johan;Carlsson;19890327'
table.split(';') # returns ['Johan','Carlsson','19890327']
king = 'CarlXVIGustaf'
king.split('XVI') # returns ['Carl','Gustaf']
Joining a list to a string: This is the reverse operation of splitting:
sep = ';'
sep.join(['Johan','Carlsson','19890327'])
# returns 'Johan;Carlsson;19890327'
Searching in a string: This method returns the first index in the string, where a
given search substring starts:
birthday = '20101210'
birthday.find('10') # returns 2
If the search string is not found, the return value of the method is -1 .
[ 41 ]
Variables and Basic Types
String formatting
String formatting is done using the format method:
course_code = "NUMA21"
print("This course's name is {}".format(course_code))
# This course's name is NUMA21
The function format is a string method; it scans the string for the occurrence of
placeholders, which are enclosed by curly brackets. These placeholders are replaced in a
way specified by the argument of the format method. How they are replaced depends on
the format specification defined in each {} pair. Format specifications are indicated by a
colon, ":", as their prefix.
The format method offers a range of possibilities to customize the formatting of objects
depending on their types. Of particular use in scientific computing are the formatting
specifiers for the float type. One may choose either the standard with {:f} or the
exponential notation with {:e}:
quantity = 33.45
print("{:f}".format(quantity)) # 33.450000
print("{:1.1f}".format(quantity)) # 33.5
print("{:.2e}".format(quantity)) # 3.35e+01
The format specifiers allow to specify the rounding precision (digits following the decimal
point in the representation). Also the total number of symbols including leading blanks to
represent the number can be set.
In this example, the name of the object that gets its value inserted is given as an argument to
the format method. The first {} pair is replaced by the first argument and the following
pairs by the subsequent arguments. Alternatively, it may also be convenient to use the keyvalue syntax:
print("{name} {value:.1f}".format(name="quantity",value=quantity))
# prints "quantity 33.5"
Here, two values are processed, a string name without a format specifier and a float
value that is printed in fixed point notation with one digit after the decimal point. (We
refer to the complete reference documentation for more details on string formatting, [34]).
[ 42 ]
Variables and Basic Types
Braces in the string
Sometimes, a string might contain a pair of curly braces, which should not
be considered as placeholders for a format method. In that case, double
braces are used:
r"we {} in LaTeX \begin{{equation}}".format('like')
This returns the following string: 'we like in LaTeX
\\begin{equation}'.
Summary
In this chapter, you met the basic data types in Python and saw the corresponding syntax
elements. We will work mostly with numeric types such as integers, floats and complex.
Booleans are needed for setting conditions, and by using strings, we often communicate
results and messages.
Exercises
Ex. 1 → Check whether x = 2.3 is a zero of the function:
Ex. 2 → According to de Moivre's formula, the following holds:
Choose numbers n and x and verify that formula in Python.
Ex. 3 → Complex numbers. Verify Euler's formula in the same way:
[ 43 ]
Variables and Basic Types
Ex. 4 → Suppose we are trying to check the convergence of a diverging sequence (here the
sequence is defined by the recursive relation un+1 = 2un and u0 = 1.0):
u = 1.0 # you have to use a float here!
uold = 10.
for iteration in range(2000):
if not abs(u-uold) > 1.e-8:
print('Convergence')
break # sequence has converged
uold = u
u = 2*u
else:
print('No convergence')
1. Since the sequence does not converge, the code should print the No
convergence message. Execute it to see what happens.
2. What happens if you replace the line:
if not abs(u-uold) > 1.e-8
with:
if abs(u-uold) < 1.e-8
It should give exactly the same result, shouldn't it? Run the code again to see
what happens.
3. What happens if you replace u=1.0 by u=1 (without decimal point). Run the code
to check your predictions.
4. Explain the unexpected behavior of this code. The key to understand what
happens is that inf evaluates to nan, and the comparison of nan with anything
else is returns always the value False .
Ex. 5 → An implication C = (A ⇒ B) is a Boolean expression that is defined as
C is True if A is False or A and B are both True
C is False otherwise
Write a Python function implication(A, B).
[ 44 ]
Variables and Basic Types
Ex. 6 → This exercise is to train Boolean operations. Two binary digits (bits) are added by
using a logical device called a half adder. It produces a carry bit (the digit of the next higher
value) and the sum as defined by the following table, and half adder circuit.
p
q
sum
carry
1
1
0
1
1
0
1
0
0
1
1
0
0
0
0
0
Definition of the half adder operation
Figure 2.3: A half adder circuit
[ 45 ]
Variables and Basic Types
A full adder consists of two half adders and sums up two bits and an additional carry bit on
the input (refer to the following figure):
Figure 2.4: A full adder circuit
Write a function that implements a half adder and another that implements a full adder.
Test these functions.
[ 46 ]
3
Container Types
Container types are used to group objects together. The main difference between the
different container types is the way individual elements are accessed and how operations
are defined.
Lists
A list is, as the name hints, a list of objects of any kind:
L = ['a' 20.0, 5]
M = [3,['a', -3.0, 5]]
The individual objects are enumerated by assigning each element an index. The first
element in the list gets index 0. This zero-based indexing is frequently used in mathematical
notation. Consider the usual indexing of coefficients of a polynomial.
The index allows us to access the following objects:
L[1] # returns 20.0
L[0] # returns 'a'
M[1] # returns ['a',-3.0,5]
M[1][2] # returns 5
The bracket notation here corresponds to the use of subscripts in mathematical formulas. L
is a simple list, while M itself contains a list so that one needs two indexes to access an
element of the inner list.
A list containing subsequent integers can easily be generated by the command range:
L=list(range(4)) # generates a list with four elements: [0, 1, 2 ,3]
Container Types
A more general use is to provide this command with start, stop, and step parameters:
L=list(range(17,29,4)) # generates [17, 21, 25]
The command len returns the length of the list:
len(L) # returns 3
Slicing
Slicing a list between i and j creates a new list containing the elements starting at index
i and ending just before j.
For slicing, a range of indexes has to be given. L[i:j] means create a list by taking all
elements from L starting at L[i] until L[j-1]. In other words, the new list is obtained by
removing the first i elements from L and taking the next j-i elements (for j > i ≥ 0). See
the following figure (Figure 3.1) for more examples:
Figure 3.1: Some typical slicing situations
[ 48 ]
Container Types
Here, L[i:] means remove the i first elements, L[:i] means take only the first i elements,
and similarly, L[:-i] means remove the last i elements, and L[-i:] means take only the
last i elements. This may be combined in L[i:-j] to remove the first i and the last j
elements:
L = ['C', 'l', 'o', 'u', 'd', 's']
L[1:5] # remove one element and take four from there:
# returns ['l', 'o', 'u', 'd']
One may omit the first or last bound of the slicing:
L = ['C', 'l', 'o', 'u','d', 's']
L[1:] # ['l', 'o', 'u', 'd','s']
L[:5] # ['C', 'l', 'o','u','d']
L[:] # the entire list
Python allows the use of negative indexes for counting from the right. In particular, the
element L[-1] is the last element in the list L.
Some list indexing descriptions:
L[i:] amounts to taking all elements except the i first ones
L[:i] amounts to taking the first i elements
L[-i:] amounts to taking the last i elements
L[:-i] amounts to taking all elements except the i last ones
Here is an example:
L = ['C', 'l', 'o', 'u', 'd', 's']
L[-2:] # ['d', 's']
L[:-2] # ['C', 'l', 'o','u']
Omitting one index in the range corresponds to half-open intervals in ℝ. The half-open
interval (∞, a) means, take all numbers strictly lower than a; this is similar to the syntax
L[:j].
Out of-bound slices
Notice that you never get index errors with out-of-bound slices. Possibly,
you may obtain empty lists.
[ 49 ]
Container Types
Here is an example:
L = list(range(4)) # [0, 1, 2, 3]
L[4] # IndexError: list index out of range
L[1:100] # same as L[1:]
L[-100:-1] # same as L[:-1]
L[-100:100] # same as L[:]
L[5:0] # empty list []
L[-2:2] # empty list []
Be careful when using variables in indexing that may become negative, since it changes the
slice completely. This might lead to unexpected results:
a = [1,2,3]
for iteration in range(4):
print(sum(a[0:iteration-1]))
The result is 3, 0, 1, 3 while one expects 0, 0, 1, 3.
Strides
When computing slices, one may also specify a stride, which is the length of the step from
one index to the other. The default stride is one. Here is an example:
L = list(range(100))
L[:10:2] # [0, 2, 4, 6, 8]
L[::20] # [0, 20, 40, 60, 80]
L[10:20:3] # [10, 13, 16, 19]
Note that the stride may also be negative:
L[20:10:-3] # [20, 17, 14, 11]
It is also possible to create a new list that is reversed, using a negative stride (find about
reverse method in section In-place operations):
L = [1, 2, 3]
R = L[::-1] # L is not modified
R # [3, 2, 1]
[ 50 ]
Container Types
Altering lists
Typical operations on lists are insertion and deletion of elements and list concatenation.
With the slicing notation, list insertion and deletion become obvious; deletion is just
replacing a part of a list by an empty list []:
L = ['a', 1, 2, 3, 4]
L[2:3] = [] # ['a', 1, 3, 4]
L[3:] = [] # ['a', 1, 3]
Insertion means replacing an empty slice with the list to be inserted:
L[1:1] = [1000, 2000] # ['a', 1000, 2000, 1, 3]
Two lists are concatenated by the plus operator + :
L = [1, -17]
M = [-23.5, 18.3, 5.0]
L + M # gives [1, -17, 23.5, 18.3, 5.0]
Concatenating a list n times with itself motivates the use of the multiplication operator *:
n = 3
n * [1.,17,3] # gives [1., 17, 3, 1., 17, 3, 1., 17, 3]
[0] * 5 # gives [0,0,0,0,0]
There is no arithmetic operations on list, such as elementwise summation or division. For
such operations we use arrays (refer to section Array).
Belonging to a list
One may use the keywords in and not in to determine whether an element belongs to a
list or not which is similar to and in mathematics:
L = ['a', 1, 'b', 2]
'a' in L # True
3 in L # False
4 not in L # True
[ 51 ]
Container Types
List methods
Some useful methods of the list type are collected in the following Table 3.1:
Command
Action
list.append(x)
Add x to the end of the list.
list.expand(L)
Expand the list by the elements of the list L.
list.insert(i,x) Insert x at positioni.
list.remove(x)
Remove the first item from the list whose value is x.
list.count(x)
The number of times x appears in the list.
list.sort()
Sort the items of the list, in place.
list.reverse()
Reverse the elements of the list, in place.
list.pop()
Remove the last element of the list, in place.
Table 3.1: Methods of the datatype list
There are two ways list methods can act:
They can directly alter the list, that is, in-place operations.
They produce a new object.
In–place operations
All methods that result in a list are in-place operating methods, for example, reverse:
L = [1, 2, 3]
L.reverse() # the list
L is now reversed
L # [3, 2, 1]
Be aware of in-place operations. One might be tempted to write:
L=[3, 4, 4, 5]
newL = L.sort()
This is correct Python. But it results in a possibly unintended alternation of L in a variable
newL having the value None. The reason is that sort operates in-place.
[ 52 ]
Container Types
Here we demonstrate in-place operating methods:
L = [0, 1, 2, 3, 4]
L.append(5) # [0, 1, 2,
L.reverse() # [5, 4, 3,
L.sort() # [0, 1, 2, 3,
L.remove(0) # [1, 2, 3,
L.pop() # [1, 2, 3, 4]
L.pop() # [1, 2, 3]
L.extend(['a','b','c'])
3,
2,
4,
4,
4, 5]
1, 0]
5]
5]
# [1, 2, 3, 'a', 'b', 'c']
L is altered. The count method is an example of a method that generates a new object:
L.count(2) # returns 1
Merging lists – zip
A particularly useful function for lists is zip. It can be used to merge two given lists into a
new list by pairing the elements of the original lists. The result is a list of tuples (refer
section Tuples for more information):
ind = [0,1,2,3,4]
color = ["red", "green", "blue", "alpha"]
list(zip(color,ind)) # gives [('red', 0), ('green', 1),
('blue', 2), ('alpha', 3)]
This example also demonstrates what happens if the lists have different lengths. The length
of the zipped list is the shorter of the two input lists.
zip creates a special iterable object that can be turned into a list by applying the list
function, as in the preceding example. Refer to section Iterators in Chapter 9, Iterating, for
more details on iterable objects.
List comprehension
A convenient way to build up lists is by using the list comprehension construct, possibly
with a condition inside. The syntax of a list comprehension is:
[ for in ]
[ 53 ]
Container Types
or more generally:
[ for in if ]
Here is an example:
L = [2, 3, 10, 1, 5]
L2 = [x*2 for x in L] # [4, 6, 20, 2, 10]
L3 = [x*2 for x in L if 4 < x <= 10] # [20, 10]
It is possible to have several for loops inside a list comprehension:
M = [[1,2,3],[4,5,6]]
flat = [M[i][j] for i in range(2) for j in range(3)]
# returns [1, 2, 3, 4, 5, 6]
This is of particular interest when dealing with arrays.
Set notation
List comprehension is closely related to the mathematical notation for sets.
Compare:
and L2 = [2*x for x in L].
One big difference though, is that lists are ordered while sets aren't (Refer,
section Sets for more information).
Arrays
The NumPy package offers arrays, which are container structures for manipulating vectors,
matrices, or even higher order tensors in mathematics. In this section, we point out the
similarities between arrays and lists. But arrays deserve a broader presentation, which will
be given in Chapter 4, Linear Algebra – Arrays, and Chapter 5, Advanced Array Concepts.
Arrays are constructed from lists by the function array :
v = array([1.,2.,3.])
A = array([[1.,2.,3.],[4.,5.,6.]])
To access an element of a vector, we need one index, while an element of a matrix is
addressed by two indexes:
v[2]
A[1,2]
# returns 3.0
# returns 6.0
[ 54 ]
Container Types
At first glance, arrays are similar to lists, but be aware that they are different in a
fundamental way, which can be explained by the following points:
Access to array data corresponds to that of lists, using square brackets and slices.
They may also be used to alter the array:
M = array([[1.,2.],[3.,4.]])
v = array([1., 2., 3.])
v[0] # 1
v[:2] # array([1.,2.])
M[0,1] # 2
v[:2] = [10, 20] # v is now array([10., 20., 3.])
The number of elements in a vector, or the number of rows of a matrix, is
obtained by the function len :
len(v) # 3
Arrays store only elements of the same numeric type (usually float or complex
but also int). Refer to section Array properties in Chapter 4, Liner Algebra –
Arrays, for more information.
The operations +, *, /, and - are all elementwise. The dot function and, in
Python versions ≥ 3.5, the infix operator @ are used for the scalar product and the
corresponding matrix operations.
Unlike lists, there is no append method for arrays. Nevertheless, there are special
methods to construct arrays by stacking smaller size arrays (Refer to section
Stacking in Chapter 4, Linear Algebra – Arrays, for more information.). A related
point is that arrays are not elastic as lists; one cannot use slices to change their
length.
Vector slices are views; that is, they may be used to modify the original array.
Refer to section Array views and copies in Chapter 5, Advanced Array Concepts, for
more information.
[ 55 ]
Container Types
Tuples
A tuple is an immutable list. Immutable means that it cannot be modified. A tuple is just a
comma-separated sequence of objects (a list without brackets). To increase readability, one
often encloses a tuple in a pair of parentheses:
my_tuple = 1, 2, 3
# our first tuple
my_tuple = (1, 2, 3)
# the same
my_tuple = 1, 2, 3,
# again the same
len(my_tuple) # 3, same as for lists
my_tuple[0] = 'a'
# error! tuples are immutable
The comma indicates that the object is a tuple:
singleton = 1,
len(singleton)
# note the comma
# 1
Tuples are useful when a group of values goes together; for example, they are used to
return multiple values from functions (refer to section Returns Values in Chapter 7,
Functions. One may assign several variables at once by unpacking a list or tuple:
a, b = 0, 1 #
a, b = [0, 1]
(a, b) = 0, 1
[a,b] = [0,1]
a
#
#
#
gets 0 and b gets 1
exactly the same effect
same
same thing
The swapping trick
Use packing and unpacking to swap the contents of two variables:
a, b = b, a
To summarize:
Tuples are nothing other than immutable lists with a notation without brackets.
In most cases, lists may be used instead of tuples.
The notation without parentheses is convenient but dangerous. You should use
parentheses when you are not sure:
a, b = b, a # the swap trick; equivalent to:
(a, b) = (b, a)
# but
1, 2 == 3, 4 # returns (1, False, 4)
(1, 2) == (3, 4) # returns False
[ 56 ]
Container Types
Dictionaries
Lists, tuples, and arrays are ordered sets of objects. The individual objects are inserted,
accessed, and processed according to their place in the list. On the other hand, dictionaries
are unordered sets of pairs. One accesses dictionary data by keys.
Creating and altering dictionaries
For example, we may create a dictionary containing the data of a rigid body in mechanics,
as follows:
truck_wheel = {'name':'wheel','mass':5.7,
'Ix':20.0,'Iy':1.,'Iz':17.,
'center of mass':[0.,0.,0.]}
A key/data pair is indicated by a colon, :. These pairs are comma separated and listed
inside a pair of curly brackets, {}.
Individual elements are accessed by their keys:
truck_wheel['name']
truck_wheel['mass']
# returns 'wheel'
# returns 5.7
New objects are added to the dictionary by creating a new key:
truck_wheel['Ixy'] = 0.0
Dictionaries are also used to provide parameters to a function (refer to section Parameters
and arguments in Chapter 7, Functions, for further information). Keys in a dictionary can be,
among others, strings, functions, tuples with immutable elements, and classes. Keys cannot
be lists or arrays. The command dict generates a dictionary from a list with key/value
pairs:
truck_wheel = dict([('name','wheel'),('mass',5.7),('Ix',20.0),
('Iy',1.), ('Iz',17.),
('center of mass',[0.,0.,0.])])
The zip function may come in handy in this context (refer to section Merging List).
[ 57 ]
Container Types
Looping over dictionaries
There are mainly three ways to loop over dictionaries:
By keys:
for key in truck_wheel.keys():
print(key) # prints (in any order) 'Ix', 'Iy', 'name',...
or equivalently:
for key in truck_wheel:
print(key) # prints (in any order) 'Ix', 'Iy', 'name',...
By value:
for value in truck_wheel.value():
print(value)
# prints (in any order) 1.0, 20.0, 17.0, 'wheel', ...
By item, that is, key/value pairs:
for item in truck_wheel.items():
print(item)
# prints (in any order) ('Iy', 1.0), ('Ix, 20.0),...
Please, refer to section Shelves in Chapter 12, Input and Output, for a special dictionary
object for file access.
Sets
Sets are containers that share properties and operations with sets in mathematics. A
mathematical set is a collection of distinct objects. Here are some mathematical set
expressions:
[ 58 ]
Container Types
And their Python counterparts:
A
B
C
D
E
5
= {1,2,3,4}
= {5}
= A.union(B)
# returns set([1,2,3,4,5])
= A.intersection(C)
# returns set([1,2,3,4])
= C.difference(A)
# returns set([5])
in C
# returns True
Sets contain an element only once, corresponding to the aforementioned definition:
A = {1,2,3,3,3}
B = {1,2,3}
A == B # returns True
And a set is unordered; that is, the order of the elements in the set is not defined:
A = {1,2,3}
B = {1,3,2}
A == B # returns True
Sets in Python can contain all kinds of hashable objects, that is, numeric objects, strings, and
Booleans.
There are union and intersection methods:
A={1,2,3,4}
A.union({5})
A.intersection({2,4,6}) # returns set([2, 4])
Also, sets can be compared using the methods issubset and issuperset :
{2,4}.issubset({1,2,3,4,5}) # returns True
{1,2,3,4,5}.issuperset({2,4}) # returns True
Empty set
An empty set is defined in Python by empty_set=set([]) and not by {},
which would define an empty dictionary!
[ 59 ]
Container Types
Container conversions
We summarize in the following Table 3.2 the most important properties of the container
types presented so far. Arrays will be treated in Chapter 4, Linear Algebra – Arrays.
Table 3.2 : Container Types
As you can see in the previous table, there is a difference in accessing container elements,
and sets and dictionaries are not ordered.
Due to the different properties of the various container types, we frequently convert one
type to another:
[ 60 ]
Container Types
Type checking
The direct way to see the type of a variable is to use the type command:
label = 'local error'
type(label) # returns str
x = [1, 2] # list
type(x) # returns list
However, if you want to test for a variable to be of a certain type, you should use
isinstance (instead of comparing the types with type):
isinstance(x, list) # True
The reason for using isinstance becomes apparent after having read Chapter 8,
Classes, and in particular the concept of subclassing and inheritance in section Subclassing
and Inheritance in Chapter 8, Classes. In short, often different types share some common
properties with some basic type. The classical example is the type bool, which is derived by
subclassing from the more general type int. In this situation, we see how the command
isinstance can be used in a more general way:
test = True
isinstance(test, bool) # True
isinstance(test, int) # True
type(test) == int # False
type(test) == bool # True
So, in order to make sure that the variable test is as good as an integer (the particular type
may be irrelevant), you should check that it is an instance of integer:
if isinstance(test, int):
print("The variable is an integer")
Type checking
Python is not a typed language. What that means is that objects are
identified by what they can do rather than what they are. For instance, if
you have a string manipulating function that acts on an object by using
the len method, then your function will probably be useful for any objects
implementing that method.
So far, we have come across different types: float, int, bool, complex, list, tuple,
module, function, str, dict, and array.
[ 61 ]
Container Types
Summary
In this chapter, you learned to work with container types, mainly lists. It is important to
know how to fill these containers and how to access their content. We saw that there is
access by position or by keyword.
We will meet the important concept of slicing again in the next chapter on arrays. These are
containers specially designed for mathematical operations.
Exercises
Ex. 1 → Execute the following statements:
L = [1, 2]
L3 = 3*L
1. What is the content of L3?
2. Try to predict the outcome of the following commands:
L3[0]
L3[-1]
L3[10]
3. What does the following command do?
L4 = [k**2 for k in L3]
4. Concatenate L3 and L4 to a new list L5.
Ex. 2 → Use the range command and a list comprehension to generate a list with 100
equidistantly spaced values between 0 and 1.
Ex. 3 → Assume that the following signal is stored in a list:
L = [0,1,2,1,0,-1,-2,-1,0]
[ 62 ]
Container Types
What is the outcome of:
L[0]
L[-1]
L[:-1]
L + L[1:-1] + L
L[2:2] = [-3]
L[3:4] = []
L[2:5] = [-5]
Do this exercise by inspection only, that is, without using your Python Shell.
Ex. 4 → Consider the Python statements:
L = [n-m/2 for n in range(m)]
ans = 1 + L[0] + L[-1]
and assume that the variable m has been previously assigned an integer value. What is the
value of ans? Answer this question without executing the statements in Python.
Ex. 5 → Consider the recursion formula:
with n = 0,…, 1000, h= 1/1000, and a = -0.5.
0
1. Create a list u. Store in its first three elements e , eha, and e2ha. These represent the
starting values u0, u1, and u2 in the given formula. Build up the complete list from
the recursion formula.
2. Construct a second list, td, in which you store the values nh, with n = 0, …, 1000.
Plot td versus u (refer section Basic plotting in Chapter 6, Plotting, for more
information). Make a second plot in which you plot the difference, that is, |eat –
un|, where tn represents the values inside the vector td . Set axis labels and a title.
n
The recursion is a multistep formula to solve the differential equation u' = au with the initial
value u(0) = u0 = 1. un approximates u(nh) = eanhu0.
[ 63 ]
Container Types
Ex. 6 → Let A and B be sets. The set (A \ B) ∪ (B \ A) is called the symmetric difference of
the two sets. Write a function that performs this operation. Compare your results to the
result of the command:
A.symmetric_difference(B).
Ex. 7 → Verify in Python the statement that the empty set is a subset of any set.
Ex. 8 → Study other operations on sets. You find a complete list of those by using the
command completion feature of IPython. In particular, study the update and
intersection_update methods. What is the difference between intersection and
intersection_update?
[ 64 ]
4
Linear Algebra – Arrays
Linear algebra is one of the essential building blocks of computational mathematics. The
objects of linear algebra are vectors and matrices. The package NumPy includes all the
necessary tools to manipulate those objects.
The first task is to build matrices and vectors, or to alter existing ones by slicing. The other
main task is the dot operation, which embodies most of the linear algebra operations (scalar
product, matrix-vector product, and matrix-matrix product). Finally, various methods are
available to solve linear problems.
Overview of the array type
For the impatient, here is how to use arrays in a nutshell. Be aware though that the behavior
of arrays may be surprising at first, so we encourage you to read on after this introductory
section.
Vectors and matrices
Creating vectors is as simple as using the function array to convert a list to an array:
v = array([1.,2.,3.])
The object v is now a vector that behaves much like a vector in linear algebra. We have
already emphasized the differences with the list object in Python (refer to section Arrays in
Chapter 3, Containers Type). Here are some illustrations of the basic linear algebra
operations on vectors:
# two vectors with three components
v1 = array([1., 2., 3.])
Linear Algebra – Arrays
v2 = array([2, 0, 1.])
# scalar multiplications/divisions
2*v1 # array([2., 4., 6.])
v1/2 # array([0.5, 1., 1.5])
# linear combinations
3*v1 # array([ 3., 6., 9.])
3*v1 + 2*v2 # array([ 7., 6., 11.])
# norm
from scipy.linalg import norm
norm(v1) # 3.7416573867739413
# scalar product
dot(v1, v2) # 5.
v1 @ v2 # 5 ; alternative formulation
Note that all basic arithmetic operations are performed elementwise:
# elementwise operations:
v1 * v2 # array([2., 0., 3.])
v2 / v1 # array([2.,0.,.333333])
v1 - v2 # array([-1., 2., 2.])
v1 + v2 # array([ 3., 2., 4.])
Some functions act elementwise on arrays as well:
cos(v1) # cosine, elementwise: array([ 0.5403,
-0.4161, -0.9899])
This subject will be covered in the section Functions Acting on Arrays.
A matrix is created in a similar way to a vector, but from a list of lists instead:
M = array([[1.,2],[0.,1]])
Vectors are no column – and no row matrices
The n vector, an n × 1, and a 1 × n matrix are three different objects even if
they contain the same data.
To create a row matrix containing the same data as the vector v = array([1., 2., 1.]),
we do this:
R = array([[1.,2.,1.]]) # notice the double brackets:
# this is a matrix
shape(R)
# (1,3): this is a row matrix
[ 66 ]
Linear Algebra – Arrays
The corresponding column matrix is obtained by the method reshape:
C = array([1., 2., 1.]).reshape(3, 1)
shape(C) # (3,1): this is a column matrix
Indexing and slices
Indexing and slicing are similar to that of a list. The main difference is that there may be
several indexes or slices when the array is a matrix. The subject will be covered in depth in
section Array indexing; here, we just give some illustrating examples of indexing and slicing:
v = array([1., 2., 3])
M = array([[1., 2],[3., 4]])
v[0] # works as for lists
v[1:] # array([2., 3.])
M[0, 0] # 1.
M[1:] # returns the matrix array([[3., 4]])
M[1] # returns the vector array([3., 4.])
# access
v[0] # 1.
v[0] = 10
# slices
v[:2] # array([10., 2.])
v[:2] = [0, 1] # now v == array([0., 1., 3.])
v[:2] = [1, 2, 3] # error!
Linear algebra operations
The essential operator that performs most of the usual operations of linear algebra is the
Python function dot. It is used for matrix-vector multiplications:
dot(M, v) # matrix vector multiplication; returns a vector
M @ v # alternative formulation
It may be used to compute a scalar product between two vectors:
dot(v, w) # scalar product; the result is a scalar
v @ w # alternative formulation
[ 67 ]
Linear Algebra – Arrays
Lastly, it is used to compute matrix-matrix products:
dot(M, N) # results in a matrix
M @ N # alternative formulation
Solving a linear system
If A is a matrix and b is a vector, you can solve the linear equation:
Using the solve method, which has this syntax:
from scipy.linalg import solve
x = solve(A, b)
For example, we want to solve:
Here is the solution for the preceding equation:
from scipy.linalg import solve
A = array([[1., 2.], [3., 4.]])
b = array([1., 4.])
x = solve(A, b)
allclose(dot(A, x), b) # True
allclose(A @ x, b) # alternative formulation
The command allclose is used here to compare two vectors. If they are close enough to
each other, this command returns True. Optionally a tolerance value can be set. For more
methods related to linear equations systems, refer to section Linear algebra methods in SciPy.
[ 68 ]
Linear Algebra – Arrays
Mathematical preliminaries
In order to understand how arrays work in NumPy, it is useful to understand the
mathematical parallel between accessing tensor (matrix and vector) elements by indexes
and evaluating mathematical functions by providing arguments. We also cover in this
section the generalization of the dot product as a reduction operator.
Arrays as functions
Arrays may be considered from several different points of view. We believe that the most
fruitful one in order to understand arrays is that of functions of several variables.
For instance, selecting a component of a given vector in ℝn may just be considered a
function from the set of ℕn to ℝ, where we define the set:
Here the set ℕn has n elements. The Python function range generates ℕn.
Selecting an element of a given matrix, on the other hand, is a function of two parameters,
taking its value in ℝ. Picking a particular element of an m × n matrix may thus be
considered a function from ℕm × ℕn to ℝ.
Operations are elementwise
NumPy arrays are essentially treated as mathematical functions. This is in particular true
for operations. Consider two functions, f and g, defined on the same domain and taking real
values. The product f g of those two functions is defined as the pointwise product, that is:
[ 69 ]
Linear Algebra – Arrays
Note that this construction is possible for any operation between two functions. For an
arbitrary operation defined on two scalars, which we denote here by , we could define
as follows:
This innocuous remark allows us to understand NumPy's stance on operations; all
operations are elementwise in arrays. For instance, the product between two matrices m and
n is defined, as with functions, as follows:
Shape and number of dimensions
There is a clear distinction between a:
Scalar: Function with no arguments
Vector: Function with one argument
Matrix: Function with two arguments
Higher order tensor: Function with more than two arguments
In what follows, the number of dimensions is the number of arguments of a function. The
shape corresponds essentially to the domain of definition of a function.
For instance, a vector of size n is a function from the set ℕn to ℝ. As a result, its domain of
definition is ℕn. Its shape is defined as the singleton (n,). Similarly, a matrix of size m × n is a
function defined on ℕm × ℕm. The corresponding shape is simply the pair (m, n). The shape of
an array is obtained by the numpy.shape function, and the number of dimensions by the
numpy.ndim function.
[ 70 ]
Linear Algebra – Arrays
The dot operations
Treating arrays as functions, although very powerful, completely neglects the linear algebra
structures we are familiar with, that is, matrix-vector and matrix-matrix
operations. Fortunately, these linear algebra operations may all be written in a similar
unified form:
The vector-vector operation:
The matrix-vector operation:
The matrix-matrix operation:
The vector-matrix operation:
The essential mathematical concept is that of reduction. For a matrix-vector operation, the
reduction is given by:
In general, a reduction operation defined between two tensors T and U of respective
number of dimensions m and n may be defined as:
[ 71 ]
Linear Algebra – Arrays
Clearly, the shapes of the tensors must be compatible for that operation to make any sense.
This requirement is familiar for matrix-matrix multiplication. The multiplication M N of
matrices M and N only makes sense if the number of columns of M equals the number of
rows of N.
Another consequence of the reduction operation is that it produces a new tensor with m + n
– 2 dimensions. In the following table, we gather the output of the reduction operation for
the familiar cases involving matrices and vectors:
Table 4.1: Output of the reduction operation for the familiar cases involving matrices and
vectors
In Python, all reduction operations are performed using the dot function:
angle = pi/3
M = array([[cos(angle), -sin(angle)],
[sin(angle), cos(angle)]])
v = array([1., 0.])
y = dot(M, v)
As in mathematical textbooks, also in modern Python (Version 3.5 and higher), the dot
product is sometimes preferred to be written in its operator form, dot(M, v), or by using
the more handy infix notation, M @ v. From now on we stick to the operator form; you can
modify the examples if the other form is preferred.
Elementwise versus matrix multiplication
The multiplication operator * is always elementwise. It has nothing to do
with the dot operation. Even if A is a matrix and v is a vector, A*v is still a
legal operation.
The matrix-vector multiplication is performed using the dot function.
Refer to section Broadcasting of Chapter 5, Advanced Array Concepts, for
more information.
[ 72 ]
Linear Algebra – Arrays
The array type
The objects used to manipulate vectors, matrices, and more general tensors in NumPy are
called arrays. In this section, we examine their essential properties, how to create them, and
how to access their information.
Array properties
Arrays are essentially characterized by three properties, which is given in the following
table (Table 4.2):
Name
Description
shape
It describes how the data should be interpreted, as a vector, a matrix or as a higher
order tensor, and it gives the corresponding dimension. It is accessed with the
shape attribute.
dtype
It gives the type of the underlying data (float, complex, integer, and so on).
strides This attribute specifies in which order the data should be read. For instance, a
matrix could be stored in memory contiguously column by column (the FORTRAN
convention), or row by row (the C convention). The attribute is a tuple with the
numbers of bytes that have to be skipped in memory to reach the next row and the
number of bytes to be skipped to reach the next column. The strides attribute
even allows for a more flexible interpretation of the data in memory, which is what
makes array views possible.
Table 4.2 : Properties of Arrays
Consider the following array:
A = array([[1, 2, 3], [3, 4, 6]])
A.shape
# (2, 3)
A.dtype
# dtype('int64')
A.strides # (24, 8)
Its elements have type 'int64'; that is, they use 64 bits or 8 bytes in memory. The complete
array is stored in memory row-wise. The distance from A[0, 0] to the first element in the
next row A[1,0] is thus 24 bytes (three matrix elements) in memory. Correspondingly, the
distance in memory between A[0,0] and A[0,1] is 8 bytes (one matrix element). These
values are stored in the attribute strides .
[ 73 ]
Linear Algebra – Arrays
Creating arrays from lists
The general syntax to create an array is the function array . The syntax to create a real
vector would be:
V = array([1., 2., 1.], dtype=float)
To create a complex vector with the same data:
V = array([1., 2., 1.], dtype=complex)
When no type is specified, the type is guessed. The array function chooses the type that
allows storing of all the specified values:
V = array([1, 2]) # [1, 2] is a list of integers
V.dtype # int
V = array([1., 2]) # [1., 2] mix float/integer
V.dtype # float
V = array([1. + 0j, 2.]) # mix float/complex
V.dtype # complex
Silent type conversion
NumPy silently casts floats into integers, which might give unexpected results:
a = array([1, 2, 3])
a[0] = 0.5
a # now: array([0, 2, 3])
The same often unexpected array type casting happens from complex to float.
Array and Python parentheses
As we have noticed in section Program and program flow in Chapter 1, Getting Started,
Python allows a line break when some opening brace or parenthesis is not closed. This
allows a convenient syntax for array creation, which makes it more pleasing to the human
eye:
# the identity matrix in 2D
Id = array([[1., 0.], [0., 1.]])
# Python allows this:
Id = array([[1., 0.],
[0., 1.]])
# which is more readable
[ 74 ]
Linear Algebra – Arrays
Accessing array entries
Array entries are accessed by indexes. In contrast to vector coefficients two indexes are
needed to access matrix coefficients. These are given in one pair of brackets. This
distinguishes the array syntax from a list of lists. There, two pairs of brackets are needed to
access elements.
M = array([[1., 2.],[3., 4.]])
M[0, 0] # first row, first column: 1.
M[-1, 0] # last row, first column: 3.
Basic array slicing
Slices are similar to those of lists except that there might now be in more than one
dimension:
M[i,:] is a vector filled by the row i of M.
M[:,j] is a vector filled by the column i of M.
M[2:4,:] is a slice of 2:4 on the rows only.
M[2:4,1:4] is a slice on rows and columns.
The result of matrix slicing is given in the following figure (Figure 4.1):
Figure 4.1: The result of matrix slicing
[ 75 ]
Linear Algebra – Arrays
Omitting a dimension
If you omit an index or a slice, NumPy assumes you are taking rows only.
M[3] is a vector that is a view on the third row of M and M[1:3] is a
matrix that is a view on the second and third rows of M.
Changing the elements of a slice affects the entire array:
v = array([1., 2., 3.])
v1 = v[:2] # v1 is array([1., 2.])
v1[0] = 0. # if v1 is changed ...
v # ... v is changed too: array([0., 2., 3.])
General slicing rules are given in the following table (Table 4.3):
Table 4.3: General Slicing Rules
The results of slicing operations for an array M of shape (4, 4) are given in the following table
(Table 4.4):
Table 4.4: Result of slicing operation for an array M of shape (4,4)
[ 76 ]
Linear Algebra – Arrays
Altering an array using slices
You may alter an array using slices or by direct access. The following changes only one
element in a 5 × 3 matrix M:
M[1, 3] = 2.0 # scalar
But we may change one full row of the matrix:
M[2, :] = [1., 2., 3.] # vector
We may also replace a full submatrix:
M[1:3, :] = array([[1., 2., 3.],[-1.,-2., -3.]])
There is a distinction between a column matrix and a vector. The
following assignment with a column matrix returns no error
M[1:4, 2:3] = array([[1.],[0.],[-1.0]])
while the assignment with a vector returns a Value Error
M[1:4, 2:3] = array([1., 0., -1.0]) # error
The general slicing rules are shown in Table 4.2. The matrices and vectors in the preceding
examples must have the right size to fit into matrix M. You may also make use of the
broadcasting rules (for more information, refer to section Broadcasting of Chapter 5,
Advanced Array Concepts) to determine the allowed size of the replacement arrays. If the
replacement array does not have the right shape, a ValueError exception will be raised.
Functions to construct arrays
The usual way to set up an array is via a list. But there are also a couple of convenient
methods for generating special arrays, which are given in the following table (Table 4.5):
Methods
Shape Generates
zeros((n,m))
(n,m)
Matrix filled with zeros
ones((n,m))
(n,m)
Matrix filled with ones
diag(v,k)
(n,n)
(Sub-, super-) diagonal matrix from a vector v
random.rand(n,m)
(n,m)
Matrix filled with uniformly distributed random numbers in
(0,1)
[ 77 ]
Linear Algebra – Arrays
arange(n)
(n,)
First n integers
linspace(a,b,n)
(n,)
Vector with n equispaced points between a and b
Table 4.5: Commands to create arrays
These commands may take additional arguments. In particular, the commands zeros,
ones, and arange take dtype as an optional argument. The default type is float, except
for arange. There are also methods such as zeros_like and ones_like, which are slight
variants of the preceding ones. For instance, the zeros_like(A) method is equivalent to
zeros(shape(A)).
Here is the identity function, which constructs an identity matrix of a given size:
I = identity(3)
The command is identical to:
I = array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
Accessing and changing the shape
The number of dimensions is what distinguishes a vector from a matrix. The shape is what
distinguishes vectors of different sizes, or matrices of different sizes. In this section, we
examine how to obtain and change the shape of an array.
The shape function
The shape of a matrix is the tuple of its dimensions. The shape of an n × m matrix is the
tuple (n, m). It can be obtained by the shape function:
M = identity(3)
shape(M) # (3, 3)
For a vector, the shape is a singleton containing the length of that vector:
v = array([1., 2., 1., 4.])
shape(v) # (4,) <- singleton (1-tuple)
[ 78 ]
Linear Algebra – Arrays
An alternative is to use the array attribute shape, which gives the same result:
M = array([[1.,2.]])
shape(M) # (1,2)
M.shape # (1,2)
However, the advantage of using shape as a function is that this function may be used on
scalars and lists as well. This may come in handy when code is supposed to work with both
scalars and arrays:
shape(1.) # ()
shape([1,2]) # (2,)
shape([[1,2]]) # (1,2)
Number of dimensions
The number of dimensions of an array is obtained with the function numpy.ndim or using
the array attribute ndarray.ndim :
ndim(A) # 2
A.ndim # 2
Note that the number of dimensions, given by the function ndim, of a tensor T (a vector,
matrix, or higher order tensor) is always equal to the length of its shape:
T = zeros((2,2,3)) # tensor of shape (2,2,3); three dimensions
ndim(T) # 3
len(shape(T)) # 3
Reshape
The method reshape gives a new view of the array, with a new shape, without copying the
data:
v = array([0,1,2,3,4,5])
M = v.reshape(2,3)
shape(M) # returns (2,3)
M[0,0] = 10 # now v[0] is 10
[ 79 ]
Linear Algebra – Arrays
Reshape does not copy
Reshape does not create a new array. It rather gives a new view on the
existing array. In the preceding example, changing one element of M
would automatically result in a change in the corresponding element in v.
When this behavior is not acceptable, you need to copy the data.
The various effects of the reshape method on an array defined by arange(6) are given in
the following figure :
Figure 4.2: The various effects of the reshape method on an array defined by arange(6)
If one tries to reshape an array with a shape that does not multiply to the original shape, an
error is raised:
ValueError: total size of new array must be unchanged.
[ 80 ]
Linear Algebra – Arrays
Sometimes, it is convenient to specify only one shape parameter and let Python determine
the other in such a way that it multiplies to the original shape. This is done by setting the
free shape parameter -1:
v = array([1, 2, 3, 4, 5, 6, 7, 8])
M = v.reshape(2, -1)
shape(M) # returns (2, 4)
M = v.reshape(-1, 2)
shape(M) # returns (4,2 )
M = v.reshape(3,- 1) # returns error
Transpose
A special form of reshaping is transposing. It just switches the two shape elements of the
matrix. The transpose of a matrix A is a matrix B such that:
Which is resolved in the following way:
A = ...
shape(A) # 3,4
B = A.T # A transpose
shape(B) # 4,3
Transpose does not copy
Transposition is very similar to reshaping. In particular, it does not copy
the data either and just returns a view on the same array:
A= array([[ 1., 2.],[ 3., 4.]])
B=A.T
A[1,1]=5.
B[1,1] # 5
Transposing a vector makes no sense since vectors are tensors of one dimension, that is,
functions of one variable. NumPy will, however, comply and return exactly the same object:
v = array([1., 2., 3.])
v.T # exactly the same vector!
[ 81 ]
Linear Algebra – Arrays
What you have in mind when you want to transpose a vector is probably to create a row or
column matrix. This is done using reshape:
v.reshape(-1, 1) # column matrix containing v
v.reshape(1, -1) # row matrix containing v
Stacking
The universal method to build matrices from a couple of (matching) submatrices is
concatenate. Its syntax is:
concatenate((a1, a2, ...), axis = 0)
This command stacks the submatrices vertically (on top of each other) when axis=0 is
specified. With the axis=1 argument, they are stacked horizontally, and this generalizes
according to arrays with more dimensions. This function is called by several convenient
functions, as follows:
hstack: Used to stack matrices horizontally
vstack: Used to stack matrices vertically
columnstack: Used to stack vectors in columns
Stacking vectors
One may stack vectors row-wise or column-wise using vstack and column_stack, as
illustrated in the following figure:
[ 82 ]
Linear Algebra – Arrays
hstack would produce the concatenation of v1 and v2.
Let us consider the symplectic permutation as an example for vector stacking:
We have a vector of size 2n. We want to perform a symplectic transformation of a vector
with an even number of components, that is, exchange the first half with the second half of
the vector with sign change:
This operation is resolved in Python as follows:
# v is supposed to have an even length.
def symp(v):
n = len(v) // 2 # use the integer division //
return hstack([v[-n:], -v[:n]])
Functions acting on arrays
There are different types of functions acting on arrays. Some act elementwise, and they
return an array of the same shape. Those are called universal functions. Other array
functions return an array of a different shape.
Universal functions
Universal functions are functions that act elementwise on arrays. They thus have an output
array that has the same shape as the input array. These functions allow us to compute the
result of a scalar function on a whole array at once.
Built-in universal functions
A typical example is the cos function (the one provided by NumPy):
cos(pi) # -1
cos(array([[0, pi/2, pi]])) # array([[1, 0, -1]])
[ 83 ]
Linear Algebra – Arrays
Note that universal functions work on arrays in a componentwise manner. This is also true
for operators, such as multiplication or exponent:
2 * array([2, 4]) # array([4, 8])
array([1, 2]) * array([1, 8]) # array([1, 16])
array([1, 2])**2 # array([1, 4])
2**array([1, 2]) # array([1, 4])
array([1, 2])**array([1, 2]) # array([1, 4])
Create universal functions
Your function will automatically be universal if you use only universal functions in it. If,
however, your function uses functions that are not universal, you might get scalar results,
or even an error, when trying to apply them on an array:
def const(x):
return 1
const(array([0, 2])) # returns 1 instead of array([1, 1])
Another example is the following:
def heaviside(x):
if x >= 0:
return 1.
else:
return 0.
heaviside(array([-1, 2])) # error
The expected behaviour would be that the heaviside function applied to a vector [a, b]
would return [heaviside(a), heaviside(b)]. Alas, this does not work because the
function always returns a scalar, no matter the size of the input argument. Besides, using
the function with an array input would raise an exception. The NumPy function
vectorize allows us to quickly solve this problem:
vheaviside = vectorize(heaviside)
vheaviside(array([-1, 2])) # array([0, 1]) as expected
[ 84 ]
Linear Algebra – Arrays
A typical application of this method is its use when plotting a function:
xvals = linspace(-1, 1, 100)
plot(xvals, vectorize(heaviside)(xvals))
axis([-1.5, 1.5, -0.5, 1.5])
The following graph shows the heaviside function:
The vectorize function does not improve performance. It provides only
a convenient way to quickly transform a function, so that it operates
elementwise on list and arrays.
[ 85 ]
Linear Algebra – Arrays
Array functions
There are a number of functions acting on arrays that do not act componentwise. Examples
of such functions are max, min, and sum. These functions may operate on the whole matrix,
row-wise, or column-wise. When no argument is provided, they act on the whole matrix.
Suppose A is the following matrix:
The sum function acting on that matrix returns a scalar:
sum(A) # 36
The command has an optional parameter, axis . It allows us to choose along which axis to
perform the operation. For instance, if the axis is 0, it means that the sum should be
computed along the first axis. The sum along axis 0 of an array of shape (m, n) will be a
vector of length n.
Suppose we compute the sum of A along the axis 0:
sum(A, axis=0) # array([ 6, 8, 10, 12])
This amounts to computing the sum on the columns:
The result is a vector:
Now suppose we compute the sum along the axis 1:
A.sum(axis=1) # array([10, 26])
[ 86 ]
Linear Algebra – Arrays
This amounts to computing the sum on the rows:
The result is a vector:
Linear algebra methods in SciPy
SciPy offers a large range of methods from numerical linear algebra in its scipy.linalg
module. Many of these methods are Python wrapping programs from LAPACK, a collection
of well-approved FORTRAN subroutines used to solve linear equation systems and
eigenvalue problems. Linear algebra methods are the core of any method in scientific
computing, and the fact that SciPy uses wrappers instead of pure Python code makes these
central methods extremely fast. We present in detail here how two linear algebra problems
are solved with SciPy to give you a flavour of this module.
Solving several linear equation systems with LU
Let A be an n × n matrix and b1, b2, …, bk be a sequence of n-vectors. We consider the
problem to find n vectors xi such that:
We assume that the vectors bi are not known simultaneously. In particular, it is quite a
common situation that the ith problem has to be solved before bi+1 becomes available.
LU factorization is a way to organize the classical Gauss elimination method in such a way
that the computation is done in two steps:
A factorization step of the matrix A to get matrices in triangular form
A relatively cheap backward and forward elimination step that works on the bi's
and benefits from the more time-consuming factorization step
[ 87 ]
Linear Algebra – Arrays
The method also uses the fact that if P is a permutation matrix such that PA is the original
matrix with its rows permuted.
The two systems
have the same solution.
LU factorization finds a permutation matrix P, a lower triangular matrix L, and an upper
triangular matrix U such that:
.
Such a factorization always exists. Furthermore, L can be determined in such a way that Lii =
1. Thus, the essential data from L that has to be stored is Lij with i > j. Consequently, L and U
can be stored together in an n × n array, while the information about the permutation matrix
P just requires an n integer vector – the pivot vector.
In SciPy, there are two methods to compute the LU factorization. The standard one is
scipy.linalg.lu, which returns the three matrices L, U, and P. The other method
islu_factor. That is the method we describe here, because it will be conveniently used
later in combination with lu_solve:
import scipy.linalg as sl
[LU,piv] = sl.lu_factor(A)
Here, the A matrix is factorized and an array with the information about L and U is returned,
together with the pivot vector. With this information, the system can be solved by
performing row interchanges of the vectors bi according to the information stored in the
pivot vector, backward substitution using U, and finally forward substitution using L. This
is bundled in Python, in the lu_solve method. The following code snippet shows how the
system Axi = bi is solved once the LU factorization is performed and its results stored in the
tuple (LU, piv):
import scipy.linalg as sl
xi = sl.lu_solve((LU, piv), bi)
[ 88 ]
Linear Algebra – Arrays
Solving a least square problem with SVD
A linear equation system Ax = b, with A being an m × n matrix and m > n, is called an
overdetermined linear system. In general it has no classical solution and one seeks a vector
x* ℝn with the property:
Here,
denotes the Euclidean vector norm
.
This problem is called a least square problem. A stable method to solve it is based on
factorizing A = UΣVT, with U being a m × m orthogonal matrix, V a n × n orthogonal matrix,
and Σ = (σij) an m × n matrix with the property σij = 0 for all i ≠j. This factorization is called a
singular value decomposition (SVD).
We write,
with a diagonal n × n matrix Σ1. If we assume that A has full rank, then Σ1 is invertible and
it can be shown that,
. If we split U = [U1 U2] with U1 being an m × n submatrix,
then the preceding equation can be simplified to
.
SciPy provides a function called svd, which we use to solve this task:
import scipy.linalg as sl
[U1, Sigma_1, VT] = sl.svd(A, full_matrices = False,
compute_uv = True)
xast = dot(VT.T, dot(U1.T, b) / Sigma_1)
r = dot(A, xast) - b # computes the residual
nr = sl.norm(r, 2) # computes the Euclidean norm of r
[ 89 ]
Linear Algebra – Arrays
The keyword full_matrices says that only the portion U1 of U needs to be computed. As
one often uses svd to compute only singular values, σii, we have to explicitly demand the
computation of U and V by using the keyword compute_uv. The SciPy function
scipy.linalg.lstsq solves the least square problem similarly by using a singular value
decomposition.
More methods
In the examples so far, you met a couple of methods for computational tasks in linear
algebra, for example, solve. Most common methods are available after the
command import scipy.linalg as sl is executed. We refer to their documentation for
further reference. Some linear algebra functions of the scipy.linalg module are given in
the following table (Table 4.6):
Methods
Description (matrix methods)
sl.det
Determinant of a matrix
sl.eig
Eigenvalues and eigenvectors of a matrix
sl.inv
Matrix inverse
sl.pinv
Matrix pseudoinverse
sl.norm
Matrix or vector norm
sl.svd
Singular value decomposition
sl.lu
LU decomposition
sl.qr
QR decomposition
sl.cholesky
Cholesky decomposition
sl.solve
Solution of a general or symmetric linear system: Ax = b
sl.solve.banded The same for banded matrices
sl.lstsq
Least squares solution
Table 4.6: Linear algebra functions of the scipy.linalg module
Execute import scipy.linalg as sl first.
[ 90 ]
Linear Algebra – Arrays
Summary
In this chapter, we worked with the most important objects in linear algebra – vectors and
matrices. For this, we learned how to define arrays and we met important array methods. A
smaller section demonstrated how to use modules from scipy.linalg to solve central
tasks in linear algebra.
Exercises
Ex. 1 → Consider a 4 × 3 matrix M:
1. Construct this matrix in Python using the function array .
2. Construct the same matrix using the function arange followed by a suitable
reshape.
3. What is the result of the expression M[2,:] ? What is the result of the similar
expression M[2:]?
Ex. 2 → Given a vector x, construct in Python the following matrix:
[ 91 ]
Linear Algebra – Arrays
Here, xi are the components of the vector x (numbered from zero). Given a vector y, solve in
Python the linear equation system Va = y. Let the components of a be denoted by ai, i = 0, …,
5. Write a function poly, which has a and z as input and which computes the polynomial:
Plot this polynomial and depict in the same plot the points (xi, yi) as small stars. Try your
code with the vectors:
x = (0.0, 0.5, 1.o, 1.5, 2.0, 2.5)
y = (-2.0, 0.5, -2.0, 1.0, -0.5, 1.0)
Ex. 3 → The matrix V in Ex. 2 is called a Vandermonde matrix. It can be set up in Python
directly by the command vander. Evaluating a polynomial defined by a coefficient vector
can be done with the Python command polyval. Repeat Ex. 2 by using these commands.
Ex. 4 → Let u be a one dimensional array. Construct another array ξ with values ξi = (u1 + ui+1
+ ui+2)/3. In statistics, this array is called the moving average of u. In approximation theory, it
plays the role as the Greville abscissae of cubic splines. Try to avoid the use of for loops in
your script.
Ex. 5 →
1.
2.
3.
4.
Construct from the matrix V given in Ex. 2 a matrix A by deleting V's first column.
Form the matrix B = (AT A)-1 AT.
Compute c = B y with y from Ex. 2.
Use c and polyval to plot the polynomial defined by c. Plot in the same picture
again the points (xi, yi).
Ex. 6 → Ex. 5 describes the least square method. Repeat that exercise but use SciPy's
scipy.linalg.lstsq method instead.
[ 92 ]
Linear Algebra – Arrays
Ex. 7 → Let v be a vector written in its coordinate form as a 3 × 1 matrix
T
[1 -1 1] . Construct the projection matrices:
.
Show experimentally that v is an eigenvector for both matrices P and Q. What are the
corresponding eigenvalues?
Ex. 8 → In numerical linear algebra the m × m matrix A with the property
is used as an example for an extreme growth-factor, when performing LU factorization.
Set up this matrix in Python for various m, compute its LU factorization using the
command scipy.linalg.lu and derive experimentally a statement about the growth
factor
in relation to m.
[ 93 ]
5
Advanced Array Concepts
In this chapter, we will explain some more advanced aspects of arrays. First, we will cover
the notion of an array view, followed by Boolean arrays and how to compare arrays. We
briefly describe indexing and vectorization, explain sparse arrays, and some special topics
such as broadcasting.
Array views and copies
In order to control precisely how memory is used, NumPy offers the concept of view of an
array. Views are smaller arrays that share the same data as a larger array. This works just
like a reference to one single object (refer to section Basic Types in Chapter 1, Getting
Started).
Array views
The simplest example of a view is given by a slice of an array:
M = array([[1.,2.],[3.,4.]])
v = M[0,:] # first row of M
The preceding slice is a view of M. It shares the same data as M. Modifying v will modify M as
well:
v[-1] = 0.
v # array([[1.,0.]])
M # array([[1.,0.],[3.,4.]]) # M is modified as well
Advanced Array Concepts
It is possible to access the object that owns the data using the array attribute base:
v.base # array([[1.,0.],[3.,4.]])
v.base is M # True
If an array owns its data, the attribute base is none :
M.base # None
Slices as views
There are precise rules on which slices will return views and which ones will return copies.
Only basic slices (mainly index expressions with :) return views, whereas any advanced
selections (such as slicing with a Boolean) will return a copy of the data. For instance, it is
possible to create new matrices by indexing with lists (or arrays):
a = arange(4) # array([0.,1.,2.,3.])
b = a[[2,3]] # the index is a list [2,3]
b # array([2.,3.])
b.base is None # True, the data was copied
c = a[1:3]
c.base is None # False, this is just a view
In the preceding example, the array b is not a view, whereas the array c, obtained with a
simpler slice, is a view.
There is an especially simple slice of an array that returns a view of the whole array:
N = M[:] # this is a view of the whole array M
Transpose and reshape as views
Some other important operations return views. For instance, transpose returns a view:
M = random.random_sample((3,3))
N = M.T
N.base is M # True
The same applies for all reshaping operations:
v = arange(10)
C = v.reshape(-1,1) # column matrix
C.base is v # True
[ 95 ]
Advanced Array Concepts
Array copy
Sometimes it is necessary to explicitly request that the data be copied. This is simply
achieved with the NumPy function called array:
M = array([[1.,2.],[3.,4.]])
N = array(M.T) # copy of M.T
We may verify that the data has indeed been copied:
N.base is None # True
Comparing arrays
Comparing two arrays is not as simple as it may seem. Consider the following code, which
is intended to check whether two matrices are close to each other:
A = array([0.,0.])
B = array([0.,0.])
if abs(B-A) < 1e-10: # an exception is raised here
print("The two arrays are close enough")
This code raises the exception when the if statement is executed:
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()
In this section, we explain why this is so and how to remedy this state of affairs.
Boolean arrays
Boolean arrays are useful for advanced array indexing (refer to section Indexing with Boolean
arrays). A Boolean array is simply an array for which the entries have the type bool:
A = array([True,False]) # Boolean array
A.dtype # dtype('bool')
[ 96 ]
Advanced Array Concepts
Any comparison operator acting on arrays will create a Boolean array instead of a simple
Boolean:
M = array([[2, 3],
[1, 4]])
M > 2 # array([[False, True],
# [False, True]])
M == 0 # array([[False, False],
# [False, False]])
N = array([[2, 3],
[0, 0]])
M == N # array([[True, True],
# [False, False]])
...
Note that because array comparison creates Boolean arrays, one cannot use array
comparison directly in conditional statements, for example, if statements. the solution is to
use the methods all and any:
A = array([[1,2],[3,4]])
B = array([[1,2],[3,3]])
A == B # creates array([[True, True], [True, False]])
(A == B).all() # False
(A != B).any() # True
if (abs(B-A) < 1e-10).all():
print("The two arrays are close enough")
Checking for equality
Checking for equality of two float arrays is not straight forward, because two floats may be
very close without being equal. In NumPy, it is possible to check for equality with
allclose. This function checks for equality of two arrays up to a given precision:
data = random.rand(2)*1e-3
small_error = random.rand(2)*1e-16
data == data + small_error # False
allclose(data, data + small_error, rtol=1.e-5, atol=1.e-8)
# True
The tolerance is given in terms of a relative tolerance bound, rtol, and an absolute error
bound, atol. The command allclose is a short form of: (abs(A-B) <
atol+rtol*abs(B)).all().
[ 97 ]
Advanced Array Concepts
Note that allclose can be also applied to scalars:
data = 1e-3
error = 1e-16
data == data + error # False
allclose(data, data + error, rtol=1.e-5, atol=1.e-8)
#True
Boolean operations on arrays
You cannot use and, or, and not on Boolean arrays. Indeed, those operators force the
casting from array to Boolean, which is not permitted. Instead, we can use the operators
given in the following table (Table 5.1) for componentwise logical operations on Boolean
arrays:
Logic operator
Replacement for Boolean arrays
A and B
A & B
A or B
A | B
not A
~ A
Table 5.1 Logical operators and, or and not do not work with arrays.
A = array([True, True, False, False])
B = array([True, False, True, False])
A and B # error!
A & B # array([True, False, False, False])
A | B # array([True, True, True, False])
~A # array([False, False, True, True])
Here is an example usage of logical operators with Boolean arrays:
Suppose that we have a sequence of data that is marred with some measurement error.
Suppose further that we run a regression and it gives us a deviation for each value. We wish
to obtain all the exceptional values and all the values with little deviation that are lower
than a given threshold:
data = linspace(1,100,100) # data
deviation = random.normal(size=100) # the deviations
#don't forget the parentheses in next statement!
exceptional = data[(deviation<-0.5)|(deviation>0.5)]
exceptional = data[abs(deviation)>0.5] # same result
small = data[(abs(deviation)<0.1)&(data<5.)] # small deviation and data
[ 98 ]
Advanced Array Concepts
Array indexing
We have already seen that one may index arrays by combinations of slices and integers, this
is the basic slicing technique. There are, however, many more possibilities, which allow for
a variety of ways to access and modify array elements.
Indexing with Boolean arrays
It is often useful to access and modify only parts of an array, depending on its value. For
instance, one might want to access all the positive elements of an array. This turns out to be
possible using Boolean arrays, which act like masks to select only some elements of an
array. The result of such an indexing is always a vector. For instance, consider the following
example:
B = array([[True, False],
[False, True]])
M = array([[2, 3],
[1, 4]])
M[B] # array([2,4]), a vector
In fact, the M[B] call is equivalent to M.flatten()[B]. One may then replace the resulting
vector by another vector. For instance, one may replace all the elements by zero (refer to
section Broadcasting for more information):
M[B] = 0
M # [[0, 3], [1, 0]]
Or one may replace all the selected values by others:
M[B] = 10, 20
M # [[10, 3], [1, 20]]
By combining the creation of Boolean arrays (M > 2), smart indexing (indexing with
Boolean array), and broadcasting, one may use the following elegant syntax:
M[M>2] = 0
# all the elements > 2 are replaced by 0
The expression broadcasting here refers to the tacit conversion of the scalar 0 to a vector of
an appropriate shape.
[ 99 ]
Advanced Array Concepts
Using where
The command where gives a useful construct that can take a Boolean array as a condition
and either return the indexes of the array elements satisfying the condition or return
different values depending on the values in the Boolean array.
The basic structure is:
where(condition, a, b)
This will return values from a when the condition is True and values from b when it is
False.
For instances consider, a Heaviside function:
The following code implements a Heaviside function:
def H(x):
return where(x < 0, 0, 1)
x = linspace(-1,1,11) # [-1. -0.8 -0.6 -0.4 -0.2 0. 0.2 0.4 0.6 0.8 1. ]
print(H(x))
# [0 0 0 0 0 1 1 1 1 1 1]
The second and third arguments can be either arrays of the same size as the condition (the
Boolean array) or scalars. We give two more example to demonstrated how to manipulate
elements from an array or a scalar depending on a condition:
x = linspace(-4,4,5)
# [-4. -2. 0. 2. 4.]
print(where(x > 0, sqrt(x), 0))
# [ 0.+0.j 0.+0.j 0.+0.j 1.41421356+0.j 2.+0.j ]
print(where(x > 0, 1, -1)) # [-1 -1 -1 1 1]
If the second and third arguments are omitted, then a tuple containing the indexes of the
elements satisfying the condition is returned.
[ 100 ]
Advanced Array Concepts
For example consider the use of where with only one argument in the following code:
a = arange(9)
b = a.reshape((3,3))
print(where(a > 5))
# (array([6, 7, 8]),)
print(where(b > 5))
# (array([2, 2, 2]), array([0, 1, 2]))
Performance and Vectorization
When it comes to performance of your Python code, it often boils down to the difference
between interpreted code and compiled code. Python is an interpreted programming
language and basic Python code is executed directly without any intermediate compilation
to machine code. With a compiled language, the code needs to be translated to machine
instructions before execution.
The benefits of an interpreted language are many but interpreted code cannot compete with
compiled code for speed. To make your code faster, you can write some parts in a compiled
language like FORTRAN, C, or C++. This is what NumPy and SciPy do.
For this reason, it is best to use functions in NumPy and SciPy over interpreted versions
whenever possible. NumPy array operations such as matrix multiplication, matrix-vector
multiplication, matrix factorization, scalar products, and so on are much faster than any
pure Python equivalent. Consider the simple case of scalar products. The scalar product is
much slower than the compiled NumPy function, dot(a,b) (more than 100 times slower
for arrays with about 100 elements):
def my_prod(a,b):
val = 0
for aa,bb in zip(a,b):
val += aa*bb
return val
Measuring the speed of your functions is an important aspect of scientific computing. Refer
to section Measuring execution time in Chapter 13, Testing, for details on measuring
execution times.
[ 101 ]
Advanced Array Concepts
Vectorization
To improve performance, one has to vectorize the code often. Replacing for loops and
other slower parts of the code with NumPy slicing, operations, and functions can give
significant improvements. For example, the simple addition of a scalar to a vector by
iterating over the elements is very slow:
for i in range(len(v)):
w[i] = v[i] + 5
where using NumPy's addition is much faster:
w = v + 5
Using NumPy slicing can also give significant speed improvements over iterating with for
loops. To demonstrate this let us consider forming the average of neighbors in a twodimensional array:
def my_avg(A):
m,n = A.shape
B = A.copy()
for i in range(1,m-1):
for j in range(1,n-1):
B[i,j] = (A[i-1,j] + A[i+1,j] + A[i,j-1] + A[i,j+1])/4
return B
def slicing_avg(A):
A[1:-1,1:-1] = (A[:-2,1:-1] + A[2:,1:-1] +
A[1:-1,:-2] + A[1:-1,2:])/4
return A
These functions both assign each element the average of its four neighbors. The second
version, using slicing, is much faster.
Besides replacing for loops and other slower constructions with NumPy functions, there is
a useful function called vectorize, refer to section Functions acting on arrays in Chapter 4,
Linear Algebra – Arrays. This will take a function and create a vectorized version that applies
the function on all elements of an array using functions wherever possible.
[ 102 ]
Advanced Array Concepts
Consider the following example for vectorizing a function:
def my_func(x):
y = x**3 - 2*x + 5
if y>0.5:
return y-0.5
else:
return 0
Applying this by iterating over an array is very slow:
for i in range(len(v)):
v[i] = my_func(v[i])
Instead, use vectorize to create a new function, like this:
my_vecfunc = vectorize(my_func)
This function can then be applied to the array directly:
v = my_vecfunc(v)
The vectorized option is much faster (around 10 times faster with arrays of length 100).
Broadcasting
Broadcasting in NumPy denotes the ability to guess a common, compatible shape between
two arrays. For instance, when adding a vector (one-dimensional array) and a scalar (zerodimensional array), the scalar is extended to a vector, in order to allow for the addition. The
general mechanism is called broadcasting. We will first review that mechanism from a
mathematical point of view, and then proceed to give the precise rules for broadcasting in
NumPy.
Mathematical view
Broadcasting is often performed in mathematics, mainly implicitly. Examples are
expressions such as f(x) + C or f(x) + g(y). We'll give an explicit description of that technique
in this section.
[ 103 ]
Advanced Array Concepts
We have in mind the very close relationship between functions and NumPy arrays, as
described in section Mathematical preliminaries of Chapter 4, Linear Algebra – Arrays.
Constant functions
One of the most common examples of broadcasting is the addition of a function and a
constant; if C is a scalar, one often writes:
This is an abuse of notation since one should not be able to add functions and constants.
Constants are however implicitly broadcast to functions. The broadcast version of the
constant C is the function defined by:
Now it makes sense to add two functions together:
We are not being pedantic for the sake of it, but because a similar situation may arise for
arrays, as in the following code:
vector = arange(4) # array([0.,1.,2.,3.])
vector + 1.
# array([1.,2.,3.,4.])
In this example, everything happens as if the scalar 1. had been converted to an array of
the same length as vector, that is, array([1.,1.,1.,1.]), and then added to vector.
This example is exceedingly simple, so we proceed to show less obvious situations.
[ 104 ]
Advanced Array Concepts
Functions of several variables
A more intricate example of broadcasting arises when building functions of several
variables. Suppose, for instance, that we were given two functions of one variable, f and g,
and that we want to construct a new function F according to the formula:
This is clearly a valid mathematical definition. We would like to express this definition as
the sum of two functions in two variables defined as
and now we may simply write:
The situation is similar to that arising when adding a column matrix and a row matrix:
C = arange(2).reshape(-1,1) # column
R = arange(2).reshape(1,-1) # row
C + R
# valid addition: array([[0.,1.],[1.,2.]])
This is especially useful when sampling functions of two variables, as shown in section
Typical examples.
General mechanism
We have seen how to add a function and a scalar and how to build a function of two
variables from two functions of one variable. Let us now focus on the general mechanism
that makes this possible. The general mechanism consists of two steps: reshaping and
extending.
[ 105 ]
Advanced Array Concepts
First, the function g is reshaped to a function that takes two arguments. One of these
arguments is a dummy argument, which we take to be zero, as a convention:
Mathematically, the domain of definition of is now
in a way similar to:
Then the function f is reshaped
Now both and take two arguments, although one of them is always zero. We proceed to
the next step, extending. It is the same step that converted a constant into a constant
function (refer to the constant function example).
The function is extended to:
The function is extended to:
Now the function of two variables F, which was sloppily defined by F(x,y) = f(x) + g(y), may
be defined without reference to its arguments:
For example, let us describe the preceding mechanism for constants. A constant is a scalar,
that is, a function of zero arguments. The reshaping step is thus to define the function of one
(empty) variable:
Now the extension step proceeds simply by:
[ 106 ]
Advanced Array Concepts
Conventions
The last ingredient is the convention on how to add the extra arguments to a function, that
is, how the reshaping is automatically performed. By convention, a function is
automatically reshaped by adding zeros on the left.
For example, if a function g of two arguments has to be reshaped to three arguments, the
new function would be defined by:
Broadcasting arrays
We now repeat the observation that arrays are merely functions of several variables (refer to
section Mathematical preliminaries in Chapter 4, Linear Algebra – Arrays). Array broadcasting
thus follows exactly the same procedure as explained above for mathematical functions.
Broadcasting is done automatically in NumPy.
In the following figure (Figure 5.1), we show what happens when adding a matrix of shape
(4, 3) to a matrix of size (1, 3). The second matrix is of the shape (4, 3):
Figure 5.1: Broadcasting between a matrix and a vector.
The broadcasting problem
When NumPy is given two arrays with different shapes, and is asked to perform an
operation that would require the two shapes to be the same, both arrays are broadcast to a
common shape.
[ 107 ]
Advanced Array Concepts
Suppose the two arrays have shapes s1 and s2. This broadcasting is performed in two steps:
1. If the shape s1 is shorter than the shape s2 then ones are added on the left of the
shape s1. This is a reshaping.
2. When the shapes have the same length, the array is extended to match the shape
s2 (if possible).
Suppose we want to add a vector of shape (3, ) to a matrix of shape (4, 3). The vector needs
be broadcast. The first operation is a reshaping; the shape of the vector is converted from (3,
) to (1, 3). The second operation is an extension; the shape is converted from (1, 3) to (4, 3).
For instance, suppose a vector of size n is to be broadcast to the shape (m, n):
1. v is automatically reshaped to (1, n).
2. v is extended to (m, n).
To demonstrate this we consider a matrix defined by:
M = array([[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34]])
and vector given by:
v = array([100, 200, 300, 400])
Now we may add M and v directly:
M + v # works directly
The result is this matrix:
[ 108 ]
Advanced Array Concepts
Shape mismatch
It is not possible to automatically broadcast a vector v of length n to the shape (n,m). This
is illustrated in the following figure:
The broadcasting will fail, because the shape (n,) may not be automatically broadcast to
the shape (m, n). The solution is to manually reshape v to the shape (n,1). The
broadcasting will now work as usual (by extension only):
M + v.reshape(-1,1)
Here is another example, define a matrix by:
M = array([[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34]])
and a vector by:
v = array([100, 200, 300])
Now automatic broadcasting will fail, because automatic reshaping does not work:
M + v # shape mismatch error
The solution is thus to take care of the reshaping manually. What we want in that case is to
add 1 on the right, that is, transform the vector into a column matrix. The broadcasting then
works directly:
M + v.reshape(-1,1)
[ 109 ]
Advanced Array Concepts
For the shape parameter -1, refer to section Accessing and changing the shape of Chapter 4,
Linear Algebra - Arrays. The result is this matrix:
Typical examples
Let us examine some typical examples where broadcasting may come in handy.
Rescale rows
Suppose M is an n × m matrix, and we want to multiply each row by a coefficient. The
coefficients are stored in a vector coeff with n components. In that case, automatic
reshaping will not work, and we have to execute:
rescaled = M*coeff.reshape(-1,1)
Rescale columns
The setup is the same here, but we would like to rescale each column with a coefficient
stored in a vector coeff of length m. In this case, automatic reshaping will work:
rescaled = M*coeff
Obviously, we may also do the reshaping manually and achieve the same result with:
rescaled = M*coeff.reshape(1,-1)
Functions of two variables
Suppose u and v are vectors and we want to form the matrix W with elements wij = ui + vj.
This would correspond to the function F(x, y) = x + y. The matrix W is merely defined by:
W=u.reshape(-1,1) + v
[ 110 ]
Advanced Array Concepts
If the vectors u and v are [0, 1] and [0, 1, 2] respectively, the result is:
More generally, suppose that we want to sample the function w(x, y) := cos(x) + sin(2y).
Supposing that the vectors x and y are defined, the matrix w of sampled values is obtained
by:
w = cos(x).reshape(-1,1) + sin(2*y)
Note that this is very frequently used in combination with ogrid. The vectors obtained
from ogrid are already conveniently shaped for broadcasting. This allows for the following
elegant sampling of the function cos(x) + sin(2y):
x,y = ogrid[0:1:3j,0:1:3j]
# x,y are vectors with the contents of linspace(0,1,3)
w = cos(x) + sin(2*y)
The syntax of ogrid needs some explanation. First, ogrid is no function. It is an instance of
a class with a __getitem__ method (refer to section Attributes in Chapter 8, Classes). That
is why it is used with brackets instead of parentheses.
The two commands are equivalent:
x,y = ogrid[0:1:3j, 0:1:3j]
x,y = ogrid.__getitem__((slice(0, 1, 3j),slice(0, 1, 3j)))
The stride parameter in the preceding example is a complex number. This is to indicate that
it is the number of steps instead of the step size. The rules for the stride parameter might be
confusing at first glance:
If the stride is a real number, then it defines the size of the steps between start
and stop and stop is not included in the list.
If the stride is a complex number s, then the integer part of s.imag defines the
number of steps between start and stop and stop is included in the list.
[ 111 ]
Advanced Array Concepts
Another example for the output of ogrid is a tuple with two arrays, which can be used for
broadcasting:
x,y = ogrid[0:1:3j, 0:1:3j]
gives:
array([[
[
[
array([[
0. ],
0.5],
1. ]])
0. , 0.5,
1. ]])
which is equivalent to:
x,y = ogrid[0:1.5:.5, 0:1.5:.5]
Sparse matrices
Matrices with a small number of nonzero entries are called sparse matrices. Sparse matrices
occur, for example, in scientific computing when describing discrete differential operators
in the context of numerically solving partial differential equations.
Sparse matrices often have large dimensions, sometimes so large that the entire matrix
(with zero entries) would not even fit in the available memory. This is one motivation for a
special type for sparse matrices. Another motivation is better performance of operations
where zero matrix entries can be avoided.
There are only a very limited number of algorithms for general, unstructured sparse
matrices in linear algebra. Most of them are iterative in nature and based on efficient
implementations of matrix-vector multiplication for sparse matrices.
Examples for sparse matrices are diagonal or banded matrices. The simple pattern of these
matrices allows straightforward storing strategies; the principal diagonal and the sub- and
super-diagonals are stored in 1D arrays. Conversion from a sparse representation to the
classical array type and vice-versa can be done by the command diag.
[ 112 ]
Advanced Array Concepts
In general, there is not such a simple structure and the description of sparse matrices
requires special techniques and standards. Here we present a row and a column oriented
type for sparse matrices, both available through the module scipy.sparse .
Figure 5.2: A stiffness matrix from a finite element model of an elastic plate. The pixels denote nonzero entries
in the 1250 × 1250 matrix
Sparse matrix formats
The scipy.sparse module provides many different storing formats from sparse matrices.
We describe here only the most important ones: CSR, CSC, and LIL. The LIL format should
be used for generating and altering sparse matrices; CSR and CSC are efficient formats for
matrix-matrix and matrix-vector operations.
Compressed sparse row
The compressed sparse row format (CSR) uses three arrays: data, indptr, and indices:
The 1D array data stores all the nonzero values in order. It has as many elements
as there are nonzero elements, often denoted by the variable nnz.
[ 113 ]
Advanced Array Concepts
The 1D array indptr contains integers such that indptr[i] is the index of the
element in data, which is the first nonzero element of row i. If the entire row i is
zero, then indptr[i]==indptr[i+1]. If the original matrix has m rows, then
len(indptr)==m+1.
The 1D array indices contains the column index information in such a way that
indices[indptr[i]:indptr[i+1]] is an integer array with the column
indexes of the nonzero elements in row i. Obviously,
len(indices)==len(data)==nnz.
Let's see an example:
The CSR format of the matrix:
is given by the three arrays:
data = (1. 2. 3. 4.)
indptr = (0 2 2 3 5)
indices = (0 2 0 0 3)
The module scipy.sparse provides a type, csr_matrix, with a constructor, which can be
used in the following ways:
With a 2D array as argument
With a matrix in one of the other sparse formats in scipy.sparse
With a shape argument, (m,n), to generate a zero matrix in CSR format
By a 1D array for the data and an integer array ij with the shape
(2,len(data)) such that ij[0,k] is the row index and ij[1,k] is the column
index of data[k] of the matrix
The three arguments, data, indptr, and indices, can be given to the
constructor directly
The first two options are there for conversion purposes while the last two directly define the
sparse matrix.
[ 114 ]
Advanced Array Concepts
Consider the above example in python look like:
import scipy.sparse as sp
A = array([[1,0,2,0],[0,0,0,0],[3.,0.,0.,0.],[1.,0.,0.,4.]])
AS = sp.csr_matrix(A)
Among others, the following attributes are provided:
AS.data
AS.indptr
AS.indices
AS.nnz
#
#
#
#
returns
returns
returns
returns
array([ 1., 2., 3., 1., 4.])
array([0, 2, 2, 3, 5])
array([0, 2, 0, 0, 3])
5
Compressed Sparse Column
The CSR format has a column oriented twin – the compressed sparse column (CSC) format.
The only difference in it compared to the CSR format is the definition of the indptr and
indices arrays, which are now column-related. The type for the CSC format is
csc_matrix and its use corresponds to csr_matrix, explained previously in this section.
Continuing the same example in CSC format:
import scipy.sparse as sp
A = array([[1,0,2,0],[0,0,0,0],[3.,0.,0.,0.],[1.,0.,0.,4.]])
AS = sp.csc_matrix(A)
AS.data
# returns array([ 1., 3., 1., 2., 4.])
AS.indptr
# returns array([0, 3, 3, 4, 5])
AS.indices
# returns array([0, 2, 3, 0, 3])
AS.nnz
# returns 5
Row-based linked list format
The linked list sparse format stores the nonzero matrix entries rowwise in a list data such
that data[k] is a list of the nonzero entries in row k. If all entries in that row are 0, it
contains an empty list.
A second list, rows, contains at position k a list of column indexes of the nonzero elements
in row k. Here is an example in Row-Based linked List Format (LIL) format:
import scipy.sparse as sp
A = array([[1,0,2,0],[0,0,0,0], [3.,0.,0.,0.], [1.,0.,0.,4.]])
AS = sp.lil_matrix(A)
AS.data
# returns array([[1.0, 2.0], [], [3.0], [1.0, 4.0]],
dtype=object)
[ 115 ]
Advanced Array Concepts
AS.rows
AS.nnz
# returns array([[0, 2], [], [0], [0, 3]], dtype=object)
# returns 5
Altering and slicing matrices in LIL format
The LIL format is the one best suited for slicing, that is, extracting submatrices in LIL
format, and for changing the sparsity pattern by inserting nonzero elements. Slicing is
demonstrated by the next example:
BS = AS[1:3,0:2]
BS.data
# returns array([[], [3.0]], dtype=object)
BS.rows
# returns array([[], [0]], dtype=object)
Insertion of a new nonzero element automatically updates the attributes:
AS[0,1] = 17
AS.data # returns array([[1.0, 17.0, 2.0], [], [3.0], [1.0, 4.0]])
AS.rows
# returns array([[0, 1, 2], [], [0], [0, 3]])
AS.nnz
# returns 6
These operations are discouraged in the other sparse matrix formats as they are extremely
inefficient.
Generating sparse matrices
The NumPy commands eye, identity, diag, and rand have their sparse counterparts.
They take an additional argument; it specifies the sparse matrix format of the resulting
matrix.
The following commands generate the identity matrix but in different sparse matrix
formats:
import scipy.sparse as sp
sp.eye(20,20,format = 'lil')
sp.spdiags(ones((20,)),0,20,20, format = 'csr')
sp.identity(20,format ='csc')
The sp.rand command takes an additional argument describing the density of the
generated random matrix. A dense matrix has density 1 while a zero matrix has density 0:
import scipy.sparse as sp
AS=sp.rand(20,200,density=0.1,format=’csr’)
AS.nnz # returns 400
[ 116 ]
Advanced Array Concepts
There is no direct correspondence to the NumPy command zeroes. Matrices completely
filled with zeros are generated by instantiating the corresponding type with the shape
parameters as constructor parameters:
import scipy.sparse as sp
Z=sp.csr_matrix((20,200))
Z.nnz
# returns 0
Sparse matrix methods
There are methods to convert one sparse type into another or into an array:
AS.toarray # converts sparse formats to a numpy array
AS.tocsr
AS.tocsc
AS.tolil
The type of a sparse matrix can be inspected by the methods issparse , isspmatrix_lil,
isspmatrix_csr, and isspmatrix_csc.
Elementwise operations +, *, /, and ** on sparse matrices are defined as for NumPy arrays.
Regardless of the sparse matrix format of the operands, the result is always a csr_matrix.
Applying elementwise operating functions to sparse matrices requires first transforming
them to either CSR or CSC format and applying the functions to their data attribute, as
demonstrated by the next example.
The elementwise sine of a sparse matrix can be defined by an operation on its data
attribute:
import scipy.sparse as sp
def sparse_sin(A):
if not (sp.isspmatrix_csr(A) or sp.isspmatrix_csc(A)):
A = A.tocsr()
A.data = sin(A.data)
return A
[ 117 ]
Advanced Array Concepts
For matrix-matrix or matrix-vector multiplications, there is a sparse matrix method, dot. It
returns either a csr_matrix or a 1D NumPy array:
import scipy.sparse as sp
A = array([[1,0,2,0],[0,0,0,0],[3.,0.,0.,0.],[1.,0.,0.,4.]])
AS = sp.csr_matrix(A)
b = array([1,2,3,4])
c = AS.dot(b)
# returns array([ 7., 0., 3., 17.])
C = AS.dot(AS)
# returns csr_matrix
d = dot(AS,b)
# does not return the expected result!
Avoid using NumPy's command dot on sparse matrices, as this might
lead to unexpected results. Use the command dot from scipy.sparse
instead.
Other linear algebra operations such as system solving, least squares, eigenvalues, and
singular values are provided by the scipy.sparse.linalg module.
Summary
The concept of views is one of the important topics you should have learned from this
chapter. Missing this topic will give you a hard time when debugging your code. Boolean
arrays occur at various places throughout this book. They are handy and compact tools for
avoiding lengthy if constructions and loops when working with arrays. In nearly all large
computational projects, sparse matrices become an issue. You saw how these are handled
and which related methods are available.
[ 118 ]
6
Plotting
Plotting in Python can be done with the pyplot part of the matplotlib module. With
matplotlib you can create high-quality figures and graphics and also plot and visualize your
results. Matplotlib is open source and freely available software, [21]. The matplotlib
website also contains excellent documentation with examples, [35]. In this section, we will
show you how to use the most common features. The examples in the upcoming sections
assume that you have imported the module as:
from matplotlib.pyplot import *
In case you want to use the plotting commands in IPython, it is recommended that you run
the magic command %matplotlib directly after starting the IPython shell. This prepares
IPython for interactive plotting.
Basic plotting
The standard plotting function is plot. Calling plot(x,y) creates a figure window with a
plot of y as a function of x. The input arguments are arrays (or lists) of equal length. It is also
possible to use plot(y), in which case the values in y will be plotted against their index,
that is, plot(y) is a short form of plot(range(len(y)),y).
Here is an example that shows how to plot sin(x) for x ϵ [-2π, 2π] using 200 sample points
and sets markers at every fourth point:
# plot sin(x) for some interval
x = linspace(-2*pi,2*pi,200)
plot(x,sin(x))
# plot marker for every 4th point
samples = x[::4]
Plotting
plot(samples,sin(samples),'r*')
# add title and grid lines
title('Function sin(x) and some points plotted')
grid()
The result is shown in the following figure (Figure 6.1):
Figure 6.1: A plot of the function sin(x) with grid lines shown.
[ 120 ]
Plotting
As you can see, the standard plot is a solid blue curve. Each axis gets automatically scaled
to fit the values but can also be set manually. Color and plot options can be given after the
first two input arguments. Here, r* indicates red star-shaped markers. Formatting is
covered in more detail in the next section. The title command puts a title text string above
the plot area.
Calling plot multiple times will overlay the plots in the same window. To get a new clean
figure window, use figure(). The figure command might contain an integer, for
example, figure(2), which can be used to switch between figure windows. If there is no
figure window with that number, a new one is created, otherwise, the window is activated
for plotting and all subsequent plotting commands apply to that window.
Multiple plots can be explained using the legend function, along with adding labels to each
plot call. The following example fits polynomials to a set of points using the commands
polyfit and polyval, and plots the result with a legend:
# —Polyfit example—
x = range(5)
y = [1,2,1,3,5]
p2 = polyfit(x,y,2)
p4 = polyfit(x,y,4)
# plot the polynomials and points
xx = linspace(-1,5,200)
plot(xx, polyval(p2, xx), label='fitting polynomial of degree 2')
plot(xx, polyval(p4, xx),
label='interpolating polynomial of degree 4')
plot(x,y,'*')
# set the axis and legend
axis([-1,5,0,6])
legend(loc='upper left', fontsize='small')
[ 121 ]
Plotting
Here you can also see how to manually set the range of the axis using
axis([xmin,xmax,ymin,ymax]). The legend command takes optional arguments on
placement and formatting; in this case the legend is put in the upper-left corner and typeset
with a small font size, as shown in the following figure (Figure 6.2).
Figure 6.2: Two polynomials fitted to the same points.
As final examples for basic plotting, we demonstrate how to do scatter plots and
logarithmic plots in two dimensions.
[ 122 ]
Plotting
Example of 2D point scatter plot:
# create random 2D points
import numpy
x1 = 2*numpy.random.standard_normal((2,100))
x2 = 0.8*numpy.random.standard_normal((2,100)) + array([[6],[2]])
plot(x1[0],x1[1],'*')
plot(x2[0],x2[1],'r*')
title('2D scatter plot')
Figure 6.3(a): An example of a scatter plot
[ 123 ]
Plotting
The following code is an example of a logarithmic plot using loglog:
# log both x and y axis
x = linspace(0,10,200)
loglog(x,2*x**2, label = 'quadratic polynomial',
linestyle = '-', linewidth = 3)
loglog(x,4*x**4, label = '4th degree polynomial',
linestyle = '-.', linewidth = 3)
loglog(x,5*exp(x), label = 'exponential function', linewidth = 3)
title('Logarithmic plots')
legend(loc = 'best')
Figure 6.3(b): An example of a plot with logarithmic x and y axis
The examples shown in the preceding figure (Figure 6.3(a) and Figure 6.3(b)) used some
parameters of plot and loglog which allow special formatting. In the next section, we will
explain the parameters in more detail.
[ 124 ]
Plotting
Formatting
The appearance of figures and plots can be styled and customized to look how you want
them to look. Some important variables are linewidth, which controls the thickness of plot
lines; xlabel, ylabel, which set the axis labels, color for plot colors, and transparent
for transparency. This section will tell you how to use some of them. The following is an
example with more keywords:
k = 0.2
x = [sin(2*n*k) for n in range(20)]
plot(x, color='green', linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=12, linewidth=6)
There are short commands that can be used if you only need basic style changes, for
example, setting the color and line style. The following table (Table 6.1) shows some
examples of these formatting commands. You may use either the short string syntax
plot(...,'ro-'), or the more explicit syntax plot(..., marker='o', color='r',
linestyle='-').
Table 6.1: Some common plot formatting arguments
[ 125 ]
Plotting
To set the color to green with the 'o' marker we write:
plot(x,'go')
To plot histograms instead of regular plots, the hist command is used:
# random vector with normal distribution
sigma, mu = 2, 10
x = sigma*numpy.random.standard_normal(10000)+mu
hist(x,50,normed=1)
z = linspace(0,20,200)
plot(z, (1/sqrt(2*pi*sigma**2))*exp(-(z-mu)**2/(2*sigma**2)),'g')
# title with LaTeX formatting
title('Histogram with '.format(mu,sigma))
Figure 6.4 normal distribution with 50 bins and a green curve indicating the true
distribution
The resulting plot looks similar to the preceding figure (Figure 6.4). The title, and any other
text, can be formatted using LaTeX to show mathematical formulas. LaTeX formatting is
enclosed within a pair of $ signs. Also, note the string formatting done using the format
method, refer to section Strings in Chapter 2, Variables and Basic Types.
[ 126 ]
Plotting
Sometimes the brackets for the string formatting interfere with LaTeX bracket
environments. If this occurs, replace the LaTeX bracket with a double bracket, for example,
x_{1} should be replaced with x_{{1}}. The text might contain sequences that overlap
with string escape sequences, for example, \tau will be interpreted as the tab character \t.
An easy workaround is to add r before the string, for example r'\tau'; this makes it a
raw string.
Placing several plots in one figure window can be done using the subplot command.
Consider the following example, which iteratively averages out the noise on a sine curve.
def avg(x):
""" simple running average """
return (roll(x,1) + x + roll(x,-1)) / 3
# sine function with noise
x = linspace(-2*pi, 2*pi,200)
y = sin(x) + 0.4*rand(200)
# make successive subplots
for iteration in range(3):
subplot(3, 1, iteration + 1)
plot(x,y, label = '{:d} average{}'.format(iteration, 's' if iteration >
1 else ''))
yticks([])
legend(loc = 'lower left', frameon = False)
y = avg(y) #apply running average
subplots_adjust(hspace = 0.7)
Figure 6.5: An example of plotting several times in the same figure window.
[ 127 ]
Plotting
The function avg uses a roll call to shift all values of the array. subplot takes three
arguments: the number of vertical plots, the number of horizontal plots, and an index
indicating which location to plot in (counted row-wise). Note that we used the
subplots_adjust command to add extra space to adjust the distance between both the
subplots.
A useful command is savefig which lets you save a figure as an image (this can also be
done from the figure window). Many image and file formats are supported by this
command, they are specified by the filename's extension as:
savefig('test.pdf')
# save to pdf
savefig('test.svg')
# save to svg (editable format)
or
You can place the image against a non-white background, for example, a webpage. For this,
the transparent parameter can be set to make the figure's background transparent:
savefig('test.pdf', transparent=True)
If you intend to embed a figure into a LaTeX document, it is recommended that you reduce
the surrounding white space by setting the figure's bounding box tight around the drawing,
as shown here:
savefig('test.pdf', bbox_inches='tight')
Meshgrid and contours
A common task is a graphical representation of a scalar function over a rectangle:
For this, first we have to generate a grid on the rectangle [a,b] x [c,d]. This is done using the
meshgrid command:
n = ... # number of discretization points along the x-axis
m = ... # number of discretization points along the x-axis
X,Y = meshgrid(linspace(a,b,n), linspace(c,d,m))
[ 128 ]
Plotting
X and Y are arrays with (n,m) shape such that
contains the coordinates of the grid point
as
shown in the next figure (Figure 6.6):
Figure 6.6: A rectangle discretized by meshgrid
A rectangle discretized by meshgrid will be used to visualize the behavior of an iteration.
Bur first we will use it to plot level curves of a function. This is done by the command
contour.
As an example we choose Rosenbrock's banana function:
It is used to challenge optimization methods. The function values descend towards a
banana-shaped valley, which itself decreases slowly towards the function’s global minimum
at (1, 1).
First we display the level curves using contour.
rosenbrockfunction = lambda x,y: (1-x)**2+100*(y-x**2)**2
X,Y = meshgrid(linspace(-.5,2.,100), linspace(-1.5,4.,100))
Z = rosenbrockfunction(X,Y)
contour(X,Y,Z,logspace(-0.5,3.5,20,base=10),cmap='gray')
title('Rosenbrock Function: ')
xlabel('x')
ylabel('y')
[ 129 ]
Plotting
This plots the level curve at the levels given by the fourth parameter and uses the colormap
0.5
3
gray. Furthermore, we used logarithmically spaced steps from 10 to 10 using the function
logscale to define the levels, as shown in the next figure.
Figure 6.7: A contour plot of Rosenbrock function
In the preceding example, an anonymous function indicated by the keyword lambda is
used to keep the code compact. Anonymous functions are explained in section Anonymous
functions – the lambda keyword in Chapter 7, Functions, Anonymous functions. If levels are not
given as arguments to contour, the function chooses appropriate levels by itself .
The contourf function performs the same function as contour but fills the plot with colors
according to different levels. Contour plots are ideal for visualizing the behavior of a
numerical method. We illustrate this here by showing the iterations of an optimization
method.
[ 130 ]
Plotting
We continue the preceding example and depict the steps towards the minimum of the
Rosenbrock function generated by Powell's method, [27], which we will apply to find the
minimum of the Rosenbrock function:
import scipy.optimize as so
rosenbrockfunction = lambda x,y: (1-x)**2+100*(y-x**2)**2
X,Y=meshgrid(linspace(-.5,2.,100),linspace(-1.5,4.,100))
Z=rosenbrockfunction(X,Y)
cs=contour(X,Y,Z,logspace(0,3.5,7,base=10),cmap='gray')
rosen=lambda x: rosenbrockfunction(x[0],x[1])
solution, iterates = so.fmin_powell(rosen,x0=array([0,-0.7]),retall=True)
x,y=zip(*iterates)
plot(x,y,'ko') # plot black bullets
plot(x,y,'k:',linewidth=1) # plot black dotted lines
title("Steps of Powell's method to compute a minimum")
clabel(cs)
The iterative method fmin_powell applies Powell's method to find a minimum. It is
started by a given start value of x0 and reports all iterates when the option retall=True is
given. After sixteen iterations, the solution x=0, y=0 was found. The iterations are depicted
as bullets in the following contour plot (Figure 6.8).
Figure 6.8: A contour plot of Rosenbrock function with a search path of an optimization
method
[ 131 ]
Plotting
contour also creates a contour set object that we assigned to the variable cs. This is then
used by clabel to annotate the levels of the corresponding function values, as shown in the
preceding figure (Figure 6.8).
Images and contours
Let us take a look at some examples of visualizing arrays as images. The following function
will create a matrix of color values for the Mandelbrot fractal. Here we consider a fixed
point iteration, that depends on a complex parameter c:
Depending on the choice of this parameter it may or may not create a bounded sequence of
complex values zn.
For every value of c, we check if zn exceeds a prescribed bound. If it remains below the
bound within maxit iterations, we assume the sequence to be bounded.
Note how, in the following piece of code,meshgrid is used to generate a matrix of complex
parameter values c:
def mandelbrot(h,w, maxit=20):
X,Y = meshgrid(linspace(-2, 0.8, w), linspace(-1.4, 1.4, h))
c = X + Y*1j
z = c
exceeds = zeros(z.shape, dtype=bool)
for iteration in range(maxit):
z = z**2 + c
exceeded = abs(z) > 4
exceeds_now = exceeded & (logical_not(exceeds))
exceeds[exceeds_now] = True
z[exceeded] = 2 # limit the values to avoid overflow
return exceeds
imshow(mandelbrot(400,400),cmap='gray')
axis('off')
[ 132 ]
Plotting
The command imshow displays the matrix as an image. The selected color map shows the
regions where the sequence appeared unbounded in white and others in black. Here we
used axis('off') to turn off the axis as this might be not so useful for images.
Figure 6.9: An example of using imshow to visualize a matrix as an image.
By default, imshow uses interpolation to make the images look nicer. This is clearly seen
when the matrices are small. The next figure shows the difference between using:
imshow(mandelbrot(40,40),cmap='gray')
and
imshow(mandelbrot(40,40), interpolation='nearest', cmap='gray')
[ 133 ]
Plotting
In the second example, pixel values are just replicated.
Figure 6.10: The difference between using the linear interpolation of imshow
compared to using nearest neighbor interpolation
For more details on working and plotting with images using Python refer to [30].
Matplotlib objects
Till now, we have used the pyplot module of matplotlib. This module makes it easy for us
to use the most important plot commands directly. Mostly, we are interested in creating a
figure and display it immediately. Sometimes, though, we want to generate a figure that
should be modified later by changing some of its attributes. This requires us to work with
graphical objects in an object-oriented way. In this section, we will present some basic steps
to modify figures. For a more sophisticated object oriented approach to plotting in Python,
you have to leave pyplot and have to dive directly into matplotlib with its extensive
documentation.
[ 134 ]
Plotting
The axes object
When creating a plot that should be modified later, we need references to a figure and an
axes object. For this we have to create a figure first and then define some axes and their
location in the figure. And we should not forget to assign these objects to a variable:
fig = figure()
ax = subplot(111)
A figure can have several axes objects depending on the use of subplot. In a second step
plots are associated with a given axes object:
fig = figure(1)
ax = subplot(111)
x = linspace(0,2*pi,100)
# We set up a function that modulates the amplitude of the sin function
amod_sin = lambda x: (1.-0.1*sin(25*x))*sin(x)
# and plot both...
ax.plot(x,sin(x),label = 'sin')
ax.plot(x, amod_sin(x), label = 'modsin')
Here we used an anonymous function indicated by the lambda keyword . We will explain
this construct later in section Anonymous functions – the lambda keyword in Chapter 7,
Functions. In fact, these two plot commands fill the list ax.lines with two Lines2D objects:
ax.lines #[, ]
It is a good practice to use labels so that we can later identify objects in an easy way:
for il,line in enumerate(ax.lines):
if line.get_label() == 'sin':
break
We set up now things in a way that allows further modifications. The figure we got so far is
shown in preceding figure (Figure 6.11, left).
[ 135 ]
Plotting
Modifying line properties
We just identified a particular line object by its label. It is an element of the list ax.lines
list with the index il . All its properties are collected in a dictionary
dict_keys(['marker', 'markeredgewidth', 'data', 'clip_box',
'solid_capstyle', 'clip_on', 'rasterized', 'dash_capstyle', 'path',
'ydata', 'markeredgecolor', 'xdata', 'label', 'alpha', 'linestyle',
'antialiased', 'snap', 'transform', 'url',
'transformed_clip_path_and_affine', 'clip_path', 'path_effects',
'animated', 'contains', 'fillstyle', 'sketch_params', 'xydata',
'drawstyle', 'markersize', 'linewidth', 'figure', 'markerfacecolor',
'pickradius', 'agg_filter', 'dash_joinstyle', 'color', 'solid_joinstyle',
'picker', 'markevery', 'axes', 'children', 'gid', 'zorder', 'visible',
'markerfacecoloralt'])
which can be obtained by the command:
ax.lines[il].properties()
They can be changed by corresponding setter methods. Let us change the line style of the
sine – curve:
ax.lines[il].set_linestyle('-.')
ax.lines[il].set_linewidth(2)
We can even modify the data, as shown:
ydata=ax.lines[il].get_ydata()
ydata[-1]=-0.5
ax.lines[il].set_ydata(ydata)
[ 136 ]
Plotting
The result is shown in the next figure(Figure 6.11, right):
Figure 6.11: The amplitude modulated sine-function (left) and a curve with the
last data point corrupted (right).
Annotations
One useful axes method is annotate. It sets an annotation at a given position and points,
with an arrow, to another position in the drawing. The arrow can be given properties in a
dictionary:
annot1=ax.annotate('amplitude modulated\n curve', (2.1,1.0),(3.2,0.5),
arrowprops={'width':2,'color':'k',
'connectionstyle':'arc3,rad=+0.5',
'shrink':0.05},
verticalalignment='bottom', horizontalalignment='left',fontsize=15,
bbox={'facecolor':'gray', 'alpha':0.1, 'pad':10})
annot2=ax.annotate('corrupted data', (6.3,-0.5),(6.1,-1.1),
arrowprops={'width':0.5,'color':'k','shrink':0.1},
horizontalalignment='center', fontsize=12)
In the first annotation example above, the arrow points to a point with the coordinates (2.1,
1.0) and the left bottom coordinate of the text is (3.2, 0.5). If not otherwise specified, the
coordinates are given in the convenient data-coordinate system, which refers to the data
used to generate the plots.
[ 137 ]
Plotting
Furthermore, we demonstrated a couple of arrow properties specified by the arrowprop
dictionary. You can scale the arrow by the shrink key. The setting 'shrink':0.05
reduces the arrow size by 5% to keep a distance to the curve it points to. You can let the
arrow follow a spline arc or give it other shapes using the connectionstyle key.
Text properties or even a bounding box around the text can be made by extra keyword
arguments to the annotate method, refer to the following figure (Figure 6.12, left):
Experimenting with annotations requires sometimes to remove attempts that we would like
to reject. Therefore we assigned the annotate object to a variable, which allows us to remove
the annotation by its remove method:
annot1.remove()
Filling areas between curves
Filling is an ideal tool to highlight differences between curves, such as noise on top of
expected data, approximations versus exact functions, and so on.
Filling is done by the axis method
ax.fill_between(x,y1,y2)
For the next figure we used:
axf = ax.fill_between(x, sin(x), amod_sin(x), facecolor='gray')
where is a very convenient parameter that needs a Boolean array to specify the additional
filling conditions.
axf = ax.fill_between(x, sin(x), amod_sin(x),where=amod_sin(x)-sin(x) > 0,
facecolor=’gray’)
The Boolean array which selects the regions to fill is amod_sin(x)-sin(x) > 0.
[ 138 ]
Plotting
The next figure shows the curve with both variants of filling areas:
Figure 6.12: The amplitude modulated sin-function with annotations and filled
areas(left) and a modified figure with only partially filled areas by using the
where parameter (right).
If you test these commands yourself, do not forget to remove the complete filling before you
try out the partial filling, otherwise you will not see any change:
axf.remove()
Related filling commands are fill and fill_betweenx.
[ 139 ]
Plotting
Ticks and ticklabels
Figures in talks, posters, and publications look much nicer if they are not overloaded with
unnecessary information. You want to direct the spectator to those parts that contain the
message. In our example, we clean up the picture by removing ticks from the x-axis and yaxis and by introducing problem related tick labels:
Figure 6.13: The completed example of the amplitude modulated sine – function with
annotations and filled areas and modified ticks and tick labels.
ax.set_xticks(array([0,pi/2,pi,3/2*pi,2*pi]))
ax.set_xticklabels(('$0$','$\pi/2$','$\pi$','$3/2 \pi$','$2
\pi$'),fontsize=18)
ax.set_yticks(array([-1.,0.,1]))
ax.set_yticklabels(('$-1$','$0$','$1$'),fontsize=18)
[ 140 ]
Plotting
Note that we used LaTeX formatting in the strings to represent Greek letters, to set formulas
correctly, and to use a LaTeX font. It is also a good practice to increase the font size so that
the resulting figure can be scaled down into a text document without affecting the
readability of the axes. The final result of this guiding example is shown in the previous
figure (Figure 6.13).
Making 3D plots
There are some useful matplotlib tool kits and modules that can be used for a variety of
special purposes. In this section, we describe a method for producing 3D-plots.
The mplot3d toolkit provides 3D plotting of points, lines, contours, surfaces, and all other
basic components as well as 3D rotation and scaling. Making a 3D plot is done by adding
the keyword projection='3d' to the axes object as shown in the following example:
from mpl_toolkits.mplot3d import axes3d
fig = figure()
ax = fig.gca(projection='3d')
# plot points in 3D
class1 = 0.6 * random.standard_normal((200,3))
ax.plot(class1[:,0],class1[:,1],class1[:,2],'o')
class2 = 1.2 * random.standard_normal((200,3)) + array([5,4,0])
ax.plot(class2[:,0],class2[:,1],class2[:,2],'o')
class3 = 0.3 * random.standard_normal((200,3)) + array([0,3,2])
ax.plot(class3[:,0],class3[:,1],class3[:,2],'o')
[ 141 ]
Plotting
As you can see, you need to import the axes3D type from mplot3d. The resulting plot
displays the scattered 3D-data which can be seen in the following figure (Figure 6.14)
Figure 6.14: Plotting 3D data using mplot3d toolkit
Plotting surfaces is just as easy. The following example uses the built-in function
get_test_data to create a sample data for plotting a surface. Consider the following
example of a surface plot with transparency.
X,Y,Z = axes3d.get_test_data(0.05)
fig = figure()
ax = fig.gca(projection='3d')
# surface plot with transparency 0.5
ax.plot_surface(X,Y,Z,alpha=0.5)
[ 142 ]
Plotting
The alpha value sets the transparency. The surface plot is shown in the following figure
(Figure 6.15).
Figure 6.15: Example for plotting a surface mesh with three 2D projections.
You can also plot contours in any of the coordinate projections as in the next example.
fig = figure()
ax = fig.gca(projection = '3d')
ax.plot_wireframe(X,Y,Z,rstride = 5,cstride = 5)
# plot contour projection on each
ax.contour(X,Y,Z, zdir='z',offset
ax.contour(X,Y,Z, zdir='x',offset
ax.contour(X,Y,Z, zdir='y',offset
axis plane
= -100)
= -40)
= 40)
# set axis limits
ax.set_xlim3d(-40,40)
ax.set_ylim3d(-40,40)
ax.set_zlim3d(-100,100)
# set labels
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
[ 143 ]
Plotting
Note the commands for setting the axis limits. The standard matplotlib commands for
setting the axis limits are axis([-40, 40, -40, 40]), this works fine for 2D plots.
However, axis([-40,40,-40,40,-40,40]) does not work. For 3D plots you need to use
the object oriented version of the commands, ax.set_xlim3d(-40,40) and likewise. The
same goes for labeling the axis; note the commands for setting the labels. For 2D plots you
can do xlabel(’X axis’) and ylabel(’Y axis’) but there is no zlabel command.
Instead, in 3D plots you need to use ax.set_xlabel(’X axis’) and likewise, as shown in
the preceding example.
The resulting figure from this code is the following
There are many options for formatting the appearance of the plots, including color and
transparency of surfaces. The mplot3d documentation website, [23], has the details.
[ 144 ]
Plotting
Making movies from plots
If you have data that evolves, you might want to save it as a movie besides showing it in a
figure window, similar to the savefig command. One way to do this is with the visvis
module available at visvis (refer to [37] for more information).
Here is a simple example of evolving a circle using an implicit representation. Let the circle
be represented by the zero level,
, of a function
. Alternatively, consider
the disk
inside the zero set. If the value of f decreases at a rate v then the circle
will move outward with rate
.
This can be implemented as:
import visvis.vvmovie as vv
# create initial function values
x = linspace(-255,255,511)
X,Y = meshgrid(x,x)
f = sqrt(X*X+Y*Y) - 40 #radius 40
# evolve and store in a list
imlist = []
for iteration in range(200):
imlist.append((f>0)*255)
f -= 1 # move outwards one pixel
vv.images2swf.writeSwf('circle_evolution.swf',imlist)
The result is a Flash movie (*.swf file) of a growing black circle, as shown in the next figure
(Figure 6.16):
Figure 6.16: An example of evolving a circle
[ 145 ]
Plotting
In this example, a list of arrays was used to create the movie. The visvis module can also
save an GIF animation and on certain platforms an AVI animation (*.gif and *.avi files), and
there is also the possibility to capturing movie frames directly from the figure window.
These options, however, require some more packages to be installed on your system (for
example, PyOpenGL and PIL, the Python Imaging Library). See the documentation on the
visvis webpage for more details.
Another option is to use savefig to create images, one for each frame.
# create initial function values
x = linspace(-255,255,511)
X,Y = meshgrid(x,x)
f = sqrt(X*X+Y*Y) - 40 #radius 40
for iteration in range(200):
imshow((f>0)*255)
gray()
axis('off')
savefig('circle_evolution_{:d}.png'.format(iteration))
f -= 1
These images can then be combined using a standard video editing software, for example,
Mencoder or ImageMagick. This approach has the advantage that you can make highresolution videos by saving high-resolution images.
Summary
A graphical representation is the most compact form in which to present mathematical
results or the behavior of an algorithm. This chapter provided you with the basic tools for
plotting and introduced you to a more sophisticated way to work with graphical objects,
such as figures, axes, and lines in an object-oriented way.
In this chapter, you learned how to make plots, not only classical x/y-plots but also 3D-plots
and histograms. We gave you an appetizer on making films. You also saw how to modify
plots considering them to be graphical objects with related methods and attributes which
can be set, deleted, or modified.
[ 146 ]
Plotting
Exercises
Ex. 1 → Write a function that plots an ellipse given its center coordinates (x,y), the half axis a
and b rotation angle θ.
Ex. 2 → Write a short program that takes a 2D array, e.g., the preceding Mandelbrot contour
image, and iteratively replace each value by the average of its neighbors. Update a contour
plot of the array in a figure window to animate the evolution of the contours. Explain the
behavior.
Ex. 3 → Consider an N × N matrix or image with integer values. The mapping
is an example of a mapping of a toroidal square grid of points onto itself. This has the
interesting property that it distorts the image by shearing and then moving the pieces
outside the image back using the modulu function mod. Applied iteratively, this results in
randomizing the image in a way that eventually returns the original. Implement the
following sequence:
and save out the first N steps to files or plot them in a figure window.
As an example image, you can use the classic 512 × 512 Lena test image from scipy.misc.
from scipy.misc import lena
I = lena()
The result should look like this:
…
0
1
…
128
…
256
[ 147 ]
511
512
Plotting
Compute the x and y mappings and use array indexing (refer to section
Array Indexing in Chapter 5, Advance Array Concepts) to copy the pixel
values.
Ex. 4 → Reading and plotting on images. SciPy comes with the imread function (in the
scipy.misc module) for reading images, (refer to section Reading and Writing Images in
Chapter 12, Input and output). Write a short program that reads an image from file and plots
the image contour at a given gray level value overlaid on the original image.
You can get a gray level version of the image by averaging the color
channels like this: mean(im,axis=2)
Ex. 5 → Image edges. The zero crossings of the 2D Laplacian are a good indication of image
edges. Modify the program in the previous exercise to use the gaussian_laplace or
laplace function from the scipy.ndimage module to compute the 2D Laplacian and
overlay the edges on top of the image.
Ex. 6 → Reformulate the Mandelbrod fractal example (see section Images and Contours) by
using orgid instead of meshgrid, see also the explanation ogrid in Function of two variables
in Chapter 5, Advanced Array Concepts. What is the difference between orgid, mgrid, and
meshgrid?
[ 148 ]
7
Functions
This chapter introduces functions, a fundamental building block in programming. We show
how to define them, how to handle input and output, how to properly use them, and how
to treat them as objects.
Basics
In mathematics, a function is written as a map that uniquely assigns an element y from the
range R to every element x from the domain D.
This is expressed by f : D → R
Alternatively, when considering particular elements x and y, one writes f : x → y
Here, f is called the name of the function and f(x) is its value when applied to x. Here, x is
sometimes called the argument of f. Let's first look at an example before considering
functions in Python.
For example, D = ℝ x ℝ and y = f(x1, x2) = x1– x2. This function maps two real numbers to
their difference.
In mathematics, functions can have numbers, vectors, matrices, and even other functions as
arguments. Here is an example of a function with mixed arguments:
.
Functions
In this case, a number is returned. When working with functions, we have to distinguish
between two different steps:
The definition of the function
The evaluation of the function, that is, the computation of f(x) for a given value of
x
The first step is done once, while the second can be performed many times for various
arguments. Functions in programming languages follow the same concept and apply it to a
wide range of types of input arguments, for example, strings, lists, or any object. We
demonstrate a definition of the function by considering the given example again:
def subtract(x1, x2):
return x1 - x2
The keyword def indicates that we are going to define a function. subtract is the
function’s name and x1 and x2 are its parameters. The colon indicates that we are using a
block command and the value that is returned by the function follows the return keyword.
Now, we can evaluate this function. This function is called with its parameters replaced by
input arguments:
r = subtract(5.0, 4.3)
The result 0.7 is computed and assigned to the r variable.
Parameters and arguments
When defining a function, its input variables are called the parameters of the function. The
input used when executing the function is called its argument.
Passing arguments – by position and by keyword
We will consider the previous example again, where the function takes two parameters,
namely x1 and x2.
[ 150 ]
Functions
Their names serve to distinguish the two numbers, which in this case cannot be
interchanged without altering the result. The first parameter defines the number from
which the second parameter is subtracted. When subtract is called, every parameter is
replaced by an argument. Only the order of the arguments matters; the arguments can be
any objects. For instance, we may call the following:
z = 3
e = subtract(5,z)
Besides this standard way of calling a function, which is by passing the arguments by
position, it might sometimes be convenient to pass arguments using keywords. The names
of the parameters are the keywords; consider the following instance:
z = 3
e = subtract(x2 = z, x1 = 5)
Here, the arguments are assigned to the parameters by name and not by position in the call.
Both ways of calling a function can be combined so that the arguments given by position
come first and the arguments given by keyword follow last. We show this by using
the function plot, which was described in Chapter 6, Plotting:
plot(xp, yp, linewidth = 2,label = 'y-values')
Changing arguments
The purpose of parameters is to provide the function with the necessary input data.
Changing the value of the parameter inside the function normally has no effect on its value
outside the function:
def subtract(x1, x2):
z = x1 - x2
x2 = 50.
return z
a = 20.
b = subtract(10, a)
# returns -10
a
# still has the value 20
This applies to all immutable arguments, such as strings, numbers, and tuples. The situation
is different if mutable arguments, such as lists or dictionaries, are changed.
[ 151 ]
Functions
For example, passing mutable input arguments to a function and changing them inside the
function can change them outside the function too:
def subtract(x):
z = x[0] - x[1]
x[1] = 50.
return z
a = [10,20]
b = subtract(a)
# returns -10
a
# is now [10, 50.0]
Such a function misuses its arguments to return results. We strongly dissuade you from
such constructions and recommend that you do not change input arguments inside the
function (for more information refer to Default Arguments section).
Access to variables defined outside the local
namespace
Python allows functions to access variables defined in any of its enclosing program units.
These are called global variables, in contrast to local variables. The latter are only accessible
within the function. For example, consider the following code:
import numpy as np # here the variable np is defined
def sqrt(x):
return np.sqrt(x) # we use np inside the function
This feature should not be abused. The following code is an example of what not to do:
a = 3
def multiply(x):
return a * x # bad style: access to the variable a defined outside
When changing the variable a the function, multiply tacitly changes its behavior:
a=3
multiply(4)
a=4
multiply(4)
# returns 12
# returns 16
[ 152 ]
Functions
It is much better in that case to provide the variable as a parameter through the argument
list:
def multiply(x, a):
return a * x
Global variables can be useful when working with closures. Namespaces and scopes are
discussed more extensively in Chapter 11, Namespaces, Scopes, and Modules.
Default arguments
Some functions can have many parameters, and among them some might only be of interest
in nonstandard situations. It would be practical if arguments could automatically be set to
standard (default) values. We demonstrate the use of default arguments by looking at the
command norm in the scipy.linalg module. It computes various norms of matrices and
vectors.
The following snippet calls for computing the Frobenius norm of the 3 × 3 identity matrix
are equivalent (more on matrix norms can be found in [10]):
import scipy.linalg as sl
sl.norm(identity(3))
sl.norm(identity(3), ord = 'fro')
sl.norm(identity(3), 'fro')
Note that in the first call, no information about the ord keyword is given. How does Python
know that it should compute the Frobenius norm and not another norm, for example, the
Euclidean 2-norm?
The answer to the previous question is the use of default values. A default value is a value
already given by the function definition. If the function is called without providing this
argument, Python uses the value that the programmer provided when the function was
defined.
Suppose we call the function subtract only one argument; we get an error message:
TypeError: subtract() takes exactly 2 arguments (1 given)
To allow the omission of the argument x2, the definition of the function has to provide a
default value, for example:
def subtract(x1, x2 = 0):
return x1 - x2
[ 153 ]
Functions
To summarize, arguments can be given as positional arguments and keyword arguments.
All positional arguments have to be given first. You do not need to provide all keyword
arguments as long as those omitted arguments have default values in the function
definition.
Beware of mutable default arguments
The default arguments are set upon function definition. Changing mutable arguments
inside a function has a side effect when working with default values, for example:
def my_list(x1, x2 = []):
x2.append(x1)
return x2
my_list(1) # returns [1]
my_list(2) # returns [1,2]
Variable number of arguments
Lists and dictionaries may be used to define or call functions with a variable number of
arguments. Let's define a list and a dictionary as follows:
data = [[1,2],[3,4]]
style = dict({'linewidth':3,'marker':'o','color':'green'})
Then we can call the plot function using starred (*) arguments:
plot(*data,**style)
A variable name prefixed by * , such as *data in the preceding example, means that a list
that gets unpacked in the function call is provided. In this way, a list generates positional
arguments. Similarly, a variable name prefixed by **, such as **style in the example,
unpacks a dictionary to keyword arguments. Refer to the following figure (Figure 7.1):
[ 154 ]
Functions
Figure 7.1: Starred arguments in function calls
You might also want to use the reverse process, where all given positional arguments are
packed into a list and all keyword arguments are packed into a dictionary when passed to a
function.
In the function definition, this is indicated by parameters prefixed by * and **,
respectively. You will often find the *args and **kwargs parameters in code
documentation, refer Figure 7.2.
Figure 7.2: Starred arguments in function definitions
[ 155 ]
Functions
Return values
A function in Python always returns a single object. If a function has to return more than
one object, these are packed and returned as a single tuple object.
For instance, the following function takes a complex number z and returns its polar
coordinate representation as magnitude r and angle according to Euler’s formula:
And the Python counterpart would be this:
def complex_to_polar(z):
r = sqrt(z.real ** 2 + z.imag ** 2)
phi = arctan2(z.imag, z.real)
return (r,phi) # here the return object is formed
Here, we used the sqrt(x) NumPy function for the square root of a number x and
-1
arctan2(x,y) for the expression tan (x/y).
Let us try our function:
z =
a =
r =
phi
3 + 5j # here we define a complex number
complex_to_polar(z)
a[0]
= a[1]
The last three statements can be written more elegantly in a single line:
r,phi = complex_to_polar(z)
We can test our function by calling polar_to_comp; refer to Exercise 1.
If a function has no return statement, it returns the value None. There are many cases
where a function does not need to return any value. This could be because the variables
passed to a function may be subject to modification. Consider, for instance, the following
function:
def append_to_list(L, x):
L.append(x)
[ 156 ]
Functions
The preceding function does not return anything because it modifies one of the objects that
is given as an argument. We mentioned in Parameters and Arguments section why this is
useful. There are many methods that behave in the same way. To mention the list methods
only, the append, extend, reverse, and sort methods do not return anything (that is,
they return None ). When an object is modified by a method in this way, the modification is
called in-place. It is difficult to know whether a method changes an object, except by
looking at the code or the documentation.
Another reason for a function, or a method, not to return anything is when it prints out a
message or writes to a file.
The execution stops at the first occurring return statement. Lines after that statement are
dead code, which will never be executed:
def function_with_dead_code(x):
return 2 * x
y = x ** 2 # these two lines ...
return y
# ... are never executed!
Recursive functions
In mathematics, many functions are defined recursively. In this section, we will show how
this concept can be used even when programming a function. This makes the relation of the
program to its mathematical counterpart very clear, which may ease the readability of the
program.
Nevertheless, we recommend that you use this programming technique with care,
especially within scientific computing. In most applications, the more straightforward
iterative approach is more efficient. This will become immediately clear from the following
example.
Chebyshev polynomials are defined by a three-term recursion:
[ 157 ]
Functions
Such a recursion needs to be initialized, that is, T0(x) =1, T1(x) = x.
In Python, this three term recursion can be realized by the following function definition:
def chebyshev(n, x):
if n == 0:
return 1.
elif n == 1:
return x
else:
return 2. * x * chebyshev(n - 1, x)
- chebyshev(n - 2 ,x)
The function is then called like this:
chebyshev(5, 0.52) # returns 0.39616645119999994
This example also illustrates the risk of dramatically wasting computation time. The
number of function evaluations increases exponentially with the recursion level and most of
these evaluations are just duplicates of previous computations. While it might be tempting
to use recursive programs for demonstrating the strong relation between code and
mathematical definition, a production code will avoid this programming technique (refer to
Exercise 6). We also refer to a technique called memoization (refer to [22] for more details)
that combines recursive programming with a caching technique to save replicated function
evaluations.
A recursive function usually has a level parameter. In the previous example, it is n. It is
used to control the function's two main parts:
The base case, here, the first two if branches
The recursive body, in which the function itself is called once or several times
with smaller level parameters.
The number of levels passed by an execution of a recursive function is called the recursion
depth. This quantity should not be too large; otherwise the computation might no longer be
effective and in the ultimate case, the following error will be raised:
RuntimeError: maximum recursion depth exceeded
The maximal recursion depth depends on the memory of the computer you use. This error
also occurs when the initialization step is missing in the function definition. We encourage
the use of recursive programs for very small recursion depths (for more information, refer
to the section Infinite Iteration of Chapter 9, Iterating.
[ 158 ]
Functions
Function documentation
You should document your functions using a string at the beginning. This is
called docstring:
def newton(f, x0):
"""
Newton's method for computing a zero of a function
on input:
f (function) given function f(x)
x0 (float) initial guess
on return:
y (float) the approximated zero of f
"""
...
When calling help(newton), you get this docstring displayed together with the call of this
function:
Help on function newton in module __main__:
newton(f, x0)
Newton's method for computing a zero of a function
on input:
f (function) given function f(x)
x0 (float) initial guess
on return:
y (float) the approximated zero of f
The docstring is internally saved as an attribute, __doc__, of the given function. In the
example, it's newton.__doc__. The minimal information you should provide in a docstring
is the purpose of the function and the description of the input and output objects. There are
tools to automatically generate full code documentation by collecting all docstrings in your
program (for more information refer to [32]).
[ 159 ]
Functions
Functions are objects
Functions are objects, like everything else in Python. One may pass functions as arguments,
change their names, or delete them. For example:
def square(x):
"""
Return the square of x
"""
return x ** 2
square(4) # 16
sq = square # now sq is the same as square
sq(4) # 16
del square # square doesn't exist anymore
print(newton(sq, .2)) # passing as argument
Passing functions as arguments is very common when applying algorithms in scientific
computing. The functions fsolve in scipy.optimize for computing a zero of a given
function or quad in scipy.integrate for computing integrals are typical examples.
A function itself can have a different number of arguments with differing types. So, when
passing your function f to another function g as argument, make sure that f has exactly the
form described in the docstring of g.
The docstring of fsolve gives information about its func parameter:
func -- A Python function or method which takes at least one
(possibly vector) argument.
Partial application
Let's start with an example of a function with two variables.
The function
can be viewed as a function in two variables. Often one
considers ω not as a free variable but as a fixed parameter of a family of functions fω:
This interpretation reduces a function in two variables to a function in one variable, t, given
a fixed parameter value ω. The process of defining a new function by fixing (freezing) one
or several parameters of a function is called partial application.
[ 160 ]
Functions
Partial applications are easily created using the Python module functools, which provides
a function called partial for precisely this purpose. We illustrate this by constructing a
function that returns a sine for a given frequency:
import functools
def sin_omega(t, freq):
return sin(2 * pi * freq * t)
def make_sine(frequency):
return functools.partial(sin_omega, freq = frequency)
Using Closures
Using the view that functions are objects, partial applications can be realized by writing a
function, which itself returns a new function, with a reduced number of input arguments.
For instance, the function could be defined as follows:
def make_sine(freq):
"Make a sine function with frequency freq"
def mysine(t):
return sin_omega(t, freq)
return mysine
In this example the inner function mysine has access to the variable freq; it is neither a
local variable of this function nor is it passed to it via the argument list. Python allows such
a construction (refer to Namespaces section in Chapter 11, Namespaces, Scopes, and Modules).
Anonymous functions – the lambda
keyword
The keyword lambda is used in Python to define anonymous functions, that is; functions
without a name and described by a single expression. You might just want to perform an
operation on a function that can be expressed by a simple expression without naming this
function and without defining this function by a lengthy def block.
The name lambda originates from a special branch of calculus and
mathematical logic, the -calculus.
[ 161 ]
Functions
For instance, to compute the following expression, we may use SciPy’s function quad,
which requires the function to be integrated as its first argument and the integration bounds
as the next two arguments:
Here, the function to integrate is just a simple one-liner and we use the lambda keyword to
define it:
import scipy.integrate as si
si.quad(lambda x: x ** 2 + 5, 0, 1)
The syntax is as follows:
lambda parameter_list: expression
The definition of the lambda function can only consist of a single expression and in
particular, cannot contain loops. lambda functions are, just like other functions, objects and
can be assigned to variables:
parabola = lambda x: x ** 2 + 5
parabola(3) # gives 14
The lambda construction is always replaceable
It is important to note that lambda construction is only syntactic sugar in Python. Any
lambda construction may be replaced by an explicit function definition:
parabola = lambda x: x**2+5
# the following code is equivalent
def parabola(x):
return x ** 2 + 5
The main reason to use a construction is for very simple functions, when a full function
definition would be too cumbersome.
[ 162 ]
Functions
lambda functions provide a third way to make closures as we demonstrate by continuing
with the previous example:
We use the sin_omega function to compute the integral of the sine function for various
frequencies:
import scipy.integrate as si
for iteration in range(3):
print(si.quad(lambda x: sin_omega(x, iteration*pi), 0, pi/2.) )
Functions as decorators
In the partial application section, we saw how a function can be used to modify another
function. A decorator is a syntax element in Python that conveniently allows us to alter the
behavior of a function without changing the definition of the function itself. Let us start
with the following situation:
Assume that we have a function that determines the degree of sparsity of a matrix:
def how_sparse(A):
return len(A.reshape(-1).nonzero()[0])
This function returns an error if it is not called with an array object as input. More precisely,
it will not work with an object that does not implement the reshape method. For instance,
the how_sparse function will not work with a list, because lists have no reshape method.
The following helper function modifies any function with one input parameter so that it
tries to make a type conversion to an array:
def cast2array(f):
def new_function(obj):
fA = f(array(obj))
return fA
return new_function
[ 163 ]
Functions
Thus, the modified function how_sparse = cast2array(how_sparse) can be applied to
any object that can be cast to an array. The same functionality is achieved if the definition of
how_sparse is decorated with the type conversion function. It is recommend also to
consider the functools.wraps (refer to [8] for more details):
@cast2array
def how_sparse(A):
return len(A.reshape(-1).nonzero()[0])
To define a decorator, you need a callable object such as a function that modifies the
definition of the function to be decorated. The main purposes are:
To increase code readability by separating parts from a function that do not
directly serve its functionality (for example, memoizing)
To put common preamble and epilogue parts of a family of similar functions in a
common place (for example, type checking)
To be able to easily switch off and on additional functionalities of a function (for
example, test prints, tracing)
Summary
Functions are not only the ideal tools for making your program modular, but they also
reflect mathematical thinking. You learned the syntax of function definitions and to
distinguish between defining and calling a function.
We considered functions as objects that can be modified by other functions. When working
with functions, it is important to be familiar with the notion of the scope of a variable and
how information is passed into a function by parameters.
Sometimes, it is convenient to define functions on the fly with so-called anonymous
functions. For this, we introduced the keyword lambda.
[ 164 ]
Functions
Exercises
Ex 1 → Write a function polar_to_comp, which takes two arguments r and and returns
the complex number
Use the NumPy function exp for the exponential function.
Ex 2 → In the description of the Python module functools, (refer to [8] for more detail on
functools) you find the following Python function:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
Explain and test this function.
Ex 3 → Write a decorator for the function how_sparse, which cleans the input matrix A by
setting the elements that are less than 1.e-16 to zero (consider example in section Function as
decorators).
Ex 4 → A continuous function f with f(a)f(b) < 0 changes its sign in the interval [a, b] and has
at least one root (zero) in this interval. Such a root can be found with the bisection method.
This method starts from the given interval. Then it investigates the sign changes in the
subintervals,
If the sign changes in the first subinterval b is redefined to be:
Otherwise, it is redefined in the same manner to:
[ 165 ]
Functions
And the process is repeated until the b-a is less than a given tolerance.
Implement this method as a function that takes as arguments:
– the function f
– the initial interval [a, b]
– the tolerance
This function bisec should return the final interval and its midpoint.
Test the method with the function arctan and also with the polynomial f(x) = 3 x2
-5 in the interval [1.1, 1.4], and alternatively in [1.3, 1.4].
Ex. 5 → The greatest common divisor of two integers can be computed with Euclid’s
algorithm described by the following recursion:
Write a function that computes the greatest common divisor of two integers. Write another
function that computes the least common multiple of these numbers using the relation:
Ex. 6 → Study the recursive implementation of Chebyshev polynomials, consider the
example in section Recursive Function. Rewrite the program in a non-recursive way and
study computation time versus polynomial degree (see also the timeit module).
[ 166 ]
8
Classes
In mathematics, when we write sin, we refer to a mathematical object for which we know
many methods from elementary calculus. For example:
We might want to evaluate sin x at x=0.5, that is, compute sin(0.5), which returns
a real number
We might want to compute its derivative, which gives us another mathematical
object, cos
We might want to compute the first three coefficients of its Taylor polynomial
These methods may be applied not only to sin but also to other sufficiently smooth
functions. There are, however, other mathematical objects (for example, the number 5) for
which these methods make no sense. Objects that have the same methods are grouped
together in abstract classes, for example, functions. Every statement and every method
that can be applied to functions applies in particular to sin or cos. Other examples for such
classes might be a rational number, for which a denominator and numerator method exist;
an interval, which has a left and right boundary method; an infinite sequence, for which we
can ask whether it has a limit, and so on.
In this case, sin is called an instance of the class. The mathematical phrase Let g be a function
… is, in this context, called instantiation. Here, g is the name of the function; one of many
attributes that can be assigned to it. Another attribute might be its domain.
The mathematical object p(x) = 2x2– 5 is just like the sine function. Every function method
applies to p, but we can also define special methods for p. We might, for instance, ask for p’s
coefficients. These methods can be used to define the class of polynomials. As polynomials
are functions, they additionally inherit all methods of the function class.
Classes
In mathematics, we often use the same operator symbol for completely different operations.
For instance, in 5+4 and sin + cos, the operator symbol + has different meanings. By using
the same symbol, one tries to express the similarities of mathematical operations. We have
introduced these terms from object-oriented programming by applying them to
mathematical examples:
Classes
Instance and instantiation
Inheritance
Methods
Attributes
Operator overloading
In this chapter, we will show how these concepts are used in Python.
Introduction to classes
We will illustrate the concept of classes with an example of rational numbers, that is,
numbers of the form q= qN ⁄ qD, where qN and qD are integers.
Figure 8.1: An example of a class declaration
[ 168 ]
Classes
We use rational numbers here only as an example for the class concept. For future work in
Python with rational numbers use the fractions module (refer to [6]).
Class syntax
The definition of a class is made by a block command with the class keyword, the name of
the class, and some statements in the block (refer to Figure 8.1):
class RationalNumber:
pass
An instance of this class (or in other words, an object of the type RationalNumber) is
created by
r = RationalNumber()
and a query type(a) returns the answer, . If we
want to investigate whether an object is an instance of this class, we can use this:
if isinstance(a, RationalNumber):
print('Indeed it belongs to the class RationalNumber')
So far we've generated an object of the RationalNumber type, which has no data yet.
Furthermore, there are no methods defined to perform operations with these objects. This
will be the subject of the next sections.
The __init__ method
Now we provide our example class with some attributes; that is, we give it defining data. In
our case, this data will be the values of the denominator and the numerator. To this end, we
have to define a method, __init__, used to initialize the class with these values:
class RationalNumber:
def __init__(self, numerator, denominator):
self.numerator = numerator
self.denominator = denominator
[ 169 ]
Classes
Before we explain the special __init__ function, which we added to the class, we
demonstrate the instantiation of a RationalNumber object:
q = RationalNumber(10, 20)
# Defines a new object
q.numerator
# returns 10
q.denominator
# returns 20
A new object of type RationalNumber is created by using the class name as if it was a
function. This statement does two things:
It first creates an empty object, q.
Then it applies the __init__ function to it; that is, q.__init__(10, 20) is
executed.
The first parameter of __init__ refers to the new object itself. On function call, this first
parameter is replaced by the object’s instance. This applies to all methods of the class and
not only to the special method __init__. The special role of this first parameter is reflected
by the convention to name it self. In the previous example, the __init__ function defines
two attributes of the new object, numerator and denominator.
Attributes and methods
One of the main reasons for working with classes is that objects can be grouped together
and bound to a common object. We saw this already when looking at rational numbers;
denominator and numerator are two objects which we bound to an instance of the
RationalNumber class. They are called attributes of the instance. The fact that an object is
an attribute of a class instance becomes apparent from the way they are referenced, which
we have used tacitly before:
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.6 Linearized : Yes Create Date : 2016:12:14 14:17:06Z Modify Date : 2016:12:21 17:30:44+05:30 Has XFA : No XMP Toolkit : Adobe XMP Core 5.4-c005 78.147326, 2012/08/23-13:03:03 Metadata Date : 2016:12:21 17:30:44+05:30 Producer : mPDF 6.0 Format : application/pdf Document ID : uuid:561dbbf3-24c4-416e-a9f0-82445b24d070 Instance ID : uuid:f9d8a749-4bcd-457c-b1e2-a8f3486553bc Page Layout : OneColumn Page Mode : UseOutlines Page Count : 322EXIF Metadata provided by EXIF.tools