Veit Steinkamp - Python For Engineering and Scientific Computing-Rheinwerk Publishing (2024)
Veit Steinkamp - Python For Engineering and Scientific Computing-Rheinwerk Publishing (2024)
The Rheinwerk Computing series offers new and established professionals comprehen-
sive guidance to enrich their skillsets and enhance their career prospects. Our publica-
tions are written by the leading experts in their fields. Each book is detailed and hands-on
to help readers develop essential, practical skills that they can apply to their daily work.
Philip Ackermann
JavaScript: The Comprehensive Guide
2022, 1292 pages, paperback and e-book
www.rheinwerk-computing.com/5554
Sebastian Springer
Node.js: The Comprehensive Guide
2022, 834 pages, paperback and e-book
www.rheinwerk-computing.com/5556
Sebastian Springer
React: The Comprehensive Guide
2024, 676 pages, paperback and e-book
www.rheinwerk-computing.com/5705
www.rheinwerk-computing.com
Veit Steinkamp
Python for
Engineering and
Scientific Computing
Imprint
We hope that you liked this e-book. Please share your feedback with us and read the
Service Pages to find out how to contact us.
This e-book is protected by copyright. By purchasing this e-book, you have agreed to
accept and adhere to the copyrights. You are entitled to use this e-book for personal pur-
poses. You may print and copy it, too, but also only for personal use. Sharing an electronic
or printed copy with others, however, is not permitted, neither as a whole nor in parts. Of
course, making them available on the Internet or in a company network is illegal as well.
For detailed and legally binding usage conditions, please refer to the section
Legal Notes.
This e-book copy contains a digital watermark, a signature that indicates which person
may use this copy:
Contents
1 Introduction 17
2 Program Structures 29
7
Contents
8
Contents
9
Contents
10
Contents
5.14 Project Task: Bending a Beam That Is Fixed at One End ...................................... 265
5.14.1 Second Moment of Area ...................................................................................... 265
5.14.2 Equation of the Bending Line ............................................................................. 267
5.15 Project Task: Reaction Kinetics ........................................................................................ 270
11
Contents
12
Contents
7.7 Project Task: Animation of Two Coupled Simple Pendulums ........................... 395
13
Contents
11.1 Interactions with Command Buttons, Textboxes, and Labels .......................... 466
11.1.1 Labels .......................................................................................................................... 467
11.1.2 Textboxes and Command Buttons ................................................................... 468
11.2 The Layout Manager of Tkinter ....................................................................................... 468
11.2.1 The pack Method .................................................................................................... 470
14
Contents
Appendices 497
15
Chapter 1
Introduction
This chapter provides a brief overview of the extensibility, application
areas, and functionality of the Python programming language.
If you need to perform extensive calculations for your scientific work and also want to
present the results in a graphically appealing way, then you should seriously consider
using Python. Python is a programming language whose functionality is similar to that
of MATLAB when extended with appropriate modules. In addition, Python and all its
extension modules are provided free of charge. Using Python, you can, for example,
solve systems of equations, create function plots, differentiate, integrate, and also
solve differential equations. You can also create graphical user interfaces (GUIs). For
almost every problem in engineering and natural sciences, solutions exist that not
only cover a wide range of applications, but also excel in their user-friendliness and
performance.
The Python programming language was developed in the early 1990s by Dutchman
Guido van Rossum at Centrum voor Wiskunde & Informatica (CWI) in Amsterdam. Its
name has nothing to do with the snake but refers instead to the British comedy group
Monty Python.
The particular advantages and features of this programming language include the fol-
lowing:
쐍 Python is an easy-to-learn and powerful programming language.
쐍 It provides efficient data structures.
쐍 It also allows object-oriented programming (OOP).
쐍 It has a clear syntax and dynamic typing.
쐍 Python programs are compiled using an interpreter and are therefore suitable for
the rapid development of prototypes.
쐍 Python is available for Linux, macOS, and Windows.
쐍 Python can be extended by modules.
The module concept is the cornerstone and one of Python’s outstanding strengths.
A module is a component of a software system and represents a functionally self-
contained unit that provides a specific service. For a definable scientific problem, a
module that is tailored precisely to this problem is provided in each case. In this book,
I will introduce you to the NumPy, Matplotlib, SymPy, SciPy, and VPython modules.
17
1 Introduction
1.1.1 IDLE
The abbreviation IDLE stands for “Integrated Development and Learning Environ-
ment.” Figure 1.1 shows the user interface for IDLE.
IDLE is part of the standard Python download. During the installation of Python, IDLE
is installed at the same time as the Pip package manager. You can download the latest
version of Python for the Linux, macOS, and Windows operating systems at https://
www.python.org/downloads/. Then, you’ll need to install the NumPy, Matplotlib,
SymPy, SciPy, and VPython modules individually using the Pip package manager (Sec-
tion 1.1.4). This step may cause problems if you install a new Python version: The mod-
ules can no longer be imported with the new IDLE version, and the programs will no
longer run. I will show you a way to fix this problem in Section 1.1.4. If the installation
of the Python modules fails, I recommend you use the Thonny development environ-
ment.
18
1.1 Development Environments
When you click Run • Python Shell, the Python shell will open. Next to the >>> input
prompt, you can directly enter Python commands or mathematical expressions, such
as 2+3, 3*5, or 7/5. Note that you must complete each entry by pressing the (Return) key.
1.1.2 Thonny
Compared to the professional solutions, Thonny is a rather simply designed develop-
ment environment with a comparatively small range of functions. However, it is par-
ticularly suitable for programming beginners due to its ease of use. Using Thonny, you
can run and test all the sample programs discussed in this book. Figure 1.2 shows the
user interface.
Thonny is available for Linux, macOS, and Windows and can be downloaded at https://
thonny.org.
The source code of the program must be entered into the text editor (upper left area).
Once the program has been started via the (F5) function key or by clicking the Start
button, a window opens where you’ll need to enter the file name of the program. The
19
1 Introduction
result of numerical calculations is then output in the Command Line window at the bot-
tom left of the Python shell. Each function plot of Matplotlib programs will be output
in a separate window. In the shell, also referred to as the Python console, you can also
enter Python commands directly. The Assistant in the main window, on the right, sup-
ports you in terms of troubleshooting, although you should temper your expectations
about its capabilities.
A particularly important feature of Thonny is that you can easily install and update the
NumPy, Matplotlib, SymPy, SciPy, and VPython modules. For these tasks, all you need
to do is open the Tools • Manage Packages dialog box, as shown in Figure 1.3. Then, in
the text box in the top-left corner, enter the name of the module you want to install
and click Install or Update.
To remove a module, you must select the corresponding module in the pane on the
left. Then, the Uninstall command button appears to the right of the Install command
button. One notable advantage of the package manager in Thonny is that you can also
test older versions of all available modules. For this task, simply click the ... command
icon to the right of the Install button, which will open a window where you can select
the desired version of the module.
1.1.3 Spyder
Spyder is the development environment of the Anaconda distribution of Python.
Except for VPython, the modules covered in this book—NumPy, Matplotlib, SymPy,
and SciPy—are already built in.
20
1.1 Development Environments
Spyder is available as a free download for Linux, macOS, and Windows at https://
www.spyder-ide.org.
To run an animation using a Matplotlib program, you must select Automatic as the
backend in the settings under IPython Console • Graphics. After starting the program, a
separate window will open where the animation will run. Matplotlib programs contain-
ing slider controls can also be executed interactively only with this option.
Spyder is an immensely powerful development environment. However, one disadvan-
tage is that the subsequent installation of modules that are not installed by default,
such as VPython, can be difficult for beginners. For more information on installing
Python modules, see the documentation for Spyder at https://www.spyder-ide.org.
1.1.4 Pip
To use development environments other than Thonny or Spyder, you can install
Python modules using Pip. Pip is not a development environment but the package
manager for Python that installs modules from the Python Package Index (PyPI) (https://
pypi.org/). Pip allows you to download and update modules easily—when you use
Python, Pip is a particularly important tool.
If you have installed Python and want to add only the NumPy module, for example,
you can enter the following command in a terminal on Windows, Linux, or macOS:
21
1 Introduction
If you use IDLE (e.g., version 3.9) and install a new version of Python (e.g., 3.11), then the
previously installed Python modules will no longer be imported into the updated ver-
sion. In this case, you should try installing via pip3.11 install numpy.
For more information about using Pip, see https://pypi.org/project/pip. If the installa-
tion or update of the Python modules fails, I recommend using the Thonny develop-
ment environment instead.
1.2.1 NumPy
The NumPy module (numerical Python) enables you to perform extensive numerical
calculations. For example, you can solve linear systems of equations, even with com-
plex numbers. Listing 1.1 shows a simple vector calculus program.
01 import numpy as np
02 A=np.array([1, 2, 3])
03 B=np.array([4, 5, 6])
04 print("Vector A:",A)
05 print("Vector B:",B)
06 print("Total A+B:",A+B)
07 print("Product A*B:",A*B)
08 print("Cross product :",np.cross(A,B))
09 print("Scalar product:",np.dot(A,B))
Output
Vector A: [1 2 3]
Vector B: [4 5 6]
22
1.2 The Modules of Python
Total A+B: [5 7 9]
Product A*B: [ 4 10 18]
Cross product : [-3 6 -3]
Scalar product: 32
1.2.2 Matplotlib
The Matplotlib module allows you to display mathematical functions, histograms, and
many other diagram types as well as to simulate and animate physical processes. The
graphical design options are remarkably diverse and rich in detail. Listing 1.2 shows a
simple example of the function plot of a polynomial.
01 import numpy as np
02 import matplotlib.pyplot as plt
03 x=np.arange(-2,6,0.01)
04 y=x**3-7*x**2+7*x+15
05 plt.plot(x,y)
06 plt.show()
Output
Figure 1.5 shows the output of the function plot.
23
1 Introduction
1.2.3 SymPy
Using SymPy (symbolic Python), you can calculate integrals or derivatives symboli-
cally or solve differential equations symbolically. A simplification of mathematical
terms is also possible (and much more). Listing 1.3 shows a simple example of symbolic
differentiation and integration.
Output
1.2.4 SciPy
SciPy (scientific Python) allows you to numerically differentiate, integrate, and numer-
ically solve systems of differential equations. SciPy is as comprehensive as it is versa-
tile. The capabilities of SciPy can only be partially described in this book. Listing 1.4
shows a simple example of a numerical integration program.
24
1.2 The Modules of Python
Output
Area A= 41.66666666666666
1.2.5 VPython
Using VPython, you can display fields in a 3D view or even animate their movements
in 3D space. As of version 7, the animations are displayed in the standard browser after
the program starts. Listing 1.5 shows an example of how you can program the anima-
tion of a bouncing ball.
Output
Figure 1.6 shows a snapshot of the animation. The VPython module is described in
Chapter 7. Of course, not all the capabilities of the Python modules we’ve mentioned
can be treated exhaustively in this book. If you miss a particular topic, I recommend
referring to the online documentation as a supplemental source of information. A
module’s maintainers should have a website where you’ll find tutorials for each mod-
ule to get you started, including complete module descriptions.
25
1 Introduction
The chapters after Chapter 7 describe additional possible uses of the modules in greater
detail with a focus on the practical application options.
Chapter 8 describes how you can compute alternating current (AC) electrical networks
using the symbolic method. In the project assignment, you’ll learn how to size an elec-
trical power transmission system.
Chapter 9 focuses primarily on the simulation of a quality control chart. You’ll learn
how to generate normally distributed random numbers and save them to a file. This
data is then read again to calculate its statistical characteristics, such as arithmetic
mean and standard deviation.
Chapter 10 describes how to set up truth tables and simplify complex logical circuits
using SymPy.
In Chapter 11, you’ll learn how to program GUIs using Python. The project task shows
you how to simulate simple control circuits.
26
1.4 Your Path through This Book
or as nonlocal assert
is import
True return
False from
None
With a few keywords like if, else, for, and while, along with the built-in Python func-
tion print(), you can already write simple Python programs.
27
1 Introduction
can skip Chapter 2. If just starting to learn, you must read that chapter first as a prereq-
uisite for understanding subsequent chapters.
Our approach to presentation and knowledge transfer is based on a uniform principle:
One to three examples from electrical engineering, mechanical engineering, or physics
are presented for each topic. After a brief description of the task, the complete source
code is printed. Directly after the source code, the output (the results of the calcula-
tions) takes place. The source code is then discussed and analyzed.
Our analysis of source code also includes an analysis of the results (output). Are the
results in line with expectations? Does the program solve the task set for it at all? Often,
you won’t fully understand the source code of a program until you’ve taken a closer
look at the output. After viewing the output, you can then analyze the source code
again.
At the end of each chapter, one or more project assignments are provided, discussed,
and fully solved to reinforce and expand on what was learned in that chapter.
28
Chapter 2
Program Structures
In this chapter, you’ll get to know the linear program structure as well as
the branching and repetition structures of the imperative programming
style of Python. Examples of object-oriented and functional program-
ming describe further ways to program using Python.
29
2 Program Structures
Statement1
Statement2
Statement3
...
The individual statements are translated and executed sequentially line by line by the
Python interpreter. Branching and repetition do not occur.
Let’s explore some basic concepts of programming such as statement, assignment, vari-
able, data type, and object using the calculation of a simple power circuit with only one
consumer. In such a circuit, the voltage U and the resistance R of the load are given. The
program is supposed to calculate the current I, with the following formula:
In this simple case, the formula for calculating the current provides the development
steps for designing the program. The inputs U and R are to the right, and the output is
to the left of the equal sign of the formula.
Input U
Input R
I = U/ R
Output I
For the design phase of program development, structure charts are particularly useful
because they enable the description of the problem solution independently of the syn-
tax of any particular programming language. In addition, they help you understand the
30
2.1 Linear Program Structures
problem and thus to find a suitable solution. Structure charts can be used to clearly
illustrate the flow structures of programs. In our example, to calculate the current, you
use the formula to create a structure chart with four statements, as shown in Figure 2.1.
You can implement this structure chart directly as Python source code by using the =
assignment operator for the simple assignments and using the print function for the
output. Formulas can also be transferred directly into the source code. Note that you
can use the usual mathematical operators (/, *, +, and -). In addition, the quantity
searched for must always be positioned to the left of the equal sign (the = assignment
operator). For testing, enter the source code from Listing 2.1 into your development
environment and start the program.
01 #!/usr/bin/env python3
02 #01_linear1.py
03 U=230
04 R=11.8
05 I=U/R
06 b="The current is:"
07 print(b, I, " A")
Output
Analysis
The program contains a total of five statements. In applied computer science, a state-
ment is a syntactically related section of source code that tells the Python interpreter
what action it is supposed to perform. In this sample program, a statement consists of
one program line each.
In line 01, which is referred to as the shebang, the operating system is told which inter-
preter should be used for running the program. Due to the env specification, you don’t
need to specify the path of the directory where the Python interpreter is located. To run
the program on Linux or macOS directly in the terminal using the ./01_linear1.py
command, you must first set the executable flag by using the chmod +x 01_linear1.py
command.
On Windows, the statement in line 01 gets ignored. In all the following sample pro-
grams, the shebang is not specified.
In line 02, the filename of the #01_linear1.py program is written as a comment. This
specification is useful so that the developer (and you as a learner) don’t lose track of the
numerous program examples. All lines preceded by a # character are ignored by the
Python interpreter. Comments are used to explain statements of the source code in
more detail.
31
2 Program Structures
Now, the actual program starts with a statement. Line 03 causes the U variable to be
assigned the value 230. In computer science, the term variable has a completely differ-
ent meaning than in mathematics. For a simple understanding, think of the U variable
as a symbolic address within the working memory where the number 230 is stored. At
the same time, this assignment declares the U variable.
The equal sign in the context of Python has the meaning of an assignment, not that of
a mathematical equal sign! An assignment is a type of statement that gives a variable a
new value. The U variable automatically receives the Integer data type because the num-
ber 230 is an integer.
Thus, integers can consist of any number of digits. The internal designation for the
Integer data type is int. In Python, variables are declared indirectly; that is, the first
time a variable is used, its name and data type are made known to the interpreter
during runtime. However, the data type can still change during runtime.
In line 04, the R variable of the float data type is declared because the floating point
number 11.8 is assigned to it. This data type is the approximated representation of a
real number.
In line 05, the I variable is declared, and at the same time, the current is calculated.
Notice how you can also declare variables by assigning the result of a formula to them.
As in other imperative programming languages, in Python, the division operation is
32
2.1 Linear Program Structures
also written using the / operator (slash). The result of the division of voltage and resis-
tance is assigned to the I variable.
In line 06, the b variable of the string type is declared because a string is assigned to this
variable. A string is a sequence of individual characters. The quotation marks tell the
interpreter that this is a string variable. You can place any text between the quotation
marks. The internal name for the string type in Python is str.
In line 07, the result of the calculation using the print function is output in an unfor-
matted way. You can separate each output by using a comma. If the print function is
supposed to output a string, such as the unit of current (A) in this example, the string
must be enclosed in quotation marks.
>>> U=230
>>> R=11.8
>>> I=U/R
>>> b="string"
>>> type(U)
<class 'int'>
>>> type(R)
<class 'float'>
>>> type(I)
<class 'float'>
>>> type(b)
<class 'str'>
You can use the built-in type() function in Python to determine the type of an object.
The class keyword specifies the respective object type. The U object belongs to the int
class, the R and I objects belong to the float class, and the b object belongs to the str
class.
Each object is identified by a number. You can use the built-in id() function to deter-
mine these numbers (identities):
>>> id(U)
4505151824
33
2 Program Structures
>>> id(R)
4506525960
>>> id(I)
4506525888
>>> id(b)
4509028720
Each object is given its own integer as its identity, which is guaranteed to be unique and
remains constant for the lifetime of the program. The identities themselves represent
memory addresses in the working memory (RAM). Even if the type of an object should
change during runtime, its identity (memory address) remains the same. Thus, an
object has a name (identifier), a value, a type, and an identity, and it belongs to a certain
class. If we continue to talk about variables, then what we actually mean is objects. A
quote from the Python documentation should clarify this connection again:
Objects are Python’s abstraction for data. All data in a Python program is repre-
sented by objects or by relations between objects.... Every object has an identity, a
type and a value. An object’s identity never changes once it has been created; you
may think of it as the object’s address in memory. The “is” operator compares the
identity of two objects; the id() function returns an integer representing its identity.
01 #02_linear2.py
02 U=230
03 R1,R2,R3=0.12,0.52,228
04 Rg=R1+R2+R3
05 I=U/Rg
06 P1=R1*I**2
07 P2=R2*I**2
08 P3=R3*I**2
09 print("Current I={0:6.3f} A " .format(I))
10 print("P1={0:3.2f} W, P2={1:3.2f} W, P3={2:3.2f} W".format(P1,P2,P3))
11 #print("P1=%3.2f W, P2=%3.2f W, P3=%3.2f W" %(P1,P2,P3))
34
2.1 Linear Program Structures
Output
Current I= 1.006 A
P1=0.12 W, P2=0.53 W, P3=230.72 W
Analysis
The program calculates the total resistance of three resistors (line 04), the current, and
the partial powers of the resistors. The assignment in line 03 is new. The R1 resistor is
assigned the value 0.12; the R2 resistor, the value 0.52; and resistor R3, the value 228. The
declaration of the three variables is done simultaneously in one program line. The indi-
vidual identifiers of the variables are separated by commas just like the values of these
variables.
In lines 06 to 08, the current I is squared using the ** operator. The Python interpreter
considers the mathematical precedence rule. First, the current is squared and then
multiplied by the resistances.
The formatting of the outputs is specified in lines 09 and 10. The curly brackets tell the
interpreter to output the results in a formatted way. The number before the dot indi-
cates the total number of characters (digits plus separators) of a floating point number.
The number after the dot defines the number of decimal places. The output of the cur-
rent therefore has three decimal places, while those of the partial powers have two dec-
imal places each. The letter f stands for float. The number before the colon defines the
position within the output. The .format(I) and .format(P1,P2,P3) statements cause
the calculated results for the current and the partial powers to be output formatted.
Alternatively, the output could have been formatted more simply using the syntax of
line 11.
01 #03_linear3.py
02 while True:
03 print("\n---Input---")
04 U=float(input("Voltage: "))
05 R=float(input("Resistance: "))
06 I=U/R
07 P=U*I
08 print("\n---Output---")
09 print("Current {0:6.2f} A " .format(I))
10 print("Power {0:6.2f} W " .format(P))
35
2 Program Structures
Output
---Input---
Voltage: 230
Resistance: 24
---Output---
Current 9.58 A
Power 2204.17 W
Analysis
The program starts with a while loop in line 02. This loop construct is introduced at this
point because one-time console entries are just as useless as static assignments. Sec-
tion 2.4.1 discusses the syntax of the while loop in greater detail. The while keyword is
followed by the condition that must be met before the subsequent statements belong-
ing to the loop body can be executed. The while statement must be terminated with a
colon. The editor of a Python development environment automatically indents all sub-
sequent statements that should be run repeatedly (with four spaces by default). This
indentation is elementary because it tells the Python interpreter which statements
belong to the loop body.
Since the condition is always True, the loop is an infinite loop. The prompt can be inter-
rupted by pressing the (Ctrl) + (C) shortcut (also often written as ^C) or by an incorrect
input (no number or no input). I’ll show you how to avoid this pretty inelegant pro-
gramming style in Section 2.4.
The escape sequence "\n" in lines 03 and 08 causes a line break in each case. An escape
sequence is a character combination that does not represent text but instead is a con-
trol statement that tells the computer how it should arrange the screen output. The n
after the backslash (\) stands for newline.
In line 04, the built-in input() function first outputs the text specified in the quotes to
the screen and then expects an input which must be terminated via (Return). Each
input is read as a string, converted to the float type via the built-in float() function
and then assigned to the U variable.
36
2.2 Functions
2.2 Functions
If you place all the statements needed for the calculation of a complex task in a single
coherent source code section (the main program), you’ll lose track of your own work as
the number of program lines increases. The development process itself and subse-
quent changes to the source code are thus unnecessarily complicated, if not impossi-
ble. In this context, the subroutine technique provides the option to break down
complex problems into subproblems that are easy to master. This structuring option is
available in every programming language. However, in the discourse about modern
programming languages (C, C++, Java), the term subroutine is no longer common;
instead, the term function is used. In general, in modern programming languages, a
function is understood to be a structural element that combines a logically related set
of instructions into a holistic unit.
The use of functions provides the following advantages:
쐍 The source code of a program becomes clearer and is thus easier to understand.
쐍 Troubleshooting (debugging) is simplified.
쐍 Programs structured by functions are easier to maintain.
쐍 Once written and tested, functions can be used by other programs.
쐍 A function can be called at different places in the same program.
쐍 A single function can be used for different calculations if the calculation rule for the
different tasks has the same structural design. The calculation of kinetic energy,
rotational energy, electrical energy, and magnetic energy provides a vivid example
of this flexibility, as shown in Listing 2.12.
Function
A Python function is a subroutine that solves a subproblem. A function definition con-
sists of the function header and the function body. The function header is introduced
with the keyword def, followed by the function name func(), which must end with
parentheses. A colon marks the end of the function header.
37
2 Program Structures
The function body contains the individual statements. It ends with the return state-
ment. In a function call such as a=func(), the calculated values are stored in the
object, a.
input() String Reads a string from standard input and returns it.
You may wonder why the second column in Table 2.1 does not contain the word
“parameter” but “argument” instead. Applied computer science distinguishes between
the term argument and the term parameter. An argument is a value that is passed when
the function is called. This value is assigned to the assigned parameters within the
function. A parameter is a name that is used within the function.
You can find documentation for all built-in functions at https://docs.python.org/3.12/
library/functions.html.
38
2.2 Functions
01 #04_function1.py
02 U,R = 230,460
03 t=8
04 price=0.3
05
06 def current():
07 I=U/R
08 print("Current: ", I, " A")
09
10 def power():
11 P=U**2/R
12 print("Power : ", P, " W")
13
14 def work():
15 P=U**2/R
16 W=P*t
17 print("Work: ", W, " Wh")
18
19 def cost():
20 I=U/R
21 W=U*I*t
22 c=W*price/1000.0
23 print("Cost: ", c, " Euro")
24
25 current()
26 power()
27 work()
28 cost()
Output
Current: 0.5 A
Power: 115.0 W
Work: 920.0 Wh
Cost: 0.276 Euro
39
2 Program Structures
Analysis
This program consists of four functions. Each function solves a self-contained task. The
identifiers for the function names should be formulated in such a way that the task of
a function is immediately recognized. Only nouns should be used for function names
because an identifier such as calculate_current() contains a pleonasm, a meaningless
duplication, because the actual task of the function is already to perform a calculation.
A function name should describe the task of the function as precisely as possible. The
first character in a function name must not be a number or a special character.
In lines 02 to 04, the variables necessary for the calculations are defined. The values of
these variables are available to all four functions, which is why they are also referred to
as global variables.
Line 06 contains the function definition for the calculation of the current. All other
function definitions follow the same pattern. A function definition is introduced by the
def keyword. This keyword is followed by a freely selectable function name. The paren-
theses after the function name are mandatory, even if no parameters are used. Identi-
fiers for function names should consist of lowercase letters, as convention requires.
The function definition is terminated with a colon. The function body (lines 07 and 08)
consists of the individual statements. All statements of a function must be indented
evenly so that the interpreter recognizes which statements belong to the function defi-
nition. The function definition is complete when it is followed by a statement that has
the same indentation depth as the function header. The variables that are declared
within functions are referred to as local variables. A local variable cannot be changed
outside of the function in which it is declared.
In lines 25 to 28, the individual functions are called by specifying their name. The syn-
tax of a function call is similar to a simple statement without an assignment. When call-
ing the function, don’t forget the parentheses. Only the parentheses tell the interpreter
that the statement is a function call. The self-explanatory names of the identifiers for
the functions significantly improve the readability of the program. At first glance,
which calculations are performed can be immediately visible.
40
2.2 Functions
U
I
R
01 #05_function2.py
02 def current(U,R):
03 return U/R
04
05 def power(U, R):
06 return U**2/R
07
08 def work(U, R, t):
09 P=U**2/R
10 W=P*t
11 return W
12
13 def cost(U, R, t, price):
14 I=U/R
15 W=U*I*t
16 c=W*price/1000.0
17 return c
18
19 Uq=230 #V
20 RLoad=23 #ohms
21 tn=8 #h, hours
22 price_actual=0.3 #euro
23 print("Current: ", current(Uq, RLoad), " A")
24 print("Power : ", power(Uq, RLoad), " W")
25 print("Work : ", work(Uq, RLoad,tn), " Wh")
26 print("Cost : ", cost(Uq, RLoad,tn,price_actual), " euros")
41
2 Program Structures
Output
Current: 10.0 A
Power : 2300.0 W
Work : 18400.0 Wh
Cost : 5.52 euros
Analysis
In line 02, the current(U,R) function is defined with the U and R parameters. The return
statement in line 03 is followed by the calculation rule for the current. In line 23, this
function is called in the built-in print function, and the calculated value for the current
is output. As a result, there is a function call within a function. The variables in paren-
theses are referred to as parameters. A further distinction can be made between the
parameters of the function definition (called formal parameters) and the parameters
passed in the function call (called current parameters or arguments). The other function
definitions and calls follow the same pattern. The variables for the current parameters
are declared in lines 19 to 22.
In lines 23 to 26, the values for voltage Uq, resistance RLoad, time of use tn, and for the
present price preis_actual are passed as arguments to the functions.
The variables declared in the function bodies are only valid locally (i.e., these variables
cannot be accessed from outside). This principle of local validity, also known as data
encapsulation, ensures that the values of local variables cannot be changed. Thus, an
assignment at another position in the program does not cause these values to be over-
written. If this were the case, the calculations of the functions could be manipulated
from outside, and the function would return incorrect results.
42
2.2 Functions
The moment of inertia J of a cylinder increases proportionally with its mass and pro-
portionally with the square of its radius r:
The mass is calculated from the volume V and the density of the cylinder:
To calculate the volume V, you need the diameter d and the length l of the cylinder:
To implement this task, you must enter the formulas according to the syntax rules in
reverse order into the text editor of a Python development environment. Your source
code should resemble the code shown in Listing 2.6. Start the program.
01 #06_function3.py
02 rho=7.85 #kg/dm^3, density for steel
03 alpha=1.2 #1/s^2, angular acceleration
04 g=3 #accuracy
05
06 def cylinder(d,l):
07 V=round(0.785*d**2*l,g)
08 m=round(rho*V,g)
09 J=round(0.5*m*(d/2/10)**2,g)
10 Mb=round(alpha*J,g)
11 return (V,m,J,Mb)
12 #return V,m,J,Mb
13 #return [V,m,J,Mb]
14
15 d1=1 #dm
16 l1=10 #dm
17 T=cylinder(d1, l1)
18 print("Cylinder data: ", T)
19 print("Volume: ", T[0]," dm^3")
20 print("Mass: ", T[1]," kg")
21 print("Moment of inertia: ", T[2]," kgm^2")
22 print("Acceleration torque:", T[3]," Nm")
43
2 Program Structures
Output
Analysis
In lines 06 to 11, the cylinder(d,l) function is defined. The formal parameters are the
diameter d and the length l of the cylinder. First, the volume V is calculated, then the
mass m, then the moment of inertia J and finally the acceleration torque Mb. The results
are rounded to three digits of precision using the built-in round function. The return
statement in line 11 returns the four calculated values as a tuple. A tuple is a data struc-
ture that consists of an immutable sequence of variables (here V, m, J and Mb). The ele-
ments of a tuple are enclosed in parentheses and separated by commas (line 11). The
parentheses can also be omitted (line 12).
If the return values are enclosed in square brackets (line 13), then the return is a list.
Because the elements of a list are changeable, however, you should avoid this option of
a return.
In line 17, the cylinder(d1,l1) function is called with the current parameters d1=1 and
l1=10. The results are assigned to the T variable. At this point, it becomes clear that T is
not a simple variable, but an object containing the memory addresses of the variables
V, m, J and Mb. In other words: T is a reference pointing to the memory addresses of the
elements of the tuple T.
Line 18 outputs the four values of tuple T. Since the output is not unique in this form, in
lines 19 to 22 the values are read individually from the tuple using the [] operator and
then output.
01 #07_function4.py
02 rho=7.85 #kg/dm^3, density of steel
03
04 def volume(d,l):
05 return 0.785*d**2*l
06
07 def mass(d,l):
08 return rho*volume(d,l)
44
2.3 Branching Structures
09
10 def moment_of_inertia(d,l):
11 return 0.5*mass(d,l)*(d/2/10)**2
12
13 def acceleration_torque(d,l,alpha):
14 return alpha*moment_of_inertia(d,l)
15
16 d1=1 #dm
17 l1=10 #dm
18 alpha1=1.2 #1/s^2, angular acceleration
19 V=volume(d1,l1)
20 m=mass(d1,l1)
21 J=moment_of_inertia(d1,l1)
22 Mb=acceleration_torque(d1,l1,alpha1)
23 print("Volume: ", V, " dm^3")
24 print("Mass: ", m, " kg")
25 print("moment of inertia: ", J, " kgm^2")
26 print("Acceleration torque: ", Mb, " Nm")
Output
Analysis
The functions are defined, as usual, in lines 04 to 14. In line 08, the first function call of
the volume() function occurs directly after the return statement. The volume is not cal-
culated until the mass() function is called in the main program (line 20). The function
calls are made in lines 19 to 22. The mass() function calls the volume() function. The
moment of inertia() function calls the mass() function, and the acceleration torque()
function calls the moment of inertia() function. The return values are assigned to the V,
m, J, and Mb variables, which are thus available for output in lines 23 to 26.
45
2 Program Structures
if condition:
statement1
statement2
statement3
else:
statement4
statement5
If the condition is true in the single selection, then the statement block from statement1
to statement3 will be executed; if the condition is false, then the statement block from
statement4 to statement5 will be run. Let’s use an example of a quadratic equation to
show how a choice between two possible cases is implemented.
The general form of a quadratic equation is as follows:
The term under the root is called discriminant D in the technical language of mathe-
matics.
The expression under the root can also take negative values. When this case occurs, the
equation can no longer be solved within the real number space. For this reason, the
program must catch this case by checking whether D ≥ 0. For the problem to be solved,
the structure chart shown in Figure 2.3 can be created.
Input p,q
Compute D
D≥0
T F
Compute x1
Compute x2 ∅
Output x1,x2
Listing 2.8 shows the implementation of the structure chart for this simple branching
structure.
46
2.3 Branching Structures
01 #08_branch1.py
02 import math as m
03 p=-8.
04 q=7.
05 D=(p/2)**2 - q
06 if D >= 0:
07 x1 = -p/2 + m.sqrt(D)
08 x2 = -p/2 - m.sqrt(D)
09 print("x1 =",x1,"\nx2 =",x2)
10 print("p =",-(x1+x2),"\nq =",x1*x2)
11 else:
12 print("The equation cannot be solved!")
Output
x1 = 7.0
x2 = 1.0
p = -8.0
q = 7.0
Analysis
In line 02, the math module is imported and assigned to the m alias. The values for the p
and q coefficients of the quadratic equation are specified in lines 03 and 04. Line 05
calculates the discriminant D. If this value is greater than zero, the if branch will be
executed, and the values for x1 and x2 (lines 07 and 08) will be calculated. The sqrt()
root function of the math module is accessed using the m alias and the dot operator,
m.sqrt(D). Line 09 outputs the result. Line 10 performs a control calculation according
to Vieta’s theorem.
If the discriminant is less than zero, the else branch will be executed from line 11, and
the message that the equation cannot be solved will be output.
if condition1:
statement1
statement2
elif condition2:
statement3
47
2 Program Structures
statement4
elif condition3:
statement5
statement6
The elif keyword is used to query further conditions. The sample program shown in
Listing 2.9 for multiple selection determines the numerical value of a ring from the
color coding of a resistor. A carbon film resistor is coded with four color rings. The first
two rings represent the digits of an integer. The third ring serves as a multiplier. The
fourth ring indicates the tolerance. For simplicity, the complete evaluation of the color
rings is omitted in this example. For multiple selection, the structure chart can be cre-
ated from Figure 2.4.
Multiple Selection
Input Color
Color
Black Brown Red Orange Amber Green Blue Purple Gray White Default
01 #09_multiple_selection1.py
02 color=["black", "brown", "red", "orange", "yellow",
03 "\ngreen","blue","purple","gray","white"]
04 code="yellow" #input
05 if code==color[0]:
06 print("The color black is coded as 0.")
07 elif code==color[1]:
08 print("The color brown is coded as 1.")
09 elif code==color[2]:
10 print("The color red is coded as 2.")
11 elif code==color[3]:
12 print("The color orange is coded as 3.")
13 elif code==color[4]:
14 print("The color yellow is coded as 4.")
48
2.3 Branching Structures
15 elif code==color[5]:
16 print("The color green is coded as 5.")
17 elif code==color[6]:
18 print("The color blue is coded as 6.")
19 elif code==color[7]:
20 print("The color purple is coded as 7.")
21 elif code==color[8]:
22 print("The color gray is coded as 8.")
23 elif code==color[9]:
24 print("The color white is coded as 9.")
Output
Analysis
In line 02, a list of ten colors is created and assigned to the variable color. All properties
of the list elements are now stored in the color variable (an object!). Each color rep-
resents a specific digit. In line 04, the color of the color ring is assigned to the code vari-
able. The list elements are accessed using the [] operator. The if statement in line 05
determines the first alternative. The check whether the respective case applies is per-
formed using the == operator. All other cases are queried using the elif statement
(from line 07). For example, since the color yellow stands for value 4 and has the index
4 in the list, the program outputs the value 4.
01 #10_multiple_selection2.py
02 rate1,rate2,rate3=0.3,0.25,0.2 #euros
03 consumption=5500 #kWh
04
05 if 0 < consumption<= 5000:
06 print("Amount for rate1:",consumption*rate1, "euros")
07 elif 5000 < consumption <= 10000:
08 print("Amount for rate2:",consumption*rate2, "euros")
09 elif 10000 < consumption <= 30000:
49
2 Program Structures
Output
Analysis
Line 02 establishes three electricity rates. Line 03 determines the actual consumption.
The case query for the value ranges is performed using the notation known from math-
ematics. If the consumption is exactly equal to 5000 kWh or below, then the amount to
be paid for rate1 is calculated and output in line 06 using the if statement. Line 07 uses
the elif statement to query the consumption between 5,000 and 10,000 kWh. The
amount to be paid for rate2 is calculated and output in line 08. The same applies to line
09. If the consumption is not in the specified range, the else branch in line 11 will be
executed.
while condition:
statement1
statement2
statement3
...
The loop body can consist of one or more statements. The statements of the loop body
are executed as long as the condition is True, and its execution is aborted if the condi-
tion is no longer true (i.e., if this condition is False). The termination condition results
either from the calculations performed in the loop body or from a previously defined
condition.
50
2.4 Repetitive Structures
The first example shown in Listing 2.11 illustrates how to use a while loop to calculate
the value table of any mathematical function. The structure chart associated with the
program is shown in Figure 2.5. To focus on the essential structural elements of the pro-
gram, I have omitted the presentation of the function call.
While Loop
x =1
while x ≤ 10
Compute y = f(x)
Output x,y
x = x +1
The conversion of the structure chart into a Python program is shown in Listing 2.11.
01 #11_while_loop1.py
02 def f(x):
03 return x**2
04 x=1
05 while x<=10:
06 y=f(x)
07 print(x,y)
08 x=x+1 #better x+=1
Output
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
51
2 Program Structures
Analysis
In lines 02 and 03, a function named f(x) is defined. The return statement can be fol-
lowed by the term of any mathematical function, 𝑓(x). Line 04 initializes the indepen-
dent variable x with 1. The while statement in line 05 sets the termination condition as
x<=10. The loop body will be executed as long as x<=10. Each termination condition
must be terminated using a colon. All statements of the loop body must be indented
evenly. In line 08, x is incremented by the value 1 with each loop pass; in technical jar-
gon, this step is referred to as incrementing. This increment and the x<=10 condition
determine that the program executes the loop ten times. Line 06 calls function f(x)
with the current value of x. Line 07 outputs the values for x and y. In line 08, the value
of x is increased by 1 with each loop pass. Instead of writing x=x+1, the formulation x+=1
is also common.
For the storage variables mass, moment of inertia, capacity, and inductance, you gener-
ally specify the a variable. The physical quantities such as velocity, angular velocity,
voltage, and current are generally denoted by x:
In your development environment, enter the source code shown in Listing 2.12 and
then start the program.
52
2.4 Repetitive Structures
01 #12_while_loop2.py
02 def f(a,x):
03 return 0.5*a*x**2
04
05 next=True
06 while next:
07 print("Kinetic energy......1")
08 print("Rotational energy...2")
09 print("Electrical energy...3")
10 print("Magnetic energy.....4")
11 selection=int(input("Select:"))
12 if selection==1:
13 m=float(input("Mass m="))
14 v=float(input("Velocity v="))
15 Wkin=f(m,v)
16 print("\nThe kinetic energy is %6.3f Ws\n" %Wkin)
17 elif selection==2:
18 omega=float(input("Angular velocity \u03C9="))
19 J=float(input("Moment of inertia J="))
20 Wrot=f(J,omega)
21 print("\nThe rotational energy is %6.3f Ws\n" %Wrot)
22 elif selection==3:
23 C=float(input("Capacity C="))
24 U=float(input("Voltage U="))
25 Wel=f(C,U)
26 print("\nThe electrical energy is %6.3f Ws\n" %Wel)
27 elif selection==4:
28 L=float(input("Inductance L="))
29 I=float(input("Current I="))
30 Wmag=f(L,I)
31 print("\nThe magnetic energy is %6.3f Ws\n" %Wmag)
32 else:
33 next =False
For example, if you select menu item 2, enter 1.2 s-1 for the angular velocity, and 2.4 kg
m2 for the moment of inertia, the program will calculate a value of 1.728 Ws for the rota-
tional energy.
Kinetic energy......1
Rotational energy...2
Electrical energy...3
Magnetic energy.....4
Select:2
53
2 Program Structures
Analysis
Line 05 initializes the Boolean variable further via the True value. Line 06 contains the
loop header of the while loop. The loop body is executed as long as next equals True,
which is the case if the values 1, 2, 3, or 4 were entered for the Select variable. For all
other values, the else branch in line 32 is executed, and the next variable is set to False.
The denominator of the fraction contains the difference between the newly calculated
and the previously calculated function value. This value may become zero or take a
very small value during the calculations.
Listing 2.13 calculates the zero for the function:
Using a sketch for the function graphs f1 (x) = x and f2(x) = cos x, you’ll get an intersec-
tion point of both function graphs that is approximately at x = 0.74. Therefore, x1 = 0 is
set for the start value, and x2 = 1, for the end value.
01 #13_while_loop3.py
02 import math as m
03 def f(x):
04 return x-m.cos(x)
05
06 eps=1e-12 #termination condition
07 x1=0 #start value
08 x2=1 #end value
09 n=0
10 f1=f(x1)
54
2.4 Repetitive Structures
Output
1 : 0.6850733573260451
2 : 0.736298997613654
3 : 0.7391193619116293
4 : 0.7390851121274639
5 : 0.7390851332150012
6 : 0.7390851332151607
The result can be checked in the Python shell with the following command:
Analysis
Line 02 imports the math module (math), which is needed for the calculation of the cos
function. The m alias saves some typing work. Instead of math.cos(), the program calls
the cosine function via the m alias and the m.cos() dot operator. The function definition
is performed in lines 03 and 04. In lines 06 to 10, the variables are initialized with their
initial values.
Line 11 contains the termination condition of the while loop. The loop is supposed to be
executed as long as the amount of eps is greater than 10-12 and n<100. In line 13, the value
of variable x1 is assigned to variable x0. This assignment causes the last calculated value
of x1 to be temporarily stored in the (x1-x0) counter for the calculation of the differ-
ence. For the calculation of the denominator, the last value of f1 is assigned to the f0
variable in line 15 and thus also temporarily stored. The difference of the denominator
(f1-f0) can thus be calculated from the current and the previously calculated values.
Line 17 contains the termination condition. The loop is then exited when the amount
of the counter becomes less than 10-12. A check whether the case f1-f0==0 occurs or
55
2 Program Structures
whether it becomes f1==f0 would even work in this case. However, I strongly recom-
mend you avoid such an implementation because two floats are very rarely really
equal.
In line 18, the calculation of the zero is performed according to the regula falsi iteration
rule. The result is output with the number of required calculation steps in line 19.
The count variable i must always be of type integer. The range function sets the start
value, the end value, and the increment for the count variable. The head of the for loop
must end with a colon. The range(n) function internally generates a list of integers for
a range of values from 0 to n -1 with an increment of 1. For range(10), the count variable
i takes the values i = 0 to 9 in succession:
The count variable of a for loop can also iterate over a string, list, tuple, or dictionary.
These options are discussed in more detail in Section 2.5.
Figure 2.6 shows the structure chart for a for loop. The program should calculate the
value table of a mathematical function.
for x ← 0 to 10
Compute f(x)
Output x,y
The conversion of the structure chart into a Python source code is shown in Listing 2.14.
The program calculates the table of values for a parabola for the range from x = 0 to x =
10. The count variable x is of the integer type. You can also enter any other mathemat-
ical functions in the function definition (line 03).
56
2.4 Repetitive Structures
01 #14_for_loop1.py
02 def f(x):
03 return x**2
04
05 print(" x\ty")
06 for x in range(11):
07 y=f(x)
08 print("%2i %6.3f" %(x, y))
Output
x y
0 0.000
1 1.000
2 4.000
3 9.000
4 16.000
5 25.000
6 36.000
7 49.000
8 64.000
9 81.000
10 100.000
Analysis
The program outputs 11 pairs of values for x and y. The loop header in line 06 contains
the count variable x, which is automatically declared as int, and the range function,
whose parameters must also be of type int. The loop header must always end with a
colon. The loop body must be evenly indented. Line 07 calls the y = 𝑓(x) function. With
each new loop pass, x is incremented by 1, and the function value is recalculated until
the termination condition is reached. Line 08 outputs the values for x and y in a for-
matted manner. You can test the loop construct by inserting other start and stop val-
ues and other increments into the range function. If you type help(range) in the Python
shell, you’ll get detailed information about the range class.
57
2 Program Structures
The program in Listing 2.15 calculates the rectangle sums of the e-function between the
limits from 0 to 1. The expected result is A = 1.718281828459045 area units (e1 – 1).
01 #15_for_loop2.py
02 import math
03 def f(x):
04 #return x
05 #return -x+1
06 return math.exp(x)
07
08 a=0 #lower limit
09 b=1 #upper limit
10 n=1000
11 delta_x=(b-a)/n
12 r=0
13 x=a
14 for k in range(1,n+1):
15 r=r+f(x)*delta_x
16 x=a+k*delta_x
17 print("%6d %6.3f %6.15f" %(k, x, r))
Output
Analysis
The function definition is made in lines 03 and 06. The lines that have been com-
mented out can be used for further test functions. Lines 08 and 09 define the lower and
upper integration limits. The n variable in line 10 defines the number of subproducts
. For the intercept on the x-axis (called the abscissa), the somewhat unwieldy
identifier delta_x was chosen so as to avoid creating a false association with the differ-
ential dx. delta_x is calculated in line 11 from the difference between upper and lower
limits divided by the number of subproducts n.
The r variable is initialized with 0 (line 12), and the x variable is initialized with the
lower limit a (line 13). The for loop in line 14 is run through from k=1 to n+1. Thus, 1,000
subproducts (rectangles r) are added up. The number n of subproducts determines the
accuracy of numerical integration. Since rectangle sums are calculated, no improved
accuracy can be achieved by increasing n compared to other integration methods (i.e.,
trapezoidal, Simpson, or Romberg).
58
2.4 Repetitive Structures
The summation of the individual rectangle areas is performed via the summation algo-
rithm in line 15. On the right-hand side of the assignment, the sum of the old r value
and the rectangle area f(x)*delta_x at location k is calculated. The f(x) function is
called anew for each loop pass. The function argument x is recalculated at position k in
line 16 for each loop pass.
Line 17 outputs the number of calculations, the value of the upper limit, and the area.
Except for the third digit, the result calculated by the program matches the exact value.
01 #16_for_loop3.py
02 def f(x,y):
03 return x*y
04
05 x0=0
06 xn=1
07 y0=1
08 n=1000
09 delta_x=(xn-x0)/n
10 y=y0
11 for k in range(n+1):
12 x=x0+k*delta_x
13 y=y + f(x,y)*delta_x
14 print("%3i %6.3f %6.4f" %(k, x, y))
Output
59
2 Program Structures
Analysis
The function definition in line 02 expects two parameters when called. With each func-
tion call, the product of x and y is returned. The statement in line 09 calculates the
delta_x increment from the start and end values as well as the number n. In line 12, the
current x value is calculated for the function call in line 13. The algorithm of the Euler-
Cauchy method is implemented directly in line 13 in Python syntax. The print function
in line 14 outputs the number of calculations and the function value of the solution y at
position x=1. The result shows that the Euler-Cauchy method is not suitable for practi-
cal purposes because this algorithm still yields an error of 0.0006 even after 1,000 loop
passes. Doubling n only halves the error. The Heun method or the Runge–Kutta method
provide more accurate results.
Nested Loops
Loops can also be nested within each other. You can use two nested for loops to create
triangular or rectangular number schemes. Pascal’s triangle is an example of a triangu-
lar number scheme. It can be generated using the following binomial coefficient:
The math function comb(n,k) calculates the binomial coefficient. Listing 2.17 demon-
strates how you can create Pascal’s triangle using this function and two nested for
loops.
01 #17_for_for_loop1.py
02 from math import *
03 k=8
04 for n in range(k):
05 for k in range(n+1):
06 print(comb(n,k),end=' ')
07 print()
Output
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
60
2.4 Repetitive Structures
Analysis
The first for loop creates the lines of Pascal’s triangle (line 04). In line 05, the counting
range is increased by 1 with each new loop pass of the inner loop. Line 06 creates the
column entries. The end=' ' parameter prevents a line break. In line 07, the print func-
tion forces a new line break.
01 #18_for_for_loop2.py
02 a=[5,4,3,2,1] #list
03 print(a)
04 for i in range(len(a)-1):
05 for i in range(len(a)-1):
06 if a[i]>a[i+1]: #compare
07 a[i],a[i+1]=a[i+1],a[i] #swap
08 print(a)
Output
[5, 4, 3, 2, 1]
[4, 3, 2, 1, 5]
[3, 2, 1, 4, 5]
[2, 1, 3, 4, 5]
[1, 2, 3, 4, 5]
Analysis
The statement in line 02 creates list a. The list data structure is discussed in Section
2.5.2. Line 06 compares the predecessor a[i] with its direct successor a[i+1]. If the pre-
decessor is greater than its successor, the corresponding elements in the list will be
swapped (line 07). The swap process is performed using tuples, which is a data structure
described in Section 2.5.1.
61
2 Program Structures
moment indicates how stiff a beam is based on its cross-sectional area. For the second
area moment of a rectangle cross section, the following applies:
You can calculate the double integral numerically by applying the sum algorithm
within two nested for loops. Listing 2.19 shows the implementation of such an algo-
rithm.
01 #19_for_for_loop3.py
02 b=5 #width in cm
03 h=10 #height in cm
04 y1,y2=-b/2,b/2 #limits of the y-axis
05 z1,z2=-h/2,h/2 #limits of the z-axis
06 #Function definition
07 def f(y,z):
08 return z**2
09 #Calculate double integral
10 dy=dz=1e-2
11 m=int((z2-z1)/dz) #height
12 n=int((y2-y1)/dy) #width
13 sz=0
14 for i in range(m): #outside
15 z=z1+i*dz
16 sy=0
17 for j in range(n): #inside
18 y=y1+j*dy
19 sy=sy+f(y,z)
20 sz=sz+sy
21 Iy=sz*dy*dz
22 #Output
23 print("First moment of area for a rectangle cross section")
24 print("Iy =",Iy, "cm^4")
25 print("Iy =",b*h**3/12,"cm^4 exactly")
26 print(m,n)
Output
62
2.5 Data Structures
Analysis
In line 10, you can define the dy and dz increments. The increments determine the accu-
racy of the numerical integration. Lines 11 and 12 calculate the number of loop passes
for the outer and inner loops. For m = 1000 and n = 500, we obtain 1000 × 500 = 50000
computational steps in the inner loop.
In lines 15 and 18, the current values z and y are calculated for the z and y coordinates.
In line 19, these values are passed as arguments to the function f(y,z) and added to the
sum sy at each new loop pass.
In line 20, the sum is calculated in the z-direction. Line 21 calculates the second
moment of area, Iy.
The comparison between numerical integration and exact value shows that the accu-
racy is still acceptable. In Chapter 6 on using SciPy, you’ll learn how to use the function
dblquad(f,z1,z2,y1,y2)[0] to calculate the second moment of area in a much easier
way by using only one line of source code.
In applied computer science, a data structure is a set of objects that may only be manip-
ulated by means of well-defined operations. In a nutshell, data structure = objects +
operations. The data is organized in a way that is ideal for the particular data structure
in order to access it as efficiently as possible.
Python has the following built-in data structures: tuples, lists, dictionaries, and sets.
2.5.1 Tuples
A tuple is a sequence of elements that are iterable but cannot be modified. The ele-
ments of a tuple do not all need to be of the same type. The immutability of the
63
2 Program Structures
elements is the decisive characteristic of a tuple. You can define a tuple by enclosing
the elements in parentheses separated by commas. The Python shell is helpful again
with our first encounter with the tuple data structure through the following com-
mands:
>>> t=(2,4,6)
>>> t
(2, 4, 6)
>>> t[1]
4
>>> t[1]=8
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> type(t)
<class 'tuple'>
>>>
01 #20_tuple1.py
02 t1=(1,2,3)
03 t2= 4,5,6
04 t3=t1+t2
05 t4=3*t2
06 print("Tuple1 contains the elements",t1)
07 print("Tuple2 contains the elements",t2)
08 print("Tuple3 contains the elements",t3)
09 print("Tuple4 contains the elements",t4)
10 print("The third object of t3 has the value", t3[2])
11 print("Are t1 and t2 the same?",t1==t2)
12 print("t3 belongs to the class",type(t3))
13 print("t1 has id",(id(t1)))
14 print("t1[0] has id",(id(t1[0])))
15 print("t1[1] has id",(id(t1[1])))
16 print("t1[2] has id",(id(t1[2])))
64
2.5 Data Structures
Output
Analysis
Two tuples (i.e., t1 and t2) are defined in lines 02 and 03. You can also omit the paren-
theses. Line 04 concatenates tuples t1 and t2 to form the new tuple, t3. In line 05,
another new tuple t4 is created, this one containing three copies of tuple t2. Each ele-
ment of a tuple can be accessed in read-only mode via the [] operator (line 10). Tuples
can also be checked for equality using the == operator (line 11). Not only does a tuple
have its own identity (line 13), but also each element of a tuple has its own identity
(lines 14 to 16).
The left-hand value of tuple a[i], a[i+1] was assigned the right-hand value a[i+1], a[i].
a[i] had the value of a[i+1] and a[i+1] the value of a[i] after the swap. The swap oper-
ation was apparently performed successfully because the program worked. How can
we explain this contradiction, that tuples are supposed to be immutable, but during
the exchange process the values of two tuple elements were changed? Listing 2.21
resolves this contradiction.
01 #21_tuple2.py
02 a,b=10,20
03 t=(a,b)
04 print("----before----")
05 print("Value of a=%i id of a=%i" %(a,id(a)))
06 print("Value of b=%i id of b=%i" %(b,id(b)))
07 print("Value of t=",t,"id of t=",id(t))
08 a,b=b,a
65
2 Program Structures
09 print("----after----")
10 print("Value of a=%i id of a=%i" %(a,id(a)))
11 print("Value of b=%i id of b=%i" %(b,id(b)))
12 print("Value of t=",t,"id of t=",id(t))
Output
----before----
Value of a=10 id of a=4317511120
Value of b=20 id of b=4317511440
Value of t= (10, 20) id of t= 4326503240
----after----
Value of a=20 id of a=4317511440
Value of b=10 id of b=4317511120
Value of t= (10, 20) id of t= 4326503240
Analysis
The left-hand value of the tuple consists of the variables a and b (line 02). Variable a is
assigned the value 10, and variable b, the value 20. Line 03 defines a tuple with elements
a and b. In lines 05 to 07, the values and the identities of the variables and the tuple are
output. In line 08, the swap process takes place. After swapping, variable a has the value
20 and variable b has the value 10. The values and the IDs of a and b have changed after
the swap. That is, only the memory addresses of a and b were swapped. In contrast, the
values and ID of tuple t have not changed.
2.5.2 Lists
A list is an ordered summary of various objects. The values contained in a list are also
referred to as elements. The list itself is also considered an object. The special thing
about a list is that its length can be changed at runtime. The Python interpreter recog-
nizes a list definition by the square brackets in which the elements of a list are embed-
ded. The individual elements are separated by commas. Objects of a list can be, for
example, floats of measured values of a measuring sequence or any other objects. Lists
66
2.5 Data Structures
themselves can also be components of lists. The range function enables you to generate
lists automatically:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(2,10,2))
[2, 4, 6, 8]
>>> list(range(1,10,2))
[1, 3, 5, 7, 9]
Many operations are applicable to lists, such as inserting or removing single or multi-
ple elements, accessing single elements, sorting elements, and so on. The elements of a
list can be accessed via an index. Table 2.3 illustrates the structure of a list through a
model.
Index 0 1 2 3 4
Each element of a list is assigned an index. This index can be used for read and write
access to the individual elements of the list. The count always starts with index 0.
Again, you can use the Python shell to explore the list data structure, for instance,
with the following commands:
>>> l=[2,4,6]
>>> l[1]
4
>>> l[1]=8
>>> l
[2, 8, 6]
>>> type(l)
<class 'list'>
>>>
By specifying the index, the value of the element at position i is output. A new assign-
ment can change this value. The list l belongs to the list class.
A count variable can also iterate over a list in a for loop, as the following console exam-
ple shows:
67
2 Program Structures
You can perform numerous operations on lists, such as sorting the list elements, deter-
mining their length, and appending or removing elements. Table 2.4 contains selected
functions for operations on lists.
Function Description
In addition to these functions, you can also use methods. Table 2.5 contains important
methods for performing operations on lists. The letter l stands for list, and the letter e
stands for the element of a list.
Method Description
68
2.5 Data Structures
>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zipped = zip(x, y)
>>> list(zipped)
[(1, 4), (2, 5), (3, 6)]
The zip function creates a new list of three tuples from the two lists, x and y.
Operations on Lists
Listing 2.22 shows how you can implement some selected operations on lists. For this
purpose, uniform data of type float was deliberately selected as list elements in order
to establish a reference to technically relevant topics. The program calculates import-
ant statistical parameters of a measuring sequence.
01 #22_list1.py
02 import statistics as stat
03 l1=[52.1,48.7,50.1,49.6,51.8]
04 l2=[50.5,48.5,49.5,51.5,48.8]
05 l1.extend(l2) #method
06 sl=sorted(l1) #function
07 n=len(sl)
08 minimum=min(l1)
09 maximum=max(l1)
10 s=sum(l1)
11 m=s/n
12 r=maximum-minimum
13 z=stat.median(l1)
14 print("sorted list:\n",sl)
15 print("Number of elements:",n)
16 print("Minimum: %6.2f Maximum: %6.2f" %(minimum,maximum))
17 print("Sum:",s)
18 print("Mean:",m)
19 print("Median:",z)
20 print("Span:",r)
69
2 Program Structures
Output
sorted list:
[48.5, 48.7, 48.8, 49.5, 49.6, 50.1, 50.5, 51.5, 51.8, 52.1]
Number of elements 10
Minimum: 48.50 Maximum: 52.10
Sum: 501.1
Average: 50.11
Median: 49.85
Span: 3.6000000000000014
Analysis
The program calculates the arithmetic mean, the median, and the range of a measuring
sequence. In lines 03 and 04, two lists with five float types each are defined. The values
of the two lists are stored in objects l1 and l2. In line 05, list l2 is appended to list l1 via
the l1.extend(l2) method.
In line 06, the sorted(l1) function sorts the extended list l1 and assigns the result to
the sl variable. In line 07, the len(sl) function determines the length of the sorted list
and assigns it to the n variable. From the sum of the measured values (line 10), the mean
value m of the measured values can then be calculated in line 11. The determination of
the minimum (line 08) and the maximum (line 09) is performed via the built-in func-
tions min(sl) and max(sl) respectively. In line 10, the built-in sum(sl) function calcu-
lates the sum of the measuring sequence. The span r is calculated from the difference
between maximum and minimum (line 12). In line 13, the stat.median(l1) function cal-
culates the median of the measuring sequence. For this purpose, the statistics mod-
ule (line 02) must be imported.
Lines 14 to 20 show the output of the results.
Nested Lists
Nested lists are important for the representation of two-dimensional matrices with
NumPy arrays. To convert two nested lists a and b into NumPy arrays and then add
them, consider the following console commands:
70
2.5 Data Structures
array([[ 7, 8, 9],
[10, 11, 12]])
>>> A+B
array([[ 8, 10, 12],
[14, 16, 18]])
List Comprehension
Python even enables you to run statements within a list, while the list is not generated
until runtime. During runtime, its length can be changed (almost) at will. This feature
is referred to as list comprehension. Listing 2.23 demonstrates the power of the list com-
prehension feature by calculating the Pythagorean numbers within a selected range.
01 #23_list2.py
02 ug=1
03 og=20
04 p=[(a,b,c)
05 for a in range(ug,og)
06 for b in range(a,og)
07 for c in range(b,og)
08 if a**2 + b**2 == c**2]
09 n=len(p)
10 print(p)
11 print("Between %i and %i there are %i Pythagorean triples." %(ug,og,n))
Output
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17), (9, 12, 15)]
Between 1 and 20 there are 5 Pythagorean triples.
Analysis
Lines 02 and 03 define the upper and lower limits in which the Pythagorean numbers
are to be calculated. The list definition starts at line 04 and ends at line 08. The list con-
sists of only one element: a tuple of the triangle sides (a,b,c) including the three for
loops with the if query.
In line 05, the a variable is iterated from ug to og. The b variable is iterated from a to og
(line 06), and variable c is iterated from b to og (line 07). Line 08 checks that the sum of
the squares of a and b is equal to the square of the hypotenuse c. If this is the case, then
the Pythagorean triples are generated as list elements and stored in variable p (line 04).
Line 09 determines the length of list p. In line 10, the output of the Pythagorean num-
bers occurs in tuples.
71
2 Program Structures
2.5.3 Dictionaries
A dictionary is a sequence of key-value pairs. Unlike the elements of a list, an element
of a dictionary consists of two components: a key value and a data value. A key value
and its data value are separated by a colon. The individual key-value pairs are separated
by commas and enclosed in curly brackets. The first entry is the key value, while the
second entry is the data value: {key value:data value}. Table 2.6 illustrates the dictio-
nary data structure. The left-hand column contains the key values, while the right-hand
column contains the data values.
unique eindeutig
Anweisung statement
Zuweisung assignment
Schleife loop
Klammern parentheses
The following console example shows how a dictionary is implemented and how an
element can be accessed:
>>> d={"unique":"eindeutig","statement":"Anweisung"}
>>> d["unique"]
'eindeutig'
>>> type(d)
<class 'dict'>
>>>
Access to the value of a dictionary is enabled by using the key value with the [] opera-
tor. Table 2.7 lists the most important methods for operations on dictionaries.
Method Description
d.items() Returns a list of tuples. Each tuple contains a key-value pair from the
dictionary d.
72
2.5 Data Structures
Method Description
del d[k] Deletes the key-value pair with the key value k from the dictionary d.
The first sample program shown in Listing 2.24 shows how lists can be converted to dic-
tionaries and how basic operations on dictionaries must be implemented.
01 #24_dictionary1.py
02 l1=["Al","Mg"]
03 l2=[2.71,1.738]
04 l12=zip(l1,l2)
05 m1=dict(l12)
06 new={"Ti":4.5}
07 m1.update(new)
08 m2={"Fe":7.85,"V":6.12,"Mn":7.43,"Cr":7.2}
09 print("Key values of light metals:",m1.keys())
10 print("Densities of light metals:",m1.values())
11 print("Key values of heavy metals:",m2.keys())
12 print("Densities of heavy metals:",m2.values())
13 print("Light metals %s %i entries" %(m1,len(m1)))
14 print("Heavy metals %s %i entries" %(m2,len(m2)))
15 m2.update(m1)
16 print("Metals %s %i entries" %(m2,len(m2)))
17 del m2["V"]
18 print("Metals %s %i entries" %(m2,len(m2)))
19 print("Density of chromium: %s kg/dm^3" %(m2["Cr"]))
Output
Key values of light metals: dict_keys(['Al', 'Mg', 'Ti'])
Densities of light metals: dict_values([2.71, 1.738, 4.5])
Key values of heavy metals: dict_keys(['Fe', 'V', 'Mn', 'Cr'])
Densities of heavy metals: dict_values([7.85, 6.12, 7.43, 7.2])
Light metals {'Al': 2.71, 'Mg': 1.738, 'Ti': 4.5} 3 entries
Heavy metals {'Fe': 7.85, 'V': 6.12, 'Mn': 7.43, 'Cr': 7.2}
4 entries
Metals {'Fe': 7.85, 'V': 6.12, 'Mn': 7.43, 'Cr': 7.2, 'Al': 2.71,
'Mg': 1.738, 'Ti': 4.5} 7 entries
Metals {'Fe': 7.85, 'Mn': 7.43, 'Cr': 7.2, 'Al': 2.71, 'Mg': 1.738,
'Ti': 4.5} 6 entries
Density of chromium: 7.2 kg/dm^3
73
2 Program Structures
Analysis
Line 02 creates a list (i.e., list l1), which contains two light metals. Line 03 generates a
list (i.e., list l2) containing the corresponding densities. In line 04, the zip() method is
used to join both lists into a new list: l12. The dict() method converts list l12 to a dic-
tionary in line 05. In line 06, a new dictionary is created with only one key-value pair,
{"Ti":4.5}. In line 07, this element is inserted into dictionary m1. In line 08, a new dic-
tionary is created containing heavy metals m2. In lines 09 and 10, the key values are out-
put using the m1.keys() method, while the data values are output via the m1.values()
method of the m1 light metals. The same statements apply to heavy metals m2 (lines 11
and 12). Lines 13 and 14 output the key-value pairs of the light metals and heavy metals.
The m2.update(m1) method in line 15 merges the two dictionaries m1 and m2 into a new
dictionary named m2. The dictionary, which now contains the light and heavy metals, is
output in line 16. The del() method deletes vanadium from dictionary m2 in line 17. The
output in line 18 confirms the deletion. Line 19 illustrates once again how the [] opera-
tor is used to access the key of a dictionary.
A Dictionary
Listing 2.25 shows how an English–German and German–English dictionary is created
from two lists.
01 #25_dictionary2.py
02 e=["unique","statement","assignment","loop","parentheses"]
03 d=["eindeutig","Anweisung","Zuweisung","Schleife","Klammern"]
04 e2d=dict(zip(e,d))
05 d2e=dict(zip(d,e))
06 print("statement:", e2d["statement"])
07 print("Schleife:", d2e["Schleife"])
Output
statement: Anweisung
Schleife: loop
Analysis
Line 02 contains a list of English words. Line 03 contains a list of German words. In line
04, a dictionary for an English–German translation is created. In line 05, a dictionary
for the German–English translation is created. In lines 06 and 07, the translations are
output. The special trick behind this dictionary is that only one list is created for one
language at a time. If we were to proceed intuitively, we would probably implement
two dictionaries, each with two entries (key value and data value).
74
2.5 Data Structures
2.5.4 Sets
A set is an unordered collection of elements that can be iterated and modified. A set
never contains any duplicate elements. Sets are defined like a dictionary using curly
brackets. Empty curly brackets create an empty dict, not an empty set. The set Python
class implements the notion of sets known from mathematics. Consequently, the three
set operations—union (& operator), intersection (- operator), and difference quantity
(| operator)—are possible. For these set operations, the following methods are also
available: s1.union(s2), s1.intersection(s2), and s1.difference(s2), as described in
Table 2.8. Compared to the list data structure, the main advantage of a set is that it has
a highly optimized method for checking whether a particular object is included in the
set.
Let’s get a first impression of sets using the Python shell:
>>> s={1,2,3}
>>> s
{1, 2, 3}
>>> s.add(23)
>>> s
{1, 2, 3, 23}
>>> type(s)
<class 'set'>
A set is created by enclosing the comma-separated numbers in curly brackets. The add
method adds the element 23 to the set s. A type check reveals that s is an object of the
set class. Table 2.8 contains the most important methods for operations on sets.
Methods Description
Listing 2.26 forms the intersection, the difference, and the union of two quantities.
75
2 Program Structures
01 #26_sets.py
02 set1={"A","B","C","D"}
03 set2={"C","D","E","F"}
04 intersection= set1 & set2
05 difference=set1 - set2
06 union=set1 | set2
07 print("Set1:",set1)
08 print("Set2:",set2)
09 print("Intersection:",intersection)
10 print("Difference:",difference)
11 print("Union:",union)
12 print("Is B contained in set1:", set1.issuperset("B"))
Output
Analysis
Two sets are defined in lines 02 and 03. The set operations are performed in lines 04 to
06. Line 12 checks if element B is contained in set1.
76
2.6 Functional Program Style
쐍 Functions can also be defined without explicit naming. This type of function is also
referred to as a lambda function.
쐍 For elementary mathematical operations, the prefix notation is used.
To illustrate the functional programming style in an example, I want to use the calcula-
tion of the moment of inertia and acceleration torque of a solid cylinder again. This
time, however, we won’t start with a Python program, but with a “real” functional pro-
gramming language. Listing 2.27 is written in the Racket programming language, which
is a Lisp derivative. The fact that Python does not support a true functional program-
ming style becomes clear when you compare these two programming languages. In
addition, a common approach to learning a programming language is to highlight the
differences among them.
#lang racket
;cylinder.rkt
(define (volume d l)
(* 0.785 d d l))
(define (mass d l)
(* 7.85 (volume d l)))
(define (momentofinertia d l)
(* 0.5 (mass d l) 0.25e-3 d d))
Output
Volume: 7.8500000000000005
Mass: 61.6225
Moment of inertia: 0.0770281
Acceleration torque: 0.0924337
77
2 Program Structures
Analysis
The function definitions are made using the define keyword. What is noticeable in this
context is the high number of parentheses. Suitable development environments, such
as DrRacket, are available if you ever want to develop professional applications in
Racket.
The function arguments are not enclosed in parentheses, only the functions them-
selves. Formatting the source code is not mandatory. You could also place all the func-
tions on one line. To improve clarity, the definition part and the calculation part should
be separated from each other. The prefix notation, also referred to as the Polish nota-
tion, is also unusual. Polish mathematician Jan Łukasiewicz used prefix notation in the
1920s to describe mathematical propositional logic in a more compact way. To those
accustomed to school mathematics, Polish notation may seem difficult to understand.
But enthusiasts of functional programming languages defend it as particularly simple
and elegant because not only is it clearer, but also much easier to handle than infix
notation.
The outputs are realized either using the display or writeln keywords. The units have
been deliberately omitted in our example.
Functional programs are implemented in Python using the lambda operator. The lambda
calculus was originally introduced in the 1930s by Alonzo Church and Stephen Cole
Kleene for the description of function definitions. John McCarthy used this concept in
the late 1950s to define the functions of the functional programming language Lisp.
The general syntax of a lambda function looks as follows:
01 #28_functional.py
02 rho=7.85 #kg/dm^3
03 volume=lambda d,l: 0.785*d**2*l
04 mass=lambda d,l: rho*volume(d,l)
05 moment_of_inertia=lambda d,l: 0.5*mass(d,l)*(d/2/10)**2
06 acceleration_torque=lambda d,l,omega:omega*moment_of_inertia(d,l)
07 #Output d and l in dm
08 print("Volume:",volume(1,10), "dm^3")
09 print("Mass:",mass(1,10),"kg")
10 print("Moment of inertia:",moment_of_inertia(1,10),"kgm^2")
11 print("Acceleration torque:",acceleration_torque(1,10,1.2),"Nm")
78
2.7 Object-Oriented Program Style
Output
Analysis
In lines 03 to 06, the functions are defined using the lambda operator. Because these
functions are not given a name, they are also called anonymous functions. Directly after
the lambda operator and separated by commas are the formal parameters. The colon is
followed by the calculation rules. Anonymous functions are treated like normal
expressions. For this reason, they can also be assigned to variables.
The output is shown in lines 08 to 11. Inside the print functions, the variables are
treated like normal function calls with current parameter passes.
79
2 Program Structures
Python has adopted criteria 1 to 5. The basic idea is simple: All data and operations are
combined into one unit, the object. This principle is referred to as data encapsulation.
In the terminology of current OOP, instead of the terms data and operations, the terms
attributes and methods are used, where a method is just another name for the already
familiar term function. The syntax of methods and functions is completely consistent.
In a nutshell:
Object = Attributes + Methods
The concept behind OOP has three goals in particular:
쐍 The principle of reusability
Once defined, classes are supposed to be reusable in other software projects.
쐍 The principle of data encapsulation
The fact that extensive programs with many thousands of program lines (state-
ments) are divided into clearly arranged classes is supposed to reduce the complex-
ity. As a result, software projects become more manageable. Each programmer can
freely choose their own variables for their classes without mutual interference (side
effects) during program execution.
쐍 Increased maintainability
For example, if a more effective algorithm with a better runtime has been found for
certain methods of a class, it can be re-implemented in its class as a method with the
same name as its predecessor without having to change the main program.
Class
A class combines data (properties) and methods (functions). Classes are the smallest
units of an object-oriented program. A class definition describes how objects are con-
structed and which operations can be performed on them.
80
2.7 Object-Oriented Program Style
According to the unified modeling language (UML) notation, classes are represented as
class diagrams.
Cylinder
-diameter:float
-length:float
-alpha:float
+volume()
+mass()
+moment of inertia()
+acceleration torque()
Figure 2.7 shows the class diagram for the Cylinder class. A class diagram consists of a
rectangle divided into three horizontal areas. The upper rectangle contains the name of
the class. The properties are listed in the middle area. The negative sign means that the
variables must not be changed from the outside. Formulated in the technical language
of OOP, these variables are defined as private. This concept is called data encapsula-
tion.
Data Encapsulation
Data encapsulation is the prevention of uncontrolled access to the properties (the
data) of a class.
The lower area contains the methods of the class. The positive sign identifies the meth-
ods as public, which means that they are accessible from the outside, that is, from out-
side the class definition.
Methods
The Python functions defined within a class are called methods.
81
2 Program Structures
On the left-hand side of the assignment operator is a freely selectable identifier. On the
right-hand side of the assignment operator is the name of the class with the list of
parameters enclosed in parentheses.
The methods of a class are accessed using a dot operator in the following way:
objName.method()
Object
An object is a symbolically addressed memory area in which all data and methods of
the class definition are stored. An object is an instance of a class. Every object has a
name, and via this name, the methods of a class can be accessed. Any number of
objects can be created from one class.
Constructor
A constructor is a special method that is called when an object is created. This special
method takes care of the initialization of the properties.
Listing 2.29 shows how the Cylinder class is implemented. The volume, mass, moment
of inertia, and acceleration torque of a solid cylinder are calculated.
01 #29_oop.py
02 class Cylinder:
03 rho=7.85
04 def __init__(self,diameter,length,alpha):
05 self.__d=diameter #private
06 self.__l=length #private
07 self.__a=alpha #private
08
09 def volume(self):
10 return 0.785*self.__d**2*self.__l
11
12 def mass(self):
13 return self.rho*self.volume()
14
15 def moment_of_inertia(self):
16 return 0.5*self.mass()*(self.__d/2/10)**2
17
82
2.7 Object-Oriented Program Style
18 def accelerationtorque(self):
19 return self.__a*self.moment_of_inertia()
20 #Main program d and l in dm
21 z=Cylinder(1,10,1.2)
22 #Cylinder.rho=2.3
23 #z.__d=100
24 print("Volume:",z.volume(),"dm^3")
25 print("Mass: ",z.mass(),"kg")
26 print("Moment of inertia: ",z.moment_of_inertia(),"kgm^2")
27 print("Acceleration torque:",z.accelerationtorque(),"Nm")
Output
Analysis
The class definition, shown in lines 02 to 19, is preceded by the class keyword. A class
name should always start with an uppercase letter, as convention demands. The header
of a class definition is terminated with a colon.
In line 03, the class variable rho is defined. The namespace of these variables spans the
entire class, which means that all methods can use them in their calculations. A class
variable with the notation cylinder.rho=2.3 (line 22) allows for external write access.
In lines 04 to 07, the __init__() method is defined. This method is introduced and con-
cluded with two underscores and followed by the parameter list enclosed in parenthe-
ses with the self parameter and the parameters of the diameter, length, and alpha
attributes. The self parameter is not a keyword, and thus, its name can be freely
chosen. However, the convention is to use this identifier. In the function body of the
__init__ function, the assignments follow the pattern self.__d = diameter (line 05). All
variables that are prefixed with a self are called instance variables. As a result, each
newly created object (line 21) gets its own namespace. The two underscores in front of
an instance variable have the effect that these variables are declared as private; that is,
they cannot be modified from the outside (through the principle of data encapsula-
tion). If, for example, you remove the comment in line 23, the intended result won’t
change. If, on the other hand, the underscores of the instance variables are removed,
the value for the diameter can still be changed in line 23. Just try it!
The methods of the Cylinder class are defined from line 09 onwards. What is new
compared to the usual function definition is that only self is passed as a parameter. In
83
2 Program Structures
addition, the instance variables and methods with the self parameter as prefix are con-
nected by the dot operator. This notation causes a separate namespace to be formed for
each method whenever a new object is created.
In line 21, the z object is created by calling the Cylinder(1,10,1.2) method. A method
that bears the name of the class is called a constructor. Unlike C++ and Java, Python’s
language concept does not include an explicit constructor. When an object is created,
the implicit constructor is started first, and the init method is called immediately
afterwards. The constructor forms a clearly defined interface to the “outside world”
with its parameters.
Instance variables should only be addressed through such an interface. The z object is a
copy of the Cylinder class. This object can be used to access the methods of the Cylinder
class via the dot operator (lines 24 to 27). Many other objects of the Cylinder class can be
re-created with different current parameters. All instance variables and methods are
then each assigned their own namespace. The access of an object to a method of the
Cylinder class can also be interpreted in this way: The object z sends the message “Cal-
culate the acceleration torque” to the accelerationtorque() method of the Cylinder
class, and the method, thus addressed, returns the response “Here is the result.”
2.7.2 Inheritance
Inheritance is another important concept in OOP. The basic idea is again the reusability
of source code.
This concept can be confusing at first because the derived classes not only take over the
properties of the base class, but they also extend them. More vividly, you can think of
inheritance as a takeover or an extension. A derived class inherits attributes and meth-
ods from one or more base classes.
Inheritance
A base class makes its properties and methods available to other classes (the derived
classes).
01 #30_inheritance.py
02 class Area:
03
04 def __init__(self,width,length):
05 self.w=width
06 self.l=length
84
2.7 Object-Oriented Program Style
07
08 def area(self):
09 return self.w*self.l
10
11 class Volume(Area):
12
13 def __init__(self,width,length,height):
14 Area.__init__(self,width,length)
15 #super().__init__(width,length)
16 self.h=height
17
18 def volume(self):
19 return Area.area(self)*self.h
20 #return super().area()*self.h
21
22 A=Area(1,2)
23 V=Volume(1,2,3)
24 print("Area: ",A.area()," m^2")
25 print("Volume:",V.volume()," m^3")
Output
Area: 2 m^2
Volume: 6 m^3
Analysis
The base class Area (line 02 to 09) contains the area() method for the calculation of a
rectangular area. Starting from line 11, the derived class Volume takes over the attri-
butes width and length as well as the area() method from the base class. The fact that
a class inherits from another class is communicated to the Python interpreter by pass-
ing the name of the base class as a parameter to the derived class (line 11). The __init__
method in line 13 requires the width, length, and height of a cuboid as parameters. The
__init__ method of the base class is accessed either through the name of the base class
Area (line 14) or through the built-in function super() in line 15. In line 19 or line 20, the
volume is calculated according to the well-known formula “base area multiplied by
height.” Line 22 creates an object A for the base area, and line 23 creates an object V for
the volume calculation. These two objects can be used to access the methods of the
Area and Volume classes using the dot operator in the print() function (lines 24 and 25).
85
2 Program Structures
Given is the mass of the load ma = 1 kg, the modulus of elasticity E = 216 ⋅ 103 N/mm2, the
length of the shaft l = 120 mm, and the maximum permissible bending stress = 100
N/mm2.
The minimum diameter d, the static deflection 𝑓, and the critical rotational speed nk are
to be determined.
For the calculation of the deflection and the bending stiffness, we need the second
moment of area Ia:
If the second moment of area is divided by half the shaft diameter, we obtain the axial
section modulus W. This value is needed for the calculation of the shaft diameter.
Using the bending main equation, the bending stress of the shaft can then be used to
calculate the minimum diameter of the shaft.
If the force acts exactly in the center of the shaft, the maximum bending moment is
calculated with the following formula:
For the deflection in the center of the shaft, the following formula applies:
86
2.8 Project Task: Dimensions of a Shaft
The critical rotational speed nk is calculated from the root of the quotient of the bend-
ing stiffness Rb and the mass m:
All formulas can be transferred directly into Python source code, as shown in Listing
2.31.
01 #31_project_shaft.py
02 from math import sqrt,pi
03 g=9.81 #Acceleration due to gravity
04 rho=7.85 #kg/dm^3
05 E=216e3 #N/mm^2
06 l=120 #mm
07 sigma=100 #N/mm^2
08 m=1 #kg
09 #Calculations
10 F=m*g
11 Mb=F*l/4
12 d=pow((32*Mb)/(pi*sigma),1/3)
13 d=round(d+0.5)
14 Ia=pi*d**4/64.0
15 f=F*l**3/(48*E*Ia)
16 Rb=48*E*Ia/l**3 #F/f
17 nk=sqrt(1e3*Rb/m)/(2*pi)
18 #Outputs
19 print("Diameter in mm:",round(d,2))
20 print("Deflection in mm:",round(f,3))
21 print("Critical rotational speed 1/min:",int(60*nk))
Output
Diameter in mm: 4
Deflection in mm: 0.13
Critical rotational speed 1/min: 2622
87
2 Program Structures
Analysis
The inputs are implemented as assignments in lines 03 to 08. If several different
parameters are to be tested, these assignments can be replaced by input functions.
The individual calculations are performed in lines 10 to 17. In line 12, the cube root is cal-
culated using the pow function. The rounding function round(d+0.5) in line 13 ensures
that the next larger integer diameter is determined.
Outputs are shown in lines 19 through 21. The program calculates a diameter of 4 mm
for the given sizes. At this diameter and the load with the mass of 1 kg, the shaft deflects
by 0.13 mm. Dangerous resonance effects occur at the critical rotational speed of 2622
1/min. Due to the increased deflection, the shaft may break at this speed.
2.9 Tasks
1. Write a Python program that calculates the air resistance of a car (or bicycle) in com-
plete calm according to this formula:
Furthermore, the program should calculate the drive power and the work done for a
given travel time.
2. Formulate the following mathematical expressions as Python source code:
88
2.9 Tasks
89
Chapter 3
Numerical Calculations Using NumPy
In this chapter, you’ll learn how to perform operations on vectors and
matrices and solve systems of linear equations using NumPy.
The acronym NumPy stands for numeric Python. As this name suggests, this module
provides functions for numerical calculations. Besides the number of functions pro-
vided, the short runtime of the NumPy functions is particularly noteworthy. You
should always import the NumPy module using the import numpy as np import state-
ment. Assigning the np alias has become the accepted convention. NumPy forms the
basis for almost all scientific calculations and is therefore often used in combination
with the Matplotlib and SciPy modules.
np.arange(start,stop,step,dtype=None)
You don’t need to specify the data type, which is determined automatically by NumPy.
As a rule, numbers of the float type are processed. Three different float data types are
possible:
91
3 Numerical Calculations Using NumPy
The linspace() function specifies the number of elements (num) instead of the incre-
ment (step). The default value is 50. The general syntax for linspace() is:
01 #01_1dim_array.py
02 import numpy as np
03 x1=list(range(10))
04 x2=np.arange(10)
05 x3=np.arange(1,10,0.5)
06 x4=np.linspace(1,10,10)
07 x5=np.linspace(1,10,10,endpoint=False)
08 print("Python list:",type(x1) ,"\n",x1)
09 print("arange() Increment 1:",type(x2),"\n",x2)
10 print("arange() Increment 0.5:",type(x3),"\n",x3)
11 print("linspace() Increment 1:",type(x4),"\n",x4)
12 print("linspace() Increment 0.9:",type(x5),"\n",x5)
Output
Analysis
Line 03 generates the list x1 from Python function range(10). Line 08 outputs the num-
bers from 0 to 9 for x1. The preset increment is 1.
Line 04 creates an array x2 with NumPy function arange(). Line 09 also outputs the
numbers from 0 to 9 for x2. The increment of 1 is also preset.
92
3.1 NumPy Functions
Line 05 creates a NumPy array x3 with increment 0.5. The final value is not output (line
10).
In lines 06 and 07, two arrays are created via NumPy function linspace(). If the end-
point=False property is set, then the last element won’t be output, and the increment is
0.9 (line 12). The default is endpoint=True.
The NumPy functions arange() and linspace() are of type numpy.ndarray. The nd prefix
stands for multi-dimensional arrays (n-dimensional).
01 #02_runtime_comparison.py
02 import time as t
03 import numpy as np
04 #Python list
05 def version1(n):
06 t1=t.time()
07 x1=list(range(n)) #generate list
08 x2=list(range(n))
09 sum=[]
10 for i in range(n):
11 sum.append(x1[i]+x2[i])
12 return t.time() - t1
13 #NumPy arange()
14 def version2(n):
15 t1=t.time()
16 x1=np.arange(n)
17 x2=np.arange(n)
18 sum=x1+x2
19 return t.time() - t1
20 #NumPy linspace()
21 def version3(n):
22 t1=t.time()
23 x1=np.linspace(0,n,n)
24 x2=np.linspace(0,n,n)
25 sum=x1+x2
26 return t.time() - t1
27
28 nt=1000000
93
3 Numerical Calculations Using NumPy
29 runtime1=version1(nt)
30 runtime2=version2(nt)
31 runtime3=version3(nt)
32 factor1=runtime1/runtime2
33 factor2=runtime1/runtime3
34 #Output
35 print("Runtime for Python range()...:",runtime1)
36 print("Runtime for NumPy arange()..:",runtime2)
37 print("Runtime for NumPy linspace():",runtime3)
38 print("arange() is%4d times faster than range()" %factor1)
39 print("linspace() is%4d times faster than range()" %factor2)
Output
Analysis
The NumPy function arange() is about 51 times faster, and the NumPy function lin-
space() is about 71 times faster than the Python list generated by the Python function,
range(). The time measurements are only rough estimates. With each new program
start and with different hardware, the results will turn out differently.
In conclusion, for the numerical analysis of large data sets, you should use NumPy
arrays.
Matrices
Matrices are represented by NumPy arrays.
94
3.1 NumPy Functions
The array function is a member of the ndarray class, as are the NumPy functions
arange() and linspace(). To test the operations on arrays, it is convenient to automate
the creation of two-dimensional arrays. You can use the NumPy method obj.reshape()
to convert a one-dimensional array into a two-dimensional array. Listing 3.3 creates an
m×n matrix from a one-dimensional array. The program also determines the type (i.e.,
the shape) of the array with the shape property and shows how a matrix is transposed.
01 #03_2dim_array.py
02 import numpy as np
03 m=3 #lines
04 n=4 #columns
05 a=np.arange(m*n).reshape(m,n)
06 b=a.reshape(n*m,)
07 print("Type of the array",a.shape,"\n",a)
08 print("Linearize\n",b)
09 print("Transpose\n",a.T)
Output
95
3 Numerical Calculations Using NumPy
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
Analysis
In lines 03 and 04, you can change the number of lines and columns of the array.
In line 05, the NumPy method reshape(m,n) converts the one-dimensional array into a
two-dimensional array.
In line 06, the a.reshape(n*m,) method linearizes the two-dimensional array a. The
a.reshape(m,n) statement is called a method here because reshape() requires an object
to execute. The object notation a.reshape(m,n) can be translated into everyday lan-
guage using the phrase “create an array object with m lines and n columns from array
object a.”
In line 07, the shape property determines the type of array a.
In line 09, array a is transposed via a.T. You can also transpose an array using the
np.transpose(a) statement.
3.1.3 Slicing
Slicing allows you to read selected portions of elements from a two-dimensional array.
With the general syntax a[start:stop:step,start:stop:step], a subrange of an array a
defined by the parameters start,stop,step is read from the m-th line and the n-th col-
umn. The default value of step is 1. Using a[m,:] you can read the m-th line, and using
a[:,n], you can read the n-th column of the array a. Listing 3.4 shows in a 4×4 matrix
how slicing works for reading columns. The matrix is created using the NumPy method
reshape().
01 #04_slicing.py
02 import numpy as np
03 m=4 #lines
04 n=4 #columns
05 a=np.arange(m*n).reshape(m,n)
06 #Output
07 print(a)
08 print("First column\n",a[:,0])
09 print("Second column\n",a[:,1])
10 print("First line\n", a[0,:])
11 print("Second line\n", a[1,:])
12 print("a[1:3,0:2]\n", a[1:3,0:2])
96
3.1 NumPy Functions
Output
[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
First column
[ 0 4 8 12]
Second column
[ 1 5 9 13]
First line
[0 1 2 3]
Second line
[4 5 6 7]
a[1:3,0:2]
[[4 5]
[8 9]]
Analysis
In lines 03 and 04, the number of lines and columns for the matrix created in 05 is
defined. In line 05, the NumPy method reshape(m,n) creates a 4×4 matrix from a
sequence of 16 integers.
In lines 08 to 11, individual columns and lines are read. Note that the count starts at
index 0.
In line 12, a range of the matrix is read.
01 #05_numpy_functions.py
02 import numpy as np
03 #import math
04 x=np.arange(-3,4,1)
05 #y1=math.sin(x)
06 y1=np.sin(x)
07 y2=np.exp(x)
08 y3=np.sinh(x)
09 y4=np.cosh(x)
10 y5=np.hypot(3,4)#diagonal
97
3 Numerical Calculations Using NumPy
11 y1,y2,y3,y4=np.round((y1,y2,y3,y4),decimals=3)
12 #Output
13 print("x values:\n",x)
14 print("sin function:\n",y1)
15 print("e-function:\n",y2)
16 print("sinh function:\n",y3)
17 print("cosh function:\n",y4)
18 print("Hypotenuse:",y5)
Output
x values
[-3 -2 -1 0 1 2 3]
sin function:
[-0.141 -0.909 -0.841 0. 0.841 0.909 0.141]
e-function:
[0.05 0.135 0.368 1. 2.718 7.389 20.086]
sinh function
[-10.018 -3.627 -1.175 0. 1.175 3.627 10.018]
cosh function
[10.068 3.762 1.543 1. 1.543 3.762 10.068]
Hypotenuse: 5.0
Analysis
The program calculates value tables for a sin, an e, a sinh, and a cosh function. The value
range is between -3 and +3 (line 04). The increment is 1. The fact that the upper limit for
the x values breaks off at +3, although 4 was specified as the upper limit in the source
code, may be confusing at first. The NumPy documentation provides the explanation:
For both integer and non-integer increments, the interval end of the value range is not
included. Thus, x < 4 is always valid. In exceptional cases, rounding effects may cause
the end of the interval to be included.
NumPy functions can be accessed via the dot operator with the np alias. If the com-
ments in lines 03 and 05 are removed, the following error message appears after the
program start:
y1=math.sin(x)
TypeError: only size-1 arrays can be converted to Python scalars
This message means that value tables for the mathematical functions from the math
module may only be created with loop constructs. For each new calculation of a func-
tion value for math.sin(x), the loop must be run again. If, on the other hand, value
98
3.1 NumPy Functions
tables are created using the NumPy functions arange() or linspace() and the pre-
defined mathematical functions from the NumPy module, then a for or while loop is
no longer needed. All mathematical NumPy functions return an ndarray. Each discrete
value of the variables (i.e., y1 to y4) can thus be accessed via the index operator. The
expenditures on lines 14 to 17 demonstrate this result: For each x argument, the corre-
sponding function value is output.
The statement in line 11 is interesting. At this point, the NumPy function round()
rounds the outputs for all four function values to three digits by passing it a tuple of
four elements. The round() function returns a tuple with four elements as well.
01 #06_numpy_statistics.py
02 import numpy as np
03 lines=5
04 columns=10
05 np.random.seed(1)
06 x=np.random.normal(8,4,size=(lines,columns))
07 mw=np.mean(x)
08 md=np.median(x)
09 v=np.var(x)
10 staw=np.std(x)
11 minimum=np.amin(x)
12 maximum=np.amax(x)
13 min_index=np.where(x==np.amin(x))
14 max_index=np.where(x==np.amax(x))
15 #min_index=np.argmin(x)
16 #max_index=np.argmax(x)
17 #Output
18 print("Random numbers\n",np.round(x,decimals=2),"\n")
19 print("Smallest number...........:",minimum)
20 print("Largest number............:",maximum)
21 print("Index of the smallest number:",min_index)
22 print("Index of the largest number..:",max_index)
23 print("Mean....................:",mw)
99
3 Numerical Calculations Using NumPy
24 print("Median..................:",md)
25 print("Variance................:",v)
26 print("Standard deviation......:",staw)
27 print("Type of x:",type(x))
28 print("Type of mw:",type(mw))
Output
Random numbers
[[14.5 5.55 5.89 3.71 11.46 -1.21 14.98 4.96 9.28 7. ]
[13.85 -0.24 6.71 6.46 12.54 3.6 7.31 4.49 8.17 10.33]
[ 3.6 12.58 11.61 10.01 11.6 5.27 7.51 4.26 6.93 10.12]
[ 5.23 6.41 5.25 4.62 5.32 7.95 3.53 8.94 14.64 10.97]
[ 7.23 4.45 5.01 14.77 8.2 5.45 8.76 16.4 8.48 10.47]]
Analysis
In line 06, the NumPy function random.normal(8,4,size=(lines,columns)) generates 50
normally distributed random numbers as a matrix with five lines and ten columns.
This function expects the center of the distribution as a first argument, a rough specifi-
cation for the spread of the random numbers to be generated as a second argument
and a tuple for the number of lines and columns as the third argument.
To ensure that the same random numbers are generated each time the program is
restarted, line 05 contains the random.seed() function. If new random numbers should
also be generated at each new program start, this function must be commented out or
deleted.
Lines 07 to 10 calculate the desired statistical measures: the mean mw, median md, vari-
ance v, and standard deviation staw.
An interesting task is to find the array index for the smallest and the largest random
number in lines 13 and 14. In line 13, the where(x==np.amin(x)) function determines the
position in the array with the smallest random number: [0.5] (output in line 21). The
100
3.2 Vectors
same applies to the determination of the index of the largest random number. The pro-
gram outputs the index [4.7] for this number in line 22. A check against the random
numbers output in line 18 confirms the results. A simpler way to determine the loca-
tion in the array where the smallest or largest random number is located is to use the
functions in lines 15 and 16, which have been commented out.
All calculated statistical measures are of type Float64 (line 28). So, you have double pre-
cision with 52-bit mantissa and 11-bit exponent.
3.2 Vectors
Vectors (Latin vector; English carrier, driver) are physical quantities that, in contrast to
scalar quantities, are characterized by a direction in addition to a magnitude. Examples
of directed magnitudes include velocities, forces, or field strengths. In physics and
mathematics, vectors are graphically represented as arrows, as shown in Figure 3.1.
As shown in Figure 3.1, vectors can be shifted arbitrarily in the plane, provided that
their magnitudes and directions do not change. The same statement is true in three-
dimensional space. The vectors shown have the same x and y components of x = 6 and
y = 4. In mathematics, the formulation (6,4) is common. The angle is about 33.7° in each
case.
101
3 Numerical Calculations Using NumPy
If you add up vector F1 = (–6,4), vector F2 = (4,–8), and vector F3 = (4,2), you get the result-
ing vector Fres = (2,–2). In the language of mathematics:
Vectors can be created from tuples or lists using the array function. Listing 3.7 shows
the implementation of a vector addition via tuples.
01 #07_vectoraddition.py
02 import numpy as np
03 F1=-6,4
04 F2=4,-8
05 F3=4,2
06 F1=np.array(F1)
07 F2=np.array(F2)
08 F3=np.array(F3)
09 Fres=F1+F2+F3
10 F_1=np.sqrt(F1[0]**2+F1[1]**2)
11 F_2=np.sqrt(F2[0]**2+F2[1]**2)
12 F_3=np.sqrt(F3[0]**2+F3[1]**2)
13 F_res=np.sqrt(Fres[0]**2+Fres[1]**2)
14 angle=np.arctan(Fres[0]/Fres[1])
15 angle=np.degrees(angle)
16 #Output
17 print("Coordinates of F1:",F1)
18 print("Coordinates of F2:",F2)
102
3.2 Vectors
19 print("Coordinates of F3:",F3)
20 print("Magnitude of F1 :",F_1)
21 print("Magnitude of F2 :",F_2)
22 print("Magnitude of F3 :",F_3)
23 print("Resulting force :",Fres)
24 print("Magnitude of Fres:",F_res)
25 print("Angle of Fres :",angle,"°")
Output
Analysis
In lines 03 to 05, the x-y components of the three forces are passed as tuples to vari-
ables F1 to F3. The statements in lines 06 to 08 each create a one-dimensional NumPy
array from the force components.
In line 09, the vector addition takes place. The forces are added element by element.
The internal processes remain hidden from the user. Internally, the program calculates
Fres[0]=F1[0]+F2[0]+F3[0] and Fres[1]=F1[1]+F2[1]+F3[1]. Line 23 outputs the result.
The program calculates the magnitudes of the three forces using the Pythagorean the-
orem (lines 10 to 12). Line 14 calculates the angle of the resulting force using NumPy
function arctan(Fres[0]/Fres[1]). The degrees(angle) NumPy function in line 15
ensures that the angle is converted to degrees.
The output of the program in lines 17 to 25 can be easily checked using Figure 3.2. The
units have been deliberately omitted.
103
3 Numerical Calculations Using NumPy
From this definition, the coordinate form of the scalar product can be derived using the
cosine theorem:
The scalar product is calculated as the sum of the products of force and displacement
components.
In abbreviated notation, the following applies to the definition of the scalar product:
The magnitude of the force is calculated from the square root of the scalar product of
the force vector with itself:
And the magnitude of the displacement is calculated from the square root of the scalar
product of the displacement vector with itself:
For the angle between force vector and path vector, the following applies:
For the specified components of the force and path vectors, a work of 5 Nm is per-
formed. The NumPy function dot(F,s) calculates the scalar product. Listing 3.8 shows
how the mechanical work is calculated from the scalar product of the force and path
vectors.
01 #08_scalarproduct.py
02 import numpy as np
03 F=2,7,-3
04 s=-2,3,4
05 F_B=np.sqrt(np.dot(F,F))
06 s_B=np.sqrt(np.dot(s,s))
07 cos_Fs=np.dot(F,s)/(F_B*s_B)
08 angle=np.degrees(np.arccos(cos_Fs))
09 W=np.dot(F,s)
10 #Output
104
3.2 Vectors
Output
Analysis
In lines 03 and 04, three force and three path components are passed as tuples to the F
and s variables, respectively. The first element of a tuple contains the x-component, the
second the y-component, and the third the z-component of the force (F) and path (s)
vectors.
Lines 05 and 06 calculate the magnitudes of the vectors with the scalar product of the
NumPy function dot(F,F) and dot(s,s), respectively. The angle between the force vec-
tor and the path vector is also calculated using the dot function (line 07). Line 09 calcu-
lates the mechanical work W with the scalar product W=np.dot(F,s). The program
internally calculates the mechanical work by the element-wise multiplication as
required by the definition of the scalar product: W=F[0]s[0]+F[1]s[1]+F[2]s[2].
Line 14 outputs the mechanical work W performed on a mass point shift in space. The
result of 5 Nm matches the previously determined value.
From this definition, the coordinate form of the cross product can be derived:
105
3 Numerical Calculations Using NumPy
Listing 3.9 calculates the torque from the force and the lever vector in three-
dimensional space with the NumPy function cross(F,l).
01 #09_crossproduct.py
02 import numpy as np
03 F=2,7,-3
04 l=-2,3,4
05 F_B=np.sqrt(np.dot(F,F))
06 l_B=np.sqrt(np.dot(l,l))
07 cos_Fl=np.dot(F,l)/(F_B*l_B)
08 angle=np.degrees(np.arccos(cos_Fl))
09 M=np.cross(F,l)
10 M_B=np.sqrt(np.dot(M,M))
11 #Output
12 print("Magnitude of force :",F_B,"N")
13 print("Magnitude of lever arm:",l_B,"m")
14 print("Angle between F and l : ",angle,"°")
15 print("Torque M :",M,"Nm")
16 print("Magnitude of torque :",M_B,"Nm")
Output
Analysis
The force vector F and the vector of the lever arm l are again defined as tuples in lines
03 and 04.
In line 09, the program calculates the cross product using the NumPy function, M=
np.cross(F,l). The result is again a vector [37 -2 20] Nm (output in line 15). The magni-
tude of 42.1 Nm of the torque corresponds to the area of the parallelogram spanned by
the force vector F and the vector of the lever arm l.
106
3.2 Vectors
Listing 3.10 calculates the volume of a cuboid using the triple product dot(c,
np.cross(a,b)).
01 #10_tripleproduct.py
02 import numpy as np
03 a=2,0,0
04 b=0,3,0
05 c=0,0,4
06 a_B=np.sqrt(np.dot(a,a))
07 b_B=np.sqrt(np.dot(b,b))
08 c_B=np.sqrt(np.dot(c,c))
09 V=np.dot(c,np.cross(a,b))
10 #Output
11 print("Magnitude of a:",a_B)
12 print("Magnitude of b:",b_B)
13 print("Magnitude of c:",c_B)
14 print("Triple product:",V)
Output
Magnitude of a: 2.0
Magnitude of b: 3.0
Magnitude of c: 4.0
Triple product: 24
Analysis
The components of the three vectors a, b, and c were chosen to form a cuboid with the
following sides: a=2, b=3 and c=4.
Line 09 calculates the triple product of NumPy functions dot() and cross(). The dot
function is passed the variable c for the height of the box and the cross(a,b) function
for the calculation of the base area as arguments.
The output in line 14 returns the correct result of 24 space units.
107
3 Numerical Calculations Using NumPy
Listing 3.11 calculates the dyadic product for the given matrices.
01 #11_outer.py
02 import numpy as np
03 A=np.array([[1,2,3]])
04 B=np.array([[4],[5],[6]])
05 C=np.outer(A,B)
06 print("Matrix A")
07 print(A)
08 print("Matrix B")
09 print(B)
10 print("Dyadic product")
11 print(C)
Output
Matrix A
[[1 2 3]]
Matrix B
[[4]
[5]
[6]]
Dyadic product
[[ 4 5 6]
[ 8 10 12]
[12 15 18]]
Analysis
In line 03, a row vector A is defined and in line 04, a column vector B is defined. The
dyadic product is calculated by NumPy function outer(A,B) in line 05. The result
matches the manually calculated value.
108
3.3 Matrix Multiplication
A simple example will demonstrate the matrix multiplication using a schema (see
Table 3.1). The following two matrices are to be multiplied:
The first matrix is entered in the first and second columns and the third and fourth
rows of a table. The second matrix is entered in the third and fourth columns and in the
first and second rows of the table.
5 6
7 8
1 2 1 · 5 + 2 · 7 = 19 1 · 6 + 2 · 8 = 22
3 4 3 · 5 + 4 · 7 = 43 3 · 6 + 4 · 8 = 50
The first row of the first matrix is multiplied element by element by the first column of
the second matrix. The two products are added up. The second row of the first matrix is
multiplied by the first column of the second matrix. The two products are added up
again. The second column is calculated according to the same schema.
NumPy provides the array([[a11,a12],[a21,a22]]) function for generating the matri-
ces. You can adjust the number of rows and columns as needed.
The easiest way to perform matrix multiplication is to use the infix operator @. Alterna-
tives are matmul(A,B) or multi_dot([A,B,C,...]).
Listing 3.12 shows how you can perform matrix multiplication using the numbers from
our earlier example.
01 #12_mulmatrix1.py
02 import numpy as np
109
3 Numerical Calculations Using NumPy
Output
<class 'numpy.ndarray'>
Matrix A
[[1 2]
[3 4]]
Matrix B
[[5 6]
[7 8]]
Product A*B
[[19 22]
[43 50]]
Product B*A
[[23 34]
[31 46]]
Analysis
Lines 03 to 06 define matrices with two rows and two columns each. The values of the
individual coefficients are stored in variables A and B.
Line 07 performs the matrix multiplication C=A@B, while line 08 performs the multipli-
cation with an interchanged order of factors D=B@A.
The product for C is correctly output line 13 and matches the value that was manually
calculated in Table 3.1. The result from line 14, on the other hand, deviates from this.
This result is also correct, as you can easily check by recalculation. (You thus learn from
this that the commutative law does not apply to a matrix product.)
110
3.3 Matrix Multiplication
I1 = 7 A 2A R2 = 2 Ω I2 = 1 A
5A 4V 1A
R1 R3
U1 = 5V U2 = 1V
1Ω 1Ω
Any passive two-port network can be described in general terms by a linear system of
equations with a matrix of four parameters and the column vectors from voltages or
currents.
For the transverse resistors R1 and R3, the following A parameters can be determined
from the circuit shown in Figure 3.3:
111
3 Numerical Calculations Using NumPy
To obtain the system matrix of the entire circuit shown in Figure 3.3, you need to mul-
tiply all three partial matrices with each other.
Listing 3.13 performs the matrix multiplication from the three partial matrices for the
π-substitution circuit. You can of course change the values of the resistors for further
testing.
01 #13_mulmatrix2.py
02 import numpy as np
03 R1=1
04 R2=2
05 R3=1
06 U2=1
07 I2=1
08 A1q=np.array([[1, 0],
09 [1/R1, 1]])
10 Al=np.array([[1, R2],
11 [0, 1]])
12 A2q=np.array([[1, 0],
13 [1/R3, 1]])
14 A=A1q@Al@A2q
15 b=np.array([[U2],[I2]])
16 E=A@b
17 U1,I1=E[0,0],E[1,0]
18 print("Chain shape A\n",A)
19 print("Input variables\n",E)
20 print("Input voltage U1=%3.2f V" %U1)
21 print("Input current I1=%3.2f A" %I1)
Output
Chain shape A
[[3. 2.]
[4. 3.]]
Input variables
[[5.]
[7.]]
Input voltage U1=5.00 V
Input current I1=7.00 A
Analysis
The values for the output voltage U2; the output current I2; and the three resistors R1, R2,
and R3 were taken from the specifications of the circuit shown in Figure 3.3.
112
3.3 Matrix Multiplication
In lines 08 to 13, the three partial matrices A1q, Al, and A2q are defined for the transverse
resistances R1 and R3 and the series resistance R2. Line 14 performs the matrix multipli-
cation A=A1q@Al@A2q. Pay attention to the correct sequence of factors. As shown earlier
in Listing 3.12, the commutative law does not apply to matrix multiplication! Changing
the order of the partial matrices would also represent a different circuit structure.
Line 15 creates the column vector b=np.array([[U2],[I2]]) for the output variables. In
line 16, system matrix A is multiplied by column vector b. The result of the matrix mul-
tiplication is assigned to column vector E.
The input voltage must be 5 V so that a voltage of U2 = 1 V is present at the output of the
π-substitute circuit. A current of I1 = 7 A must flow at the input of the circuit so that a
current of I2 = 1 A flows at the output. You can check the results using the circuit shown
in Figure 3.3.
For the transverse resistors R1 and R3, the B parameters can be determined from the cir-
cuit shown in as follows:
In general, the B parameters can be determined from the inverse matrix of A. The fol-
lowing applies:
Listing 3.14 calculates the output voltage U2 and output current I2 of a π-substitute cir-
cuit with the B catenary parameters.
01 #14_mulmatrix3.py
02 import numpy as np
03 R1=1
04 R2=2
05 R3=1
06 U1=5
07 I1=7
113
3 Numerical Calculations Using NumPy
08 B1q=np.array([[1, 0],
09 [-1/R1, 1]])
10 B2l=np.array([[1, -R2],
11 [0, 1]])
12 B3q=np.array([[1, 0],
13 [-1/R3, 1]])
14 B=B1q@B2l@B3q
15 b=np.array([[U1],[I1]])
16 E=B@b
17 U2,I2=E[0,0],E[1,0]
18 print("Chain shape B\n",B)
19 print("Output variables\n",E)
20 print("Output voltage U2=%3.2fV" %U2)
21 print("Output current I2=%3.2fA" %I2)
Output
Chain shape B
[[ 3. -2.]
[-4. 3.]]
Output variables
[[1.]
[1.]]
Output voltage U2=1.00V
Output current I2=1.00A
Analysis
Basically, the program is structured in the same way as shown in Listing 3.13, except
that the parameters in the secondary diagonal have a negative sign.
The result for the output voltage U2 and the output current I2 matches the values deter-
mined using Kirchhoff’s circuit laws in the circuit shown in .
3.3.2 Usage Example: Calculating the Energy of a Rotating Rigid Body in Space
The next example shows the multiplication of the row vector of an angular velocity
with an inertia tensor I (3×3 matrix) and the column vector of an angular velocity.
For the rotational energy, the following applies:
114
3.3 Matrix Multiplication
The superscript T means that the vector of angular velocity must be transposed, that is,
the column vector is converted into a row vector. In component notation, you obtain
the following:
The product of the mass m and the matrix with the location coordinates is referred to
as the inertia tensor. If you perform the matrix multiplication, you’ll get the rotational
energy, which is stored in the rotating body.
For a case where mass m with radius x = r rotates around the z-axis in the x-y-plane, the
following applies in a simplified way:
Listing 3.15 calculates the rotational energy of a point mass of mass m = 6 kg rotating in
space around the z-axis with an angular velocity of .
01 #15_mulmatrix4.py
02 import numpy as np
03 x=1 #distance in m
04 y=0
05 z=0
06 wx=0
07 wy=0
08 wz=1 #angular velocity
09 m=6 #mass in kg
10 w_Z=np.array([wx,wy,wz])
11 I=m*np.array([[y**2+z**2, -x*y, -x*z],
12 [-x*y, x**2+z**2, -y*z],
13 [-x*z, -y*z, x**2+y**2]])
14 w_S=np.array([[wx],
15 [wy],
16 [wz]])
17 #Calculation of the rotational energy
18 Erot=0.5*w_Z@I@w_S
19 #Erot=0.5*w_S.T@I@w_S
20 Er=Erot[0]
115
3 Numerical Calculations Using NumPy
21 #Output
22 print("Rotational energy: %3.2f joules" %Er)
Output
Analysis
The rotational energy is calculated according to the rule: “row vector multiplied by 3×3
matrix multiplied by column vector.” Following this sequence is mandatory because
the commutative law does not apply with matrices! Line 10 contains the row vector of
angular velocity, lines 11 to 13 contain the 3×3 matrix of the inertia tensor, and lines 14
to 16 contain the column vector of the angular velocity.
The statement in line 18 performs the matrix multiplication and stores the result in the
Erot variable. Alternatively, you can comment out lines 10 and 18 and remove the com-
ment in line 19. In this line, the column vector from line 14 is transposed into a row vec-
tor using the T property.
116
3.4 Linear Systems of Equations
To determine solution vector x, the inverse matrix A-1 must be formed and multiplied
by the inhomogeneity vector b:
Based a simple example, let’s walk you through the solution of a simple system of equa-
tions with three unknowns:
Listing 3.16 solves a linear system of equations for three unknowns using NumPy func-
tion solve(A,b).
01 #16_equation_system.py
02 import numpy as np
03 from numpy.linalg import solve
04 #coefficient matrix
05 A = np.array([[1, 1, 1],
06 [2, -2, 3],
07 [3, -4, 2]])
08 #inhomogeneity vector
09 b = np.array([6, 7, 1])
10 #solution
11 solution=solve(A,b)
12 #Output
13 print("Solution of a linear system of equations")
14 print("Coefficient matrix\n",A)
15 print("Inhomogeneity vector\n",b)
16 print("Solution:\n",solution)
Output
117
3 Numerical Calculations Using NumPy
Analysis
Line 03 imports the linalg submodule with the solve function.
In lines 05 to 07, the coefficient matrix A of the equation system is generated as a two-
dimensional NumPy array.
In line 09, the inhomogeneity vector array([6,7,1]) is assigned to variable b.
In line 11, NumPy function solve(A,b) calculates the solution of the linear system of
equations. The solution vector is stored in variable solution.
The solution vector contains floats although the coefficient matrix and the inhomoge-
neity vector consist of integers. If you use
print(type(A[0,0]))
print(type(b[0]))
print(type(solution[0]))
<class 'numpy.int64'>
<class 'numpy.int64'>
<class 'numpy.float64'>
When a mathematical operation on arrays produces floats, then all the integers in the
array are converted to floats. If you declare a single arbitrary integer of an array as a
float (e.g., 2. instead of 2), then all other elements of the array are automatically con-
verted to floats.
Using the network shown in Figure 3.4 as an example, try reading a system of equations
directly from the circuit using mesh analysis.
118
3.4 Linear Systems of Equations
Z1 Z3
U1 I1 Z2 I2 U2
Z4 Z5
U3 U4
I3 Z7 I4
Z6 Z8
The coefficient matrix is entered in Table 3.2. This table consists of four rows and five
columns. The fifth column is for the vector of source voltages.
I1 I2 I3 I4 U
1 Z1 + Z2 + Z4 –Z2 –Z4 0 U1
3 –Z4 0 Z4 + Z6 + Z7 –Z7 U3
The sums of the impedances from the individual meshes are shown along the main
diagonal. The secondary diagonals track the common impedances of two meshes. If
two meshes have no common impedances, a 0 is entered in the table. All coefficients of
the secondary diagonals have a negative sign and are reflected on the main diagonal of
the impedance matrix. As shown in Listing 3.17, the coefficient matrix of rows 1 to 4 and
columns 1 to 4 from Table 3.2 is transferred directly into a NumPy array.
119
3 Numerical Calculations Using NumPy
01 #17_mesh4c.py
02 import numpy as np
03 import numpy.linalg
04 U1=230
05 U2=-230
06 U3=230
07 U4=-230
08 Z1=1+2j
09 Z2=2-4j
10 Z3=3+4j
11 Z4=2+5j
12 Z5=1+5j
13 Z6=2+5j
14 Z7=4-5j
15 Z8=1+5j
16 Z=np.array([[Z1+Z2+Z4,-Z2,-Z4, 0],
17 [-Z2,Z2+Z3+Z5, 0,-Z5],
18 [-Z4, 0,Z4+Z6+Z7,-Z7],
19 [0,-Z5,-Z7,Z5+Z7+Z8]])
20 U=np.array([U1,-U2,U3,-U4])
21 current=np.linalg.solve(Z,U) #numpy.ndarray
22 for k, I in enumerate(current,start=1):
23 print("I%d = (%0.2f, %0.2fj)A" %(k,I.real,I.imag))
Output
I1 = (33.16, -52.04j)A
I2 = (19.63, -49.35j)A
I3 = (20.09, -41.98j)A
I4 = (18.09, -51.66j)A
Analysis
Lines 04 to 15 contain the values for the voltages and impedances of the network. The
coefficient matrix Z is defined in lines 16 to 19. The rows and columns of the matrix are
arranged in a NumPy array([[],...,[]]) according to Table 3.2. For the calculation of
the solution vector I, the inhomogeneity vector U must still be defined in line 20. The
solution is calculated using NumPy function linalg.solve(Z,U) in line 21. The solution
vector current contains the four mesh currents I[0], I[1], I[2], and I[3].
The Python function enumerate(current) allows for the output of the individual mesh
currents within a for loop (line 23). Each individual mesh current I is marked with the
index k. With each iteration, the enumerate(current) function returns a tuple contain-
ing the index k and the corresponding element I of the current array.
120
3.5 Project Task: Lightning Protection System
G G
h h
I G
q ı
1 2
G G
b b
G
ı 4
3
G G
h h
The voltage drops between the nodes are calculated using the node potential method.
You can read the system of equations directly from the circuit and represent it as a
matrix:
121
3 Numerical Calculations Using NumPy
The following applies to the conductance of the lightning and down conductors:
We can use Ohm’s law to calculate the currents in the lightning and down conductors
from the potential differences of the node voltages and the conductances of the light-
ning and down conductors.
Listing 3.18 solves the system of equations for the four unknown nodal voltages using
NumPy function U=linalg.solve(G,I).
01 #18_project_lightning_protection.py
02 import numpy as np
03 Iq=1e5 #current of the lightning in A
04 g=10 #conductance for steel S*m/mm^2
05 A=50 #conductor cross section in mm^2
06 l=10 #length in m
07 b=5 #width in m
08 h=3 #height in m
09 Gh=g*A/h #conductance for height in S
10 Gl=g*A/l #conductance for length in S
11 Gb=g*A/b #conductance for width in S
12 G=np.array([[Gb+Gh+Gl, -Gl, -Gb, 0],
13 [-Gl, Gb+Gh+Gl, 0,-Gb],
14 [-Gb, 0, Gb+Gh+Gl,-Gl],
15 [ 0,-Gb,-Gl, Gb+Gh+Gl]])
16 I=np.array([Iq,0,0,0])
17 U=np.linalg.solve(G,I)
18 I10=U[0]*Gh
19 I20=U[1]*Gh
20 I30=U[2]*Gh
21 I40=U[3]*Gh
22 I12=(U[0]-U[1])*Gl
23 I13=(U[0]-U[2])*Gb
24 I34=(U[2]-U[3])*Gl
25 I24=(U[1]-U[3])*Gb
26 print("--Voltage drops of down conductors--")
27 print("Voltage U10: %3.2f V" %U[0])
28 print("Voltage U20: %3.2f V" %U[1])
29 print("Voltage U30: %3.2f V" %U[2])
30 print("Voltage U40: %3.2f V" %U[3])
31 print("--Currents in down conductors--")
32 print("Current I10: %3.2f A" %I10)
33 print("Current I20: %3.2f A" %I20)
34 print("Current I30: %3.2f A" %I30)
122
3.5 Project Task: Lightning Protection System
Output
Analysis
Line 03 specifies the peak lightning current value of 100,000 A. The cross-section of the
lightning and down conductors is usually 50 mm2 (line 05). Lines 06 to 08 define the
length, width, and height of the building in meters.
In lines 09 to 11, the conductances of the lightning and down conductors are calculated.
The coefficient matrix of the conductances is written in lines 12 to 15. In line 16, the
inhomogeneity vector specifies that lightning strikes node 1. The solution vector for
the voltage drops is calculated in line 17 using NumPy function linalg.solve(G,I) and
assigned to variable U. The calculation of the currents in the down conductors is per-
formed in lines 18 to 21. Lines 22 to 25 calculate the currents in the lightning conductors
from the potential differences.
The outputs in lines 26 to 40 show that very high currents can flow with a maximum
current density of about 1,218 A/mm2. These high current densities are still acceptable
because the current only flows for a few milliseconds.
123
3 Numerical Calculations Using NumPy
3.6 Tasks
1. Calculate the volume of a parallelepiped using a determinant and the dot(cross(a,
b),c) function.
2. A catenary circuit is composed of three voltage dividers (longitudinal link R1, cross
link R2). All resistors have the same value of 1Ω. The output voltage is U2 = 1 V. Using
the catenary parameter method, calculate the input voltage U1 and the input current
I1.
3. Calculate the dyadic product for:
4. The expanded coefficient matrix of a linear system of equations is given as the fol-
lowing:
Solve this system of equations using NumPy function solve(). The coefficient
matrix and the inhomogeneity vector should be determined by means of slicing
from the extended coefficient matrix.
5. A linear system of equations with a large number of unknowns (50 to 1000) is to be
solved using NumPy function solve(). Generate the coefficient matrix and the inho-
mogeneity vector using NumPy function random.normal(). Test the limits of solve()
by gradually increasing the number of unknowns.
6. Calculate all the mesh currents for the catenary circuit from Task 2 using the mesh
analysis method. The input voltage is 13 V.
7. Calculate all node voltages for the catenary circuit from Task 2 using the node poten-
tial method. The input current has a value of 8 A.
124
Chapter 4
Function Plots and Animations
Using Matplotlib
In this chapter, you’ll learn how to use the Matplotlib module to display
and animate mathematical functions, vectors, and geometric figures in
different variations.
Matplotlib is a program library for plotting mathematical functions and geometric fig-
ures. With just a few statements, you can easily create meaningful diagrams for scien-
tific papers and publications.
The matplotlib module is usually imported together with the pyplot submodule. The
pyplot submodule serves as an interface, specifically an application programming
interface (API), to the matplotlib module. Matplotlib contains a collection of functions
similar to the functionality of MATLAB. Matplotlib methods create drawing areas for
diagrams (plots), draw lines or points in a predefined drawing area, specify line styles,
and provide numerous options for labels and the scaling of coordinate axes. Several
mathematical functions can be represented in one plot or in different subplots. Exten-
sive design options for labels (legends) of the individual function plots support the
reader in finding their way around.
The module can include using the import matplotlib.pyplot as plt statement. The plt
alias is commonly accepted as a convention.
125
4 Function Plots and Animations Using Matplotlib
01 #01_plot_loop.py
02 import matplotlib.pyplot as plt
03 lx,ly = [],[]
04 for x in range(11):
05 y=x**2
06 lx.append(x)
07 ly.append(y)
08 plt.plot(lx,ly)
09 plt.show()
Output
Figure 4.1 shows the output of the parabola whose function plot was programmed with
a loop.
126
4.1 2D Function Plots
Line 03 creates two empty lists. Inside the for loop (lines 04 to 07), the parabola is
defined in line 05. The x and y values of the parabola are stored in lines 06 and 07 in
lists lx and ly. In line 08, the values of the x-y coordinates are prepared for the plot
using the plot(lx,ly) method. In line 09, the show() method displays the parabola on
the screen.
The approach shown in Listing 4.1 is cumbersome because you must implement a loop.
In some programming languages, such as Java, C, C++, and Delphi, a loop construct is
necessary for the creation of function plots. In Python, you can do without a loop con-
struct if you import the numpy module in addition to the matplotlib.pyplot module.
Figure
In the Matplotlib documentation, a figure is a container for the top level of all plot ele-
ments. A figure object defines the entire drawing area.
One or more axes objects can be embedded in a figure.
A figure object (fig) can be created using the fig = plt.figure() statement.
The coordinate data of a figure object cannot be changed; as shown in Figure 4.2, the
coordinates are fixed. You can use the print(fig.get_figwidth()) and print(fig.get_
figheight()) statements to output the width and height of the figure object. The
127
4 Function Plots and Animations Using Matplotlib
default values are 6.4 inches for the width and 4.8 inches for the height. If you multiply
these values by the default resolution of 100 dpi, you get a drawing area of 640×480
pixels.
The fig.set_figwidth(12) and fig.set_figheight(10) statements enable you to in-
crease the width and height of the drawing area. These values enlarge the drawing area
to 1200×1000 pixels.
Axes
The most important container element is an axis. The documentation defines axes as a
coordinate system in which one or more function plots can be displayed. Several axes
objects (subplots) can be embedded in one figure object.
The following statement allows you to change the coordinates of the axes objects:
fig.subplots_adjust(left=0.15,bottom=0.15,right=0.7,top=0.8)
For the arguments, you can only use values between 0 and 1. Usually, you don’t need to
care about the coordinate data of an axis because the set default values are pretty con-
venient.
The frame lines (axes) of an axes object are referred to as spines in the Matplotlib docu-
mentation. You can use the methods of the axes class to label coordinate axes, insert
legends and any text into a plot, and give a plot a meaningful title. LaTeX notation even
enables you to represent complicated mathematical expressions, such as formulas,
Greek letters, and operators.
A coordinate system can be created using the ax=fig.add_subplot() or ax=fig.sub-
plots() statements.
You can also create a figure and an axes object using a single statement: fig,ax =
fig.subplots(). The subplots() method returns the fig,ax tuple. The fig and ax identi-
fiers are from the Matplotlib documentation and have now become accepted as a con-
vention.
The following console program enables you to display the default coordinate data of
the axes object:
The following statement allows you to hide the upper and right spines:
ax.spines[['top', 'right']].set_visible(False)
128
4.1 2D Function Plots
Method Description
add_subplot(r,c,n) Creates subplots with r rows and c columns. The number n indi-
cates the number of subplots. If you enter no parameters at all,
only one plot will be created.
savefig("name.png") Saves a graphic in PNG format. The file extension defines the file
format. The following formats are also possible: EPS (eps), JPEG
(jpeg), JPG (jpg), PDF (pdf), PGF (pgf), PNG (png), PS (ps), RAW
(raw), RGBA (rgba), SVG (svg), SVGGZ (svgz), TIF (tif), TIFF
(tiff), and WEBP (webp).
subplots_adjust(par) Sets the position of the drawing area. The following parameters
(par) are possible: left, right, bottom, and top.
Method Description
axis([x1,x2,y1,y2]) Sets the range of values for a function plot within an axes
object.
plot(x,f(x), …) Internally generates a value table for a function plot. The show
method displays the function plot on the screen.
129
4 Function Plots and Animations Using Matplotlib
Method Description
set_xticks([0,1,2, …]) Sets the scaling of the x-axis. If the list does not contain any
entries, the display of the scaling will be suppressed.
A minimal, object-oriented version of a function plot thus consists of only seven pro-
gram lines.
Listing 4.2 shows a program in the object-oriented style for the function plot of the
parabola defined earlier in Listing 4.1.
01 #02_plotnumpy.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 x=np.linspace(0,10,10)
05 y=x**2
06 fig, ax = plt.subplots(figsize=(6.4,4.8),dpi=100)#640x480 pixels
07 ax.plot(x,y)
08 fig.show()
The program produces the same output as shown earlier in Figure 4.1.
130
4.1 2D Function Plots
Thus, you could get by with only five program lines for the representation of a simple
function plot. However, to provide richer functionality for presenting graphics, the
object-oriented style is preferred in Matplotlib programs.
The fig and ax objects do not have to be implemented as shown in line 06 by using fig,
ax=plt.subplots() within one program line. The fig and ax objects can also be created
using the figure() and fig.add_subplot() methods, as in the following examples:
131
4 Function Plots and Animations Using Matplotlib
In the following examples, I want to show in greater detail which parameters you can
use. Given the large number of options, only a small selection can be presented in this
chapter. For more details, see the Matplotlib documentation.
ax.spines[['top', 'right']].set_visible(False)
Below line 07, you can insert the fig.savefig("parabel.png") statement. The savefig
("name.fileextension",dpi=number) method saves the function plot in a selectable file
format. The file extension defines the file type. Besides the PNG file format, PDF (pdf),
EPS (eps), JPG (jpg), and SVG (svg) formats are also possible. The dpi parameter sets the
resolution. The default setting is 100. Thus, the graphic has a size of 640×480 pixels.
You can double the size of the graphic by using dpi=200.
4.1.2 Gridlines
To better read function values, a useful step is to include gridlines in the function plot.
The grid() method provides numerous design options for this feature, as shown in Lis-
ting 4.3.
01 #03_grid.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 t=np.linspace(0,20,500)
05 u=325*np.sin(2*np.pi*50*t/1000)
06 fig, ax = plt.subplots()
132
4.1 2D Function Plots
07 ax.plot(t,u,linewidth=2)
08 ax.grid(color='black',linestyle='solid',lw=0.5)
09 #ax.grid(color='black',ls='dashed',lw=0.5)
10 #ax.grid(color='black',ls='dotted',lw=0.5)
11 #ax.grid(color='black',ls='dashdot',lw=0.5)
12 #ax.grid(True)
13 plt.show()
Output
An example function plot with an integrated grid is shown in Figure 4.3.
Analysis
The program represents the course u(t) of a 50 Hz alternating voltage. The linewidth=2
parameter in line 07 sets the line width of the function plot. The abbreviated notation
lw=2 has the same effect.
Lines 08 to 12 show the different styles of possible gridlines. By removing the com-
ments, you can test their appearance. The simplest way to display gridlines in function
plots is shown by line 12, which has been commented out. The linestyle property can
also be replaced by the abbreviated notation ls.
The fig object is not needed in this program. But if you leave it out, an error message
will be displayed after the program starts. You can also use this object to save the func-
tion plot as a file by using fig.savefig('name.fileformat').
For the syntax in line 06, one alternative is to create the fig and ax objects separately
using fig=plt.figure() or ax=fig.add_subplot().
133
4 Function Plots and Animations Using Matplotlib
4.1.3 Labels
For the labels of function plots, Matplotlib provides the legend() and annotate() meth-
ods. You can use the set() method to label the x and y axes. In addition, this method
can also include the title of a plot. The set_title(), set_xlabel, and set_ylabel() meth-
ods allow you to implement these specifications separately.
01 #04_labels_legend.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 t=np.arange(0,20,0.001)
05 Ueff=[230,230]
06 u=325*np.sin(2*np.pi*50*t*1e-3)
07 fig, ax = plt.subplots()
08 ax.plot(t,u,'b',lw=2,label='Instantaneous value: u(t)')
09 ax.plot([0,20],Ueff,'r--',label='RMS value: 230V')
10 ax.plot(5,325,'ro',label='Peak value:325V')
11 ax.set(xlabel='t in ms',ylabel='u(t) in V',title='50 Hz AC voltage')
12 #ax.legend(loc='upper right')
13 #ax.legend(loc='lower left')
14 ax.legend(loc='best')
15 ax.grid(color='g',ls='dashed',lw='0.5')
16 plt.show()
Output
Figure 4.4 shows the integration of a legend.
134
4.1 2D Function Plots
Analysis
The Ueff=[230,230] statement in line 05 specifies the y-coordinates of a constant volt-
age. The plot() method is passed the line colors (b stands for blue) as the third parame-
ter (line 08). The fifth parameter label='Instantaneous value: u(t)' contains the label
of the legend.
The third parameter ('ro') in line 10 causes a red dot to be drawn at the point t=5ms and
u=325V. The letter r stands for the color red. The o means that a point is to be drawn.
The set() method in line 11 creates the labels for the x-axis and the y-axis as well as the
title of the plot. The ax.legend(loc='best') method searches for the optimal location to
place the legend (line 14). The commented-out lines 12 and 13 show alternatives.
You can also spread the labels across three program lines, as in the following example:
ax.set_title('50 Hz AC voltage')
ax.set_xlabel('t in ms')
ax.set_ylabel('u(t) in V')
This variant provides more options for the positioning and color design of the axis
labels.
135
4 Function Plots and Animations Using Matplotlib
01 #05_labels_functions.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 R1,R2,R3=2,4,8
05 I=0.2
06 P=1
07 I=np.linspace(0.1, 1, 100)
08 U1=R1*I
09 U2=R2*I
10 U3=R3*I
11 U=P/I
12 fig, ax = plt.subplots()
13 ax.plot(I,U1,I,U2,I,U3,lw=2,color='blue')
14 ax.plot(I,U,lw=2,color='green')
15 ax.set(xlabel='I in A',ylabel='U in V',title='Power hyperbola U=P/I')
16 ax.annotate(r'$R_1$',xy=(1,2),xytext=(+2,-3),textcoords='offset points')
17 ax.annotate(r'$R_2$',xy=(1,4),xytext=(+2,-3),textcoords='offset points')
18 ax.annotate(r'$R_3$',xy=(1,8),xytext=(+2,-3),textcoords='offset points')
19 ax.grid(True)
20 plt.show()
Output
Figure 4.5 shows how multiple functions can be represented and labeled in the function
plot.
136
4.1 2D Function Plots
Analysis
In lines 08 to 11, the functions for the resistance characteristic curves and the power
hyperbola are defined. Lines 13 and 14 generate the function plots for the resistance
characteristic curves and the power hyperbola.
The statements in lines 16 to 18 create the labels using the annotate(param1,param2,
param3,param4) method. The $R_1$ label of the resistance characteristic curve is passed
as the first parameter. The underscore in the LaTeX notation causes the index to be sub-
script. The second parameter sets the x-y coordinates of the label. The third parameter
determines the offset. The label is moved two points to the right in the x-direction and
three points down in the y-direction. The fourth parameter specifies that the displace-
ment of the label should be in terms of points.
Within the drawing area, you can also place a commenting text or even formulas by
using the text() method, such as in the following example:
ax.text(0.2,9,r'voltage $U=\frac{P}{I}$',fontsize=12)
In this example, the first number determines the x-coordinate, and the second number
determines the y-coordinate where the text should be positioned.
01 #06_linestyle.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 x=np.linspace(0,6.3,500)
05 y1=np.sin(x)
06 y3=np.sin(3*x)/3
07 y5=np.sin(5*x)/5
08 y7=np.sin(7*x)/7
09 y=y1+y3+y5+y7
10 fig, ax = plt.subplots()
11 ax.plot(x,y1,color='b',lw=2,linestyle='-') #blue
12 ax.plot(x,y3,color='r',lw=2,linestyle='--')#red
13 ax.plot(x,y5,color='m',lw=2,linestyle=':') #magenta
14 ax.plot(x,y7,color='g',lw=2,linestyle='-.')#green
15 ax.plot(x,y,color='black',lw=3)
16 ax.set_xlabel('x')
137
4 Function Plots and Animations Using Matplotlib
17 ax.set_ylabel('y')
18 ax.grid(True)
19 plt.show()
Output
The line styles that can be used to distinguish between different functions are shown in
Figure 4.6.
Analysis
Lines 11 to 14 define the line styles. For a solid line, a dash is assigned to the linestyle=
'-' property. A double hyphen (--) draws a dashed line. A colon (:) draws a dotted line.
A dash and a dot (-.) draw a dash-dot line. If no property is specified for the line style
(line 15), then a solid line will be drawn by default. The linestyle property can also be
abbreviated as ls.
You can also change the line styles and line colors using the abbreviations b-, r--, m:,
and g-.
138
4.1 2D Function Plots
01 #07_axis_scaling.py
02 import matplotlib.pyplot as plt
03 l=[0,0.02,0.1,0.2,1.15,2.2,3.25,4.3,5.4,6.4]
04 F=[5.7,7.5,7.2,7.3,8.9,10.4,11.3,12,11.4,9.3]
05 fig, ax = plt.subplots()
06 ax.plot(l, F,'ro-')
07 ax.set_xticks([0,1,2.2,3.25,4.3,5.4,6.4])
08 ax.set_yticks([0,5.7,7.5,8.9,10.4,12,9.3])
09 #ax.axis([-0.5,7,5,13])
10 ax.set_xlabel("l in mm")
11 ax.set_ylabel("F in kN")
12 plt.show()
Output
The output of the individual axis scaling from Listing 4.7 is shown in Figure 4.7.
139
4 Function Plots and Animations Using Matplotlib
Analysis
Lines 03 and 04 contain the data for the bar length and the tensile force obtained from
a tensile test. Line 06 specifies that the tensile force is displayed as a red line with red
markings of the measuring points over the bar length.
The set_xticks([]) and set_yticks([]) methods in lines 07 and 08 set the scaling for
prominent function values. The alternative in the commented-out line 09 causes
the function to be displayed on the x-axis for the value range from -0.5 to 7 and on the
y-axis for the value range from 5 to 13.
spines['top'].set_visible(False)
spines['right'].set_visible(False)
The following statements move the left and right spines to the coordinate origin:
spines['left'].set_position(("data", 0))
spines[,'bottom']].set_position(("data", 0))
01 #08_axis_style.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 #Function definition
05 def f(x):
06 #y=np.sin(np.pi*x)
07 y=x**2-4
08 return y
09 #Graphics area
10 fig, ax = plt.subplots()
11 ax.spines[['top', 'right']].set_visible(False)
12 ax.spines[['left', 'bottom']].set_position(("data", 0))
13 x = np.linspace(-5, 5, 100)
14 ax.plot(x,f(x),'r-',lw=2)
15 ax.set_xlabel('x',loc='right')
16 ax.set_ylabel('f(x)',loc='top',rotation=0)
17 plt.show()
140
4.1 2D Function Plots
Output
The result of the conversion to an axis cross is shown in Figure 4.8.
Analysis
The statement in line 11 will hide the upper and right spines.
In line 12, the left and bottom axes are moved to the coordinate origin.
To embellish the x-axis and the y-axis with arrows, you must insert the following lines
into the graphics area:
ax.plot(1,0,'>k',transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0,1,'^k',transform=ax.get_xaxis_transform(), clip_on=False)
141
4 Function Plots and Animations Using Matplotlib
Ω is the frequency normalized to 1 Hz and n is the degree of the filter. Up to the cutoff
frequency of 1 Hz, the transfer factor remains almost constant, especially when the
degree of the filter is increased.
Listing 4.9 shows how the transfer behavior A = 𝑓(Ω) of a Butterworth low-pass filter of
the first to third degree with logarithmic scale division is represented. Due to the semi-
logx() method, the x-axis gets a logarithmic scale division.
01 #09_log_axis.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 omega=np.linspace(0.1,100,1000)
05 fig, ax = plt.subplots()
06 for n in range(1,4):
07 A=1./(np.sqrt(1.+omega**(2*n)))
08 ax.semilogx(omega,A)
09 ax.set_xlabel('Frequency in Hz')
10 ax.set_ylabel('A')
11 ax.grid(True)
12 plt.show()
Output
The logarithmic scale generated using the semilogx() method is shown in Figure 4.9.
142
4.1 2D Function Plots
Analysis
The transmission behavior shown in this example clearly shows that the transmission
factor hardly changes up to the cutoff frequency of 1 Hz. The semilogx(omega,A) method
in line 08 causes the x-axis to be scaled logarithmically in the range from 0.1 Hz to 100
Hz. Between the frequencies of 0.1 to 1 Hz, 1 Hz to 10 Hz and 10 Hz to 100 Hz, the sec-
tions on the x-axis have the same length.
If you add the ax.set_xscale('log') method below line 05 and change the ax.semilogx
(omega,A) statement to ax.plot (omega,A), the x-axis will also get a logarithmic scale
division.
Polar Coordinates
In mathematics, a polar coordinate system (also called a circular coordinate system) is
a two-dimensional coordinate system in which each point is defined by the distance
from the center and an angle. Lines are represented using the plot(a1,a2,r1,r2)
method. The abbreviations a and r stand for angle and radius, respectively. Listing 4.10
demonstrates how a square and a line are represented in a polar coordinate system.
01 #10_polar_coordinates.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04
05 def theta_rad(angle1,angle2):
06 theta=[angle1,angle2]
07 return np.radians(theta)
08
09 fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
10 ax.plot(theta_rad(0,90),[1,1],'r',lw=3)
11 ax.plot(theta_rad(90,180),[1,1],'g',lw=3)
12 ax.plot(theta_rad(180,270),[1,1],'m',lw=3)
13 ax.plot(theta_rad(270,0),[1,1],'b',lw=3)
14 ax.plot(theta_rad(0,45),[0,0.6],'black',lw=3)
15 ax.grid(True)
16 plt.show()
Output
Figure 4.10 shows lines in a diagram with polar coordinates.
143
4 Function Plots and Animations Using Matplotlib
Analysis
In line 09, the subplot_kw={'projection': 'polar'} parameter in the subplots() method
specifies that the default Cartesian coordinate system is converted to a polar coordi-
nate system. The labels for the angles and radiuses are generated automatically. Using
lines 10 to 13, the plot method draws the four lines of a square.
In line 14, the following method draws a black line (radius) with polar coordinates 45°
and 0.6:
ax.plot(theta_rad(0,45),[0,0.6],'black',lw=3)
144
4.1 2D Function Plots
01 #11_color_parabola.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 x = np.linspace(0,5,100)
05 y1 = (x-3)**2
06 y2 = -(x-2)**2+8
07 fig, ax=plt.subplots()
08 ax.plot(x, y1, x, y2, color='black')
09 ax.fill_between(x,y1,y2,where=y2>=y1,facecolor='b',alpha=0.2)
10 ax.set_xlabel('x')
11 ax.set_ylabel('y')
12 plt.show()
Output
The result of the coloring between the function graphs from Listing 4.11 is shown in
Figure 4.11.
Analysis
In line 09, the following method is used to color the area between the intersections of
the two parabolas:
ax.fill_between(x,y1,y2,where=y2>=y1,facecolor='b',alpha=0.2)
145
4 Function Plots and Animations Using Matplotlib
The range of coloring is defined by the where=y2>=y1 condition. Thus, only for the range
of values in which y2 is greater than or equal to y1, the coloring should be done. The
alpha=0.2 parameter sets the transparency (translucency) of the coloring. The smaller
the value, the higher the transparency.
01 #12_color_power.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 f=50
05 URms=230
06 R=10
07 Xc=10
08 XL=0
09 Z=np.sqrt(R**2 + (XL-Xc)**2)
10 phi=np.arctan((XL-Xc)/R)
11 I=URms/Z
12 t=np.linspace(0,20,500)
13 u=np.sqrt(2)*URms*np.sin(2*np.pi*f*t*1e-3)
14 i=np.sqrt(2)*I*np.sin(2*np.pi*f*t*1e-3-phi)
15 p=u*i
16 fig, ax=plt.subplots()
17 ax.plot(t, p, color='black')
18 ax.fill_between(t,0,p,where=p >=0,facecolor='r',alpha=0.2,
label='positive portion')
19 ax.fill_between(t,0,p,where=p <=0,facecolor='g',alpha=0.2,
label='negative portion')
20 ax.set(xlabel='t in ms',ylabel='p(t) in Watt',title= 'AC power')
21 ax.legend(loc='best')
22 plt.show()
Listing 4.12 Coloring between the Function Graph and the x-Axis
Output
How helpful this coloring can be for visualizing and quickly grasping the content using
the matplotlib module is shown in Figure 4.12.
Analysis
The statement in line 18 colors the area of the power curve for the positive range where=
p>=0 in red, while line 19 colors the negative range where=p<=0 of the power curve in
green. The legend at the bottom left of the diagram identifies the two areas.
146
4.1 2D Function Plots
4.1.7 Subplots
Matplotlib provides the following method:
fig,ax = plt.subplots(rows,columns)
This method enables you to display multiple mathematical functions with different
value ranges in subplots.
This method returns the fig,ax tuple. The fig object determines the dimensions of the
entire drawing area, and the ax object lets you access the properties of the axes.
01 #13_subplot_functions.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 x = np.linspace(0, 10, 100)
05 y1=x
06 y2=5-x
07 y3=x**2
147
4 Function Plots and Animations Using Matplotlib
08 y4=1/(0.2*x+1)
09 fig, ax = plt.subplots(2, 2)
10 #1st row, 1st column
11 ax[0,0].set(ylabel='y',title='Linear function')
12 ax[0,0].plot(x,y1,'b',lw=2)#blue
13 ax[0,0].grid(True)
14 #1st row, 2nd column
15 ax[0,1].set(title='negative slope')
16 ax[0,1].plot(x,y2,'r',lw=2)#red
17 ax[0,1].grid(True)
18 #2nd row, 1st column
19 ax[1,0].set(xlabel='x',ylabel='y',title='Parabola')
20 ax[1,0].plot(x,y3,'g',lw=2)#green
21 ax[1,0].grid(True)
22 #2nd row, 2nd column
23 ax[1,1].set(xlabel='x',title='Hyperbola')
24 ax[1,1].plot(x,y4,'k',lw=2)#black
25 ax[1,1].grid(True)
26 fig.tight_layout()
27 plt.show()
Output
How the four functions are represented in four subplots is shown in Figure 4.13.
148
4.1 2D Function Plots
Analysis
In lines 05 to 08, four mathematical functions are defined. The first two parameters of
the subplots(2,2) method in line 09 specify that the diagram consists of subplots with
two rows and two columns. The first number determines the number of rows, and the
second number determines the number of columns.
The arrangement of the subplots is determined by the indexing. For example, the
index pair [0,0] specifies that the first subplot is placed in the first row and column.
The second subplot in the first row and second column is indexed with [0,1] and so on.
The tight_layout() method in line 26 is tasked with creating enough space between the
four function plots. You can change the spacing using the pad, w_pad, and h_pad param-
eters. The pad property determines the distance between the edges of the drawing area
and the edges of the subplots as a fraction of the font size. The default value of pad is
1.08. The w_pad and h_pad properties set the vertical and horizontal spacing between
subplots.
01 #14_subplot_power.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 f=50
05 URms=230
06 R=0.001
07 Xc=10
08 XL=0
09 Z= np.sqrt(R**2+(XL-Xc)**2)
10 phi=np.arctan((XL-Xc)/R)
11 I=URms/Z
12 t=np.linspace(0.0, 20, 1000)
13 u=np.sqrt(2)*URms*np.sin(2*np.pi*f*t*1e-3)
14 i=np.sqrt(2)*I*np.sin(2*np.pi*f*t*1e-3-phi)
15 p=u*i
16 fig, ax = plt.subplots(3,1)
17 #voltage
18 ax[0].plot(t, u,'b',lw=2)
19 ax[0].set_ylabel('u(t)')
20 ax[0].grid(True)
21 #current
22 ax[1].plot(t,i,'r',lw=2)
149
4 Function Plots and Animations Using Matplotlib
23 ax[1].set_ylabel('i(t)')
24 ax[1].grid(True)
25 #power
26 ax[2].plot(t,p,'g',lw=2)
27 ax[2].set(xlabel='Time in ms',ylabel='p(t)')
28 ax[2].grid(True)
29 fig.tight_layout()
30 plt.show()
Output
How the functions of the example are represented in three subplots in three rows
below each other is shown in Figure 4.14.
Analysis
In line 16, the fig and ax objects are created using the subplots(3,1) method. The two
parameters specify that the subplots are arranged in three rows and one column. The
index of ax determines the order of the subplots to be displayed. First, the course of the
voltage is shown with the line color blue, then the course of the current with the line
color red, and finally in the third line the course of the power with the line color green.
You can arrange the code for the subplots in a different order, and the described repre-
sentation will not change.
150
4.1 2D Function Plots
ax2=fig.add_axes([left,bottom,width,height])
Listing 4.15 shows how a small section of a noisy sinusoidal signal is clarified in an
embedded plot (subplot). This section is displayed enlarged (zoomed) to make the
details of the signal more visible.
01 #15_axes_axes.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 f=50 #Frequency in Ht
05 tmax=20 #Time in ms
06 t = np.linspace(0, tmax, 500)
07 ut=5*np.sin(2*np.pi*f*t*1e-3) + 0.8*np.random.randn(t.size)
08 fig=plt.figure()
09 #left, bottom, width, height
10 ax1=fig.add_axes([0.12,0.1,0.8,0.8]) #outside
11 ax2=fig.add_axes([0.6,0.6,0.28,0.25])#inside
12 #x1,x2,y1,y2
13 ax1.axis([0,tmax,-10,10])
151
4 Function Plots and Animations Using Matplotlib
14 ax2.axis([2.5,3.5,0,10])
15 #Graphics output
16 ax1.plot(t,ut,"b-")
17 ax1.set_xlabel('t in ms')
18 ax1.set_ylabel('u(t)')
19 ax2.plot(t,ut,"b-")
20 plt.show()
Output
The output is shown in Figure 4.15.
Analysis
In line 08, the figure() method creates the fig object. Using this object, you can access
the add_axes() method.
In line 11, the add_axes([0.6,0.6,0.28,0.25]) method creates the ax2 object. For the
subplot, the default values of 640×480 pixels and a resolution of 100 dpi apply: The
left-hand distance is 0.6×640 pixels = 384 pixels, and the bottom-left corner has a dis-
tance of 0.6×480 pixels = 288 pixels from the bottom edge. This corresponds to 60%
each of the total width and height of the plot area. The width has a value of 0.28×640
pixels = 179 pixels, and the height has a value of 0.25×480 pixels = 120 pixels. This rep-
resents 28% of the total width and 25% of the total height of the plot area.
In lines 13 and 14, the axis() method sets the range of values for the x and y axes. In line
14, you can influence the zoom effect by changing the x1, x2, y1, and y2 arguments.
The ax1 object in line 10 can also be created more easily via the ax1=fig.add_subplot()
statement. In this case, the default values apply. You can query them using print(ax1.
get_position()), which will return the following output:
152
4.1 2D Function Plots
01 #16_subplot_polar_sinus.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04
05 def theta_rad(angle1,angle2):
06 theta=[angle1,angle2]
07 return np.radians(theta)
08
09 angle=45
10 x=np.linspace(0, 360, 500)
11 y=np.sin(np.pi*x/180)
12 r=[np.cos(np.radians(angle)),1]
13 fig=plt.figure(figsize=(8,4))
14 #Polar coordinates
15 ax1=fig.add_subplot(1,2,1,projection='polar')
16 ax1.set_rticks([])
17 ax1.plot(theta_rad(0,angle),[0,1],'b',lw=2)
18 ax1.plot(theta_rad(0,angle),r,'b',lw=2)
19 ax1.plot(theta_rad(0,angle),[0,1],'ro')
20 ax1.grid(True)
21 #Cartesian coordinates
22 ax2=fig.add_subplot(1,2,2)
23 ax2.spines[['top', 'right']].set_visible(False)
24 ax2.spines[['bottom', 'left']].set_position(('data',0))
25 ax2.plot(x, y,'b',linewidth=2)
26 ax2.plot(angle,np.sin(np.radians(angle)),'ro')
27 ax2.plot(0,np.sin(np.radians(0)),'ro')
28 wg=[]
29 for w in range(0,361,45):
30 wg.append(w)
31 ax2.set_xticks(wg[1:])
32 ax2.set_xlabel('x in °',loc='right')
33 ax2.set_ylabel('f(x)',loc='top',rotation=0)
34 plt.show()
Output
The output of the combination of polar and Cartesian coordinates in two subplots side
by side is shown in Figure 4.16.
153
4 Function Plots and Animations Using Matplotlib
Analysis
The most important implementation details are already known from our earlier exam-
ples. In line 15, the add_subplot(1,2,1,projection='polar') method creates the ax1
object. The two subplots are displayed in one row and two columns. The fourth param-
eter (projection='polar') specifies that the first subplot is displayed with polar coordi-
nates.
In line 22, the add_subplot(1,2,2) method creates the ax2 object. This object accesses
the spines method in lines 23 and 24.
The statement in line 23 causes the upper and right-hand frame lines (spines) of the
drawing frame not to be displayed. In line 24, set_position(('data',0)) causes the bot-
tom line of the drawing frame to be moved to the origin of the coordinate system. You
can test the program by inserting a value other than 0 for the position in line 24, and
you’ll see how the x-axis shifts upward or downward.
The set_xticks() method sets the individual label of the x-axis (line 31). The degree
measure used in this context, which is not popular with mathematicians, can also be
represented as a radian using LaTeX notation, as in the following example:
ax2.set_xticks([45,90,135,180,225,270,315,360],
[r'$\frac{1}{4}\pi$',r'$\frac{1}{2}\pi$',
r'$\frac{3}{4}\pi$',r'$\pi$',r'$\frac{5}{4}\pi$',
r'$\frac{3}{2}\pi$',r'$\frac{7}{4}\pi$',r'$2\pi$'])
154
4.1 2D Function Plots
the parameter). The oblique throw and the lemniscate were chosen as examples of
parameter representation.
Oblique Throw
In an oblique throw, the time t is the parameter on which the x and y components of
the trajectory (throwing parabola) depend. The place-time law is determined by the ini-
tial velocity and by the cosine and sine of the throwing angle :
Listing 4.17 shows how you can implement parameter equations as Python source code.
The course of the trajectory of an oblique throw for a throwing angle of 45° with an ini-
tial velocity of 20 m/s is shown.
01 #17_parameter_throw.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 g=9.81 #Gravitational acceleration in m/s^2
05 v0=20 #Initial velocity in m/s
06 alpha=45 #Throwing angle in °
07 alpha=np.radians(alpha)
08 tmax=2*v0*np.sin(alpha)/g
09 t=np.linspace(0,tmax,100)
10 #Parameter equations
11 x=v0*np.cos(alpha)*t
12 y=v0*np.sin(alpha)*t-0.5*g*t**2
13 #Representation
14 fig, ax=plt.subplots()
15 ax.plot(x,y,linewidth=2)
16 ax.set(xlabel='x in m',ylabel='y in m')
17 ax.grid(True)
18 plt.show()
Output
The output of the trajectory of the oblique throw is shown in Figure 4.17.
155
4 Function Plots and Animations Using Matplotlib
Analysis
Line 08 calculates the throwing time tmax. This time is needed to set the array for the
parameter t in line 09. In lines 11 and 12, the x and y components of the trajectory are
calculated and stored as an array with 100 values each in variables x and y. The prepara-
tion for the representation of the function plot is again done using the plot(x,y,...)
method.
Lemniscate
The lemniscate by Jakob Bernoulli (1654–1705) is a plane curve in the shape of a lying
eight. It describes the motion curve in the Watt parallelogram (James Watt, 1736–1819).
The following parameter equations can be used to plot the lemniscate as a function
plot:
Listing 4.18 shows the source code for the graphical representation of a lemniscate.
01 #18_parameter_lemniscate.py
02 import numpy as np
03 import matplotlib.pyplot as plt
156
4.1 2D Function Plots
04 t=np.linspace(-np.pi,np.pi,200)
05 a=1
06 #Parameter equations
07 x=a*np.sqrt(2)*np.cos(t)/(np.sin(t)**2+1)
08 y=a*np.sqrt(2)*np.cos(t)*np.sin(t)/(np.sin(t)**2+1)
09 #Representation
10 fig, ax=plt.subplots()
11 ax.plot(x,y,linewidth=2)
12 ax.set(xlabel='x',ylabel='y')
13 ax.grid(True)
14 plt.show()
Output
Figure 4.18 shows the graphical representation of the lemniscate.
Analysis
The t parameter in line 04 does not have the meaning of a time in this case but of a
range of values. In the t variable, 200 values from −π to +π are stored. Lines 07 and 08
each calculate the 200 values for the x and y components for the t parameter, which
are then cached in line 11 via ax.plot(x,y,...) for display using plt.show() in line 14.
The a variable in line 05 determines the extent of the lemniscate on the x-axis.
157
4 Function Plots and Animations Using Matplotlib
The example shown in Listing 4.19 is taken from the Matplotlib documentation. This
example shows how the amplitude and frequency of a sine function can be changed
interactively during runtime using two sliders.
01 #19_slider_sinus.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.widgets import Slider,Button
05 fig,ax=plt.subplots()
06 fig.subplots_adjust(left=0.2,bottom=0.25)
07 t=np.linspace(0.0,1.0,200)
08 a0=5
09 f0=5
10 s=a0*np.sin(2*np.pi*f0*t)
11 kurve, = ax.plot(t,s,lw=2,color='blue')
12 ax.axis([0, 1, -10, 10])
13 #Position objects for controls
14 #left margin, bottom margin, length, height
15 xyAmp = fig.add_axes([0.25, 0.15, 0.65, 0.03])
158
4.1 2D Function Plots
Output
Figure 4.19 shows the output of the interactively changeable function parameters.
159
4 Function Plots and Animations Using Matplotlib
Analysis
In line 04, the widgets submodule with the Slider and Button classes is imported. Line
10 defines a sine function with the amplitude a0 and frequency f0 parameters. The
function coordinates with the initialization values are assigned to the curve variable in
line 11. When the program is started, a sine function with amplitude 5 and frequency 5
will be displayed.
The coordinates for the sldAmp slider, which is supposed to change the amplitude, are
assigned to the xyAmp variable in line 15. For the coordinates of the sldFreq slider, which
is supposed to change the frequency, the same applies in line 16.
The objects for the sldAmp and sldFreq sliders as well as for the cmdReset command but-
ton are created in lines 19 to 21. The Slider() constructor of the Slider class expects the
x-y coordinates of the control as the first parameter, the second parameter determines
the label, the third and the fourth parameters determine the adjustment range, the
fifth parameter valinit=5 determines the initialization value, and the sixth and final
parameter determines the increment of the value change.
In lines 23 to 26, the update(val) function is defined. This function is responsible for
assigning the values set by the sliders to the A and f variables. The val variable is
accessed via the sldAmp and sldFreq slider objects. In line 26, the values for amplitude
and frequency are updated using the set_data() method and prepared for display on
the screen.
In lines 28 to 30, the reset function is defined. The sldAmp and sldFreq objects are reset
by the built-in reset() method. If this function is called in line 34 by a mouse click on
the Reset command button, then the sine function will be displayed again with its ini-
tialization values.
In lines 32 to 34, the event query is done via the built-in on_changed(update) method.
This method is accessed with the name of the object. The sld and cmd prefixes, which do
not exist in the original, were assigned to better identify the controls in the source
code.
This program is a negative example of a programming style you should avoid! The
arrangement of inputs (lines 08 and 09), graphic elements (line 05, lines 11 to 21) and
function definitions (lines 23 to 30) does not correspond to the desirable arrangement
of the program parts, which would adhere to this sequence:
1. Inputs
2. Function definitions
3. Graphic area
Proposed change: Change the order of the instructions to meet these criteria and test
the program.
160
4.1 2D Function Plots
The slider adjusts the control angle from 0 to 180°. The current value of the arithmetic
mean UAV of the output voltage and the control angle 𝛼 should be displayed in the GUI
of the program.
The following holds true for the arithmetic mean value of the output voltage:
01 #20_sld_phase_angle_control.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.widgets import Slider
05 Us=325 #peak value in V
06 a0=np.pi/4 #initial value 45°
07 xmax=np.pi
08 #u(x), x is an angle
161
4 Function Plots and Animations Using Matplotlib
09 def u(x):
10 return Us*np.sin(x)
11 #query slider
12 def update(val):
13 alpha = sldAlpha.val #control angle in °
14 a=np.radians(alpha) #control angle in rad
15 x = np.arange(a,xmax,0.01)
16 y.set_data(x,u(x))
17 line.set_data([a,a],[u(0),u(a)])
18 Uav=Us*(1.0 + np.cos(a))/np.pi
19 txtAngle.set_text(r'$\alpha$ = %.2f °' %alpha)
20 txtUav.set_text(r'$U_{av}$ = %.2f V' %Uav)
21 #Graphics area
22 fig, ax = plt.subplots()
23 txtAngle=ax.text(0.1,1.12*Us,r'$\alpha$ = %.2f °' %45)
24 txtUav=ax.text(0.1,1.05*Us,r'$U_{av}$ = 176.60 V')
25 fig.subplots_adjust(left=0.12,bottom=0.15)
26 ax.set_xlim(0,xmax)
27 ax.set_ylim(0,1.2*Us)
28 x0 = np.arange(a0,xmax,0.01) #for initial values
29 line, = ax.plot([a0,a0],[u(0),u(a0)],'b-')
30 y, = ax.plot(x0,u(x0),'b-')
31 xyAlpha = fig.add_axes([0.1, 0.02, 0.8, 0.03])
32 sldAlpha=Slider(xyAlpha,r'$\alpha$',0,180,valinit=np.degrees(a0),
valstep=1)
33 sldAlpha.on_changed(update)
34 ax.set(xlabel=r'$\alpha \ in\ rad$',ylabel='U in V')
35 secax = ax.secondary_xaxis('top',functions=(lambda x:10*x/np.pi,
lambda x:np.pi*x))
36 secax.set_xlabel('t in ms')
37 plt.show()
Analysis
In line 15, the NumPy function arange(a,xmax,0.01) updates the range of values for con-
trol angle a on the x-axis. In line 16, this value is adopted by the matplotlib method,
set_data(x,u(x)). In the range from 0 to a, the sine function does not display. To make
the phase angle clearly visible, the set_data([a,a],[u(0),u(a)]) method in line 17 cre-
ates a vertical line. The statement in line 18 calculates the arithmetic mean value Uav of
the output voltage.
162
4.1 2D Function Plots
What is new in this case is the dynamic output of control angle alpha in line 19 on the
drawing area (ax object). On line 19, the set_text() method outputs the updated slider
setting of alpha at the x,y position of the axes object ax specified in line 23. The same
rule applies to the output of the output voltage Uav in lines 20 and 24. A dynamic text
output always consists of two parts:
1. Placement of the static text object with text(x-position,y-position,'text') in the
drawing area
2. The dynamic output using the set_text('y=%.2f'%x) method
01 #21_meshgrid_demo.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 x=y=np.linspace(1,6,6)
05 x,y=np.meshgrid(x,y)
06 fig, ax=plt.subplots()
07 plt.plot(x,y,marker='x',color='red',ls='none')
08 plt.show()
Output
How the mesh grid presents itself in the user interface (UI) is shown in Figure 4.21.
163
4 Function Plots and Animations Using Matplotlib
Analysis
Line 04 creates an array for each of the variables x and y with the numbers from 1 to 6.
In line 05, the meshgrid(x,y) NumPy function generates a matrix with six rows and six
columns from it. In line 07, the plot(x,y,marker='x',color='red',ls='none') method
creates a square matrix as a graph with 36 red crosses.
Now, we can insert the following statements below line 06:
ax.set_xticks([])
ax.set_yticks([])
ax.set_frame_on(False)
In this case, the x-axis and y-axis will not be labeled, and the borders (frame) will not be
displayed. You can then use this graph to illustrate magnetic field lines.
To ensure that all points in the (x,y) plane are captured, the radiuses for each x-y coor-
dinate must be calculated:
A contour plot is created using the contour() matplotlib method, as shown in Listing
4.22:
01 #22_contour_plot_circles.py
02 import numpy as np
164
4.1 2D Function Plots
Output
Figure 4.22 shows how the contour plot for a magnetic field is graphically represented
in the output.
Analysis
In line 08, the linspace() NumPy function creates an array of 100 values in the value
range ±10 for the x and y coordinates. From this array, the np.meshdrid(x,y) function
creates a matrix of 100 rows and 100 columns in line 09.
165
4 Function Plots and Animations Using Matplotlib
Line 10 calculates the magnetic field strength H. The hypot(x,y) NumPy function deter-
mines the distance (hypotenuse) of the field lines from the center point for each point
at location (x|y) according to the Pythagorean theorem. Since the matrix generated by
np.meshgrid(100,100) contains 100 rows and 100 columns, a total of 10,000 values is
stored in variable H.
In line 12, following Matplotlib method calculates the circles (contour lines) of the mag-
netic field lines:
contour(x,y,H,levels=lev,colors='red')
The first two parameters contain all x-y coordinate data. The data of the H variable for
the contour plot is in the third place of the parameter list. The fourth parameter (lev-
els) is assigned the lev=[1,2,4,8,16] list from line 07. Spreading out the spacing
prevents the field lines from being too dense around the center point. All contour line
data is stored in the cp variable so that it can be provided as the first parameter to
the clabel(cp,inline=True) Matplotlib method in line 13. The inline=True parameter
causes the values of the field strengths to be displayed in the graph. To keep the outer
dimensions of the contour graphic the same length on screen output, the set_aspect
('equal') method is added to the source code in line 15.
You can test the set_aspect('equal') method by commenting out line 16.
166
4.2 3D Function Plots
The first two parameter equations describe a circular path. The third equation defines
the slope of the helix in the z-direction.
Listing 4.23 shows how a helical line can be plotted using the plot(x,y,z) function:
01 #23_3d_helix.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 R=6
05 v0=5
06 omega=3
07 t=np.linspace(0,2*np.pi,500)
08 x=R*np.cos(omega*t)
09 y=R*np.sin(omega*t)
10 z=v0*t
11 ax = plt.figure(figsize=(6,6)).add_subplot(projection='3d')
12 ax.plot(x,y,z,lw=2)
13 ax.set(xlabel='x',ylabel='y',zlabel='z',title='Electron in the magnetic
field')
14 plt.show()
Output
The graphical implementation of the helical in the coordinate system is shown in
Figure 4.23.
167
4 Function Plots and Animations Using Matplotlib
Analysis
In line 07, 500 values in the range from 0 to 2π are stored in the t variable. With these
values, the np.cos(), np.sin(), and v0*t functions in lines 08 to 10 calculate the values
for the x, y, and z coordinates. The R variable determines the radius of the helical line.
The projection='3d' parameter in line 11 ensures that, in the further course of the pro-
gram, the functions and properties necessary for the 3D plot can be accessed using the
ax object. The plot(x,y,z, ...) method is used to prepare the 3D plot for output in line
12 and displayed on the screen via show() in line 14.
R is the mean diameter, and r is the diameter of the circular cross-section of a circular
ring.
Since the surface of a body is to be represented, the plot_surface() method must be
used instead of plot(). Listing 4.24 shows how to use this function.
01 #24_3d_torus.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 n=100
05 R=2 #mean radius
06 r=1 #cross-sectionradius
07 p=np.linspace(0,2*np.pi,n)
08 t=np.linspace(0,2*np.pi,n)
09 p,t=np.meshgrid(p,t)
10 #Parameter equations
11 x=(R+r*np.cos(p))*np.cos(t)
12 y=(R+r*np.cos(p))*np.sin(t)
13 z=r*np.sin(p)
14 #Draw circular ring
15 ax = plt.figure().add_subplot(projection='3d')
16 ax.plot_surface(x,y,z,rstride=5,cstride=5,color='y',edgecolors='r')
17 ax.set(xlabel='x',ylabel='y',zlabel='z',title='Torus')
18 ax.set_zlim(-3,3)
19 plt.show()
168
4.2 3D Function Plots
Output
The circular ring generated using the plot_surface() method is shown in Figure 4.24.
Analysis
Lines 11 to 13 contain the three parameter equations. The method in line 16 is new:
plot_surface(x,y,z,rstride=5,cstride=5,color='y',
edgecolors='r')
The rstride=5 parameter sets the increment of the horizontal lines, while cstride=5
sets the increment of the vertical lines.
01 #25_3d_mountain.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 width=10
05 h=100
06 x=y=np.linspace(-width,width,100)
07 x,y=np.meshgrid(x,y)
169
4 Function Plots and Animations Using Matplotlib
Output
The output of the paraboloid and level lines of the contour plot according to Listing 4.25
in Figure 1.25.
170
4.3 Vectors
Analysis
Almost all programming elements are known from the previous examples. In line 12,
the add_subplot(2,1,1,projection='3d') method creates the subplot for the 3D plot. In
line 17, the add_subplot(2,1,2) method creates the subplot for the contour plot.
The set_aspect('equal') method in line 23 scales the x-axis and y-axis of the second
subplot with equal drawing units.
You can also test the figure() method using the figsize=plt.figaspect(2) parameter
(line 11). Then, the graphic will be displayed twice as high as it is wide.
4.3 Vectors
Vectors describe directed quantities in physics, such as forces or field strengths. For the
representation of vectors, the Matplotlib module provides the quiver([X,Y],U,V,
[C],**kwargs) method.
The [X,Y] list sets the initial coordinates of the vector. The U parameter determines the
x-component, while the V parameter determines the y-component of the vector arrow.
If you omit the [X,Y] parameter, then the start of the arrow is automatically placed at
the origin of the coordinate system, and the arrowhead points to the specified u-v coor-
dinate. The C parameter allows you to set the color of the vector arrow. The optional
parameter **kwargs provides supplementary properties.
More information on this topic is available at https://matplotlib.org/stable/api/_as_
gen/matplotlib.pyplot.quiver.html.
01 #26_vector_add.py
02 import matplotlib.pyplot as plt
03 xmin, xmax=-8, 5
04 ymin, ymax=-6,6
05 F1x,F1y=-4,4
06 F2x,F2y=-4,-4
07 F3x,F3y=2,0
08 Fresx=F1x+F2x+F3x
09 Fresy=F1y+F2y+F3y
10 fig, ax=plt.subplots()
11 #vectors
12 ax.quiver(F1x,F1y,angles='xy',scale_units='xy',scale=1,color='m')
13 ax.quiver(F2x,F2y,angles='xy',scale_units='xy',scale=1,color='g')
171
4 Function Plots and Animations Using Matplotlib
14 ax.quiver(F3x,F3y,angles='xy',scale_units='xy',scale=1,color='b')
15 ax.quiver(Fresx,Fresy,angles='xy',
16 scale_units='xy',scale=1,color='r',label="$F_{res}$")
17 ax.axis([xmin,xmax,ymin,ymax])
18 ax.set(xlabel="$F_{x}$",title="Vector addition ")
19 ax.set_ylabel("$F_{y}$",rotation=True)
20 ax.legend(loc='best')
21 plt.show()
Output
The graphical output of the vector addition is shown in Figure 1.26.
Analysis
In lines 05 to 07, the x-y components for three force vectors are given. Line 08 calculates
the total of the x-components, and line 09 calculates the total of the y-components.
The following method in lines 12 to 15 defines force vectors:
quiver(F1x,F1y,angles='xy',scale_units='xy',scale=1)
172
4.3 Vectors
쐍 scale_units='xy': The units of the given axis scalings are taken over.
쐍 scale=1: The scaling factor is 1, which means that the given axis scaling is not
changed. A smaller number increases the length of the vector arrow.
01 #27_vector_field.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 n=10
05 x1,x2=0, 10
06 u=2 #length
07 v=0 #direction
08 xk=yk=np.linspace(x1,x2+u,n)
09 x,y=np.meshgrid(xk,yk)
10 fig, ax=plt.subplots()
11 #Define vectors
12 ax.quiver(x,y,u,v,units='xy',scale=2,color='blue')
13 #Range of the x-axis
14 ax.set_xlim(x1-u/2,x2+u)
15 plt.show()
Output
The graphical output of the vector field is shown in Figure 4.27.
173
4 Function Plots and Animations Using Matplotlib
Analysis
In line 09, the meshgrid() NumPy function generates the matrix for the x-y coordinates
of the vectors (arrow beginnings). In line 12, the following method defines the vector
field:
quiver(x,y,u,v,units='xy',scale=2,color='blue')
The x,y parameters define the coordinates of the arrow beginnings of the individual
vectors. The length of a vector is set to two length units in line 06. The scale=2 scaling
factor shortens the length of a vector by a factor of 0.5. You could have obtained the
same result with u=1 and scale=1.
4.4.1 Rectangles
Rectangle objects r are created using the following method:
r=patches.Rectangle((x1,y1),b,h,fill,edgecolor,angle)
These objects are then inserted into the drawing area via add_patch(r).
The x1,y1 tuple defines the lower-left corner of the rectangle. The b and h parameters
determine its width and height. The fill parameter is set to True by default and pro-
vides the option to fill the rectangle with a specific color: facecolor=color. The edge-
color parameter can be used to change the edge color, and the angle parameter allows
the rectangle to be rotated by a specified angle in degrees.
Listing 4.28 shows how the Rectangle() function can be used to display three rectangles
with predefined dimensions in a drawing area.
01 #28_pythagoras.py
02 import numpy as np
03 import matplotlib as mlt
04 import matplotlib.pyplot as plt
05 x1,x2=-3,8
06 y1,y2=-1,11
174
4.4 Displaying Figures, Lines, and Arrows
07 a,b=3,4
08 alpha=np.degrees(np.arctan(b/a))
09 beta=90-np.degrees(np.arctan(a/b))
10 c=np.hypot(a,b)
11 fig,ax=plt.subplots()
12 ax.axis([x1,x2,y1,y2])
13 #(x1,y1),width,height
14 ra=mlt.patches.Rectangle((0,c),a,a,fill=False,lw=2,edgecolor='b',
angle=alpha)
15 rb=mlt.patches.Rectangle((c,c),b,b,fill=False,lw=2,edgecolor='b',
angle=beta)
16 rc=mlt.patches.Rectangle((0,0),c,c,fill=False,lw=2,edgecolor='b')
17 ax.add_patch(ra)
18 ax.add_patch(rb)
19 ax.add_patch(rc)
20 ax.set_aspect('equal')
21 ax.set_xticks([])
22 ax.set_yticks([])
23 ax.set_frame_on(False)
24 plt.show()
Output
Figure 4.28 shows how the program from Listing 4.28 illustrates the Pythagorean theo-
rem.
175
4 Function Plots and Animations Using Matplotlib
Analysis
Lines 05 and 06, together with the axis([x1,x2,y1,y2]) method in line 12, specify the
dimensions of the drawing area. In lines 14 to 16, the three rectangle objects ra, rb, and
rc are generated using the following method:
Rectangle((x1,y1),b,h,fill=False,lw=2,edgecolor='b',angle=…)
In this example, the fill parameter is set to False. The edges of the rectangles are
drawn in blue due to edgecolor='b'. The rectangle in the upper left is rotated by the
angle alpha in the mathematically positive direction (i.e., counterclockwise). The rect-
angle in the upper right is rotated by the angle beta. The calculations of the angles are
performed using the arctan() NumPy function in lines 08 and 09.
The add_patch() method in lines 17 to 19 places the rectangles in the drawing area.
kreis=patches.Circle((x,y),radius,fill,lw,edgecolor)
These objects can then be embedded into the drawing area via the add_patch(circle)
function. In this case, the (x,y) tuple stands for the coordinates of the center of a circle.
The third parameter determines the radius.
Lines can be created using the plot([x1,x2],[y1,y2]) method you already know. The
known properties can be used to change the line styles and widths.
Based on the transmission with three gears example, Listing 4.29 demonstrates how
circles and lines can be displayed using the Circle() and plot() methods. The simpli-
fied representation of gears as circles with the mean diameters is used whenever the
structures and gear ratios of transmissions must be illustrated.
01 #29_transmission.py
02 import matplotlib as mlt
03 import matplotlib.pyplot as plt
04 x1,x2=-12,22
05 y1,y2=-17,12
06 fig,ax=plt.subplots()
07 ax.axis([x1,x2,y1,y2])
08 #(x,y),radius
09 c1=mlt.patches.Circle((-5,5),5,fill=False,lw=2,edgecolor='b')
10 c2=mlt.patches.Circle((-5,-5),5,fill=False,lw=2,edgecolor='b')
11 c3=mlt.patches.Circle((10,-5),10,fill=False,lw=2,edgecolor='b')
12 ax.add_patch(c1)
13 ax.add_patch(c2)
14 ax.add_patch(c3)
176
4.4 Displaying Figures, Lines, and Arrows
15 #x1,x2,y1,y2
16 ax.plot([-5,-5],[-5, 5],lw=1,color='black',ls='-.')
17 ax.plot([-5,10],[-5,-5],lw=1,color='black',ls='-.')
18 ax.plot([-5,10],[5,-5],lw=1,color='black',ls='-.')
19 ax.set_aspect('equal')
20 ax.set_xticks([])
21 ax.set_yticks([])
22 ax.set_frame_on(False)
23 plt.show()
Output
As a result of Listing 4.29, three circles and a triangle are output in the drawing area as
a technology schema of a transmission, as shown in Figure 1.29.
Analysis
The lines of the triangle illustrate the distances between the individual gears. The three
circle objects (c1, c2, and c3) are created in lines 09 to 11 using the following method:
mlt.patches.Circle((x,y),radius,fill=False,lw=2,edgecolor='b')
These lines are embedded in the drawing area in lines 12 to 14 by using the add_patch()
method.
To change the diameters of the circles for further program tests, then a useful approach
is to comment out the statements in lines 20 to 22, while inserting the ax.grid() state-
ment below line 22. In this way, you can better check coordinate changes.
The circle.center=(x,y) statement allows you to move the circle object to the desired
x,y position. This statement is used in Listing 4.35 for the animation of circular objects.
177
4 Function Plots and Animations Using Matplotlib
4.4.3 Arrows
Pointer diagrams are needed in AC theory to illustrate the phase shift between voltage
and current. Listing 4.30 demonstrates how the arrow() method can generate a pointer
diagram of arrows for a series circuit consisting of an ohmic resistor and an inductor.
01 #30_pointer_diagram.py
02 import matplotlib.pyplot as plt
03 x1,x2=0,12
04 y1,y2=0,8
05 lb=2 #line width
06 pb=0.5 #arrow width
07 pl=1 #arrow length
08 U_R=10 #ohmic voltage drop
09 U_L=5 #inductive voltage drop
10 I=12 #current
11 fig,ax=plt.subplots()
12 ax.axis([x1,x2,y1,y2])
13 #Arrows: x,y,x+dx,y+dy
14 ax.arrow(0,1.8,I,0,color='r',lw=lb,length_includes_head=True,
15 head_width=pb,head_length=pl)
16 ax.arrow(0,2,U_R,0,color='b',lw=lb,length_includes_head=True,
17 head_width=pb,head_length=pl)
18 ax.arrow(U_R,2,0,U_L,color='b',lw=lb,length_includes_head=True,
19 head_width=pb,head_length=pl)
20 ax.arrow(0,2,U_R,U_L,color='b',lw=lb,length_includes_head=True,
21 head_width=pb,head_length=pl)
22 #Labels
23 ax.annotate("$I$",xy=(5,1),xytext=(5,1),fontsize=12)
24 ax.annotate("$U_g$",xy=(5,5),xytext=(5,5.5),fontsize=12)
25 ax.annotate("$U_L$",xy=(10.5,4),xytext=(10.5,4),fontsize=12)
26 ax.annotate("$U_R$",xy=(5,3),xytext=(5,2.5),fontsize=12)
27 ax.set_xticks([])
28 ax.set_yticks([])
29 ax.set_frame_on(False)
30 ax.set_aspect('equal')
31 plt.show()
Output
Figure 4.30 shows see the output pointer diagram.
178
4.4 Displaying Figures, Lines, and Arrows
Analysis
Arrows can be represented using the following method:
arrow(x,y,x+dx,y+dy,color,lw,length_includes_head=True, head_width,head_length)
The arrow() method is accessed via the ax object created in line 11. The first two param-
eters (x and y) define the coordinates of the start of the arrow. The dx and dy parameters
determine the direction and length of the arrow. The head_width and head_length prop-
erties set the width and length of the arrowhead. An especially important point is that
the length_includes_head property must be set to True so that a closed pointer triangle
is displayed when rendering.
4.4.4 Polygons
A polygon is a plane geometric figure formed by a traverse line. A polygon can be cre-
ated using the Polygon(xy) constructor of the following class:
The first parameter (xy) is an array containing the coordinates of the vertices of a poly-
gon. Listing 4.31 enables you to draw polygons with any number of corners.
01 #31_polygon.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.patches import Polygon
05 r=10
06 n=6
07 R=1.1*r
08 fig,ax=plt.subplots()
09 ax.axis([-R,R,-R,R])
179
4 Function Plots and Animations Using Matplotlib
10 for k in range(n):
11 w=2*np.pi/n
12 x1,y1=r*np.cos(k*w),r*np.sin(k*w)
13 x2,y2=r*np.cos((k+1)*w),r*np.sin((k+1)*w)
14 ax.plot([0,x2],[0,y2],lw=1,color='b')
15 p=Polygon([[x1,y1],[x2,y2]],fill=False,lw=2)
16 ax.add_patch(p)
17 ax.set_aspect('equal')
18 ax.grid(True)
19 plt.show()
Output
Figure 4.31 shows an example of a polygon drawn using the Polygon method.
Analysis
Line 04 imports the patches submodule with the Polygon class. In line 05, you can
define the radius r of the perimeter, and in line 06, the number of corners n of the poly-
gon.
The most important program actions take place within the for loop (line 10 to 16). Line
11 calculates the angle w of a circle sector (circle section). In lines 12 and 13, the coordi-
nates of the corner points are calculated. The plot() method in line 14 marks the
boundaries of the circle sectors. In line 15, the Polygon([[x1,y1],[x2,y2]], ...) con-
structor of the Polygon class creates the p object. The add_patch(p) method in line 16
adds the p object to the drawing area.
180
4.4 Displaying Figures, Lines, and Arrows
You could also have represented a regular polygon much more easily, without a loop
construct, using the following statement:
p=RegularPolygon((x,y),n,radius=10,fill=False)
ax.add_patch(p)
The (x,y) tuple specifies the center of the polynomial. The n and radius parameters
define the number of corners and the radius of the polygon. However, the effort of the
algorithm for the calculation of the x-y coordinates, which appears complicated at first
sight, is justified because it is absolutely necessary in many applications, for example,
for the representation of pointers in the complex plane.
01 #32_mag_field.py
02 import numpy as np
03 import matplotlib as mlt
04 import matplotlib.pyplot as plt
05 x1,x2=0,12
06 y1,y2=0,7
07 x=np.linspace(1,9,9)
08 y=np.linspace(1,6,6)
09 x,y=np.meshgrid(x,y)
10 fig,ax=plt.subplots()
11 ax.axis([x1,x2,y1,y2])
12 rod=mlt.patches.Rectangle((1.4,0.25),0.2,6.5,color='black')#width,height
13 circle=mlt.patches.Circle((10,3.5),0.8,fill=False,lw=2,edgecolor='black')
14 ax.add_patch(circle)
15 ax.add_patch(rod)
16 ax.plot([1,10],[6.5,6.5],lw=2,color='black') #upper line
17 ax.plot([1,10],[0.5,0.5],lw=2,color='black') #bottom line
18 ax.plot([10,10],[0.5,6.5],lw=2,color='black') #right line
19 ax.plot(x,y,marker='x',color='red',ls='none') #magnetic field lines
20 ax.arrow(1.6,3.5,1,0,color='k',lw=2,head_width=0.15)#x,y,x+dx,y+dy
21 ax.arrow(11,6,0,-4.5,color='b',lw=2,head_width=0.16,head_length=0.5)
22 ax.annotate("v",xy=(3,3),xytext=(3,3.4),fontsize=12) #labels
23 ax.annotate("$U_q$",xy=(11.2,3),xytext=(11.3,3.2),fontsize=12)
24 ax.set_xticks([])#no axis labels
25 ax.set_yticks([])#no axis labels
26 ax.set_frame_on(False)
181
4 Function Plots and Animations Using Matplotlib
27 ax.set_aspect('equal')
28 plt.show()
Output
Analysis
Figure 4.32 shows the top view of a homogeneous magnetic field with two parallel bus
bars, a rod of conducting material, and a voltage source. The magnetic field lines are
represented by red crosses, which should mean that the magnetic field lines are at right
angles to the drawing plane and point in the direction of the drawing plane according
to convention.
Almost all display elements are known from earlier examples.
4.5 Animations
Computer animation is a process in which a moving image is created from a sequence
of frames. An algorithm continuously changes the positions of the individual images.
In the process, each frame must be deleted before it is then moved to a new position
and displayed there. If the algorithm generates 24 new images in 1 second, for example,
then the viewer is given the illusion of an almost fluid movement. Computer anima-
tions can be used to illustrate physical phenomena that are beyond human perception
by slowing down fast processes or speeding up slow processes.
The from matplotlib.animation import FuncAnimation statement imports the FuncAnima-
tion method.
182
4.5 Animations
Not all possible parameters are specified. Although this object is not needed in the ani-
mation program, it must be created; otherwise, the animation will not be executed, and
only a static image will appear on the screen.
Note
You must store an animation in a variable, which means you should always create an
explicit object. If you don’t, an implicitly created animation object will be subjected to
an automatic garbage collection process, and the animation will be stopped.
You can create an implicit object by not assigning a variable to a method. The following
console example creates an implicit object:
The warning generated by this console program was not included in this case.
The second parameter (func) stands for the name of a custom Python function that is
to be animated. This function is called without specifying a parameter. The other
parameters will be discussed during the analysis of each program.
The examples selected for this purpose include the time-based shifting of a sine wave
on the x-axis, the oblique throw, and the motion of a planet in an elliptical orbit.
01 #33_animation_sine.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.animation import FuncAnimation
05
06 def f(x,k):
07 return np.sin(x-k/20)
08
183
4 Function Plots and Animations Using Matplotlib
09 def v(k):
10 y.set_data(x,f(x,k))
11 return y,
12
13 fig,ax=plt.subplots()
14 x=np.linspace(0,4*np.pi,200)
15 y, = ax.plot(x,f(x,0),'r-',lw=3)
16 #Animation
17 ani=FuncAnimation(fig,v,
18 interval=20,
19 #frames=200,
20 blit=True,
21 # save_count=50,
22 # cache_frame_data=False
23 )
24 plt.show()
Output
Figure 4.33 shows a snapshot of the animation.
Analysis
This program draws a sine curve that is moving on the screen in the x-direction. The
direction of the movement can be changed by the sign in line 07. If the k parameter is
negative, the curve moves from left to right. If the k parameter is positive, it moves
from right to left.
184
4.5 Animations
The program consists of three parts: the v(k) function in line 09, the initialization part
in line 15, and the animation in line 17.
To perform an animation, the matplotlib.animation module must be imported (line 4).
In lines 06 and 07, the sine function sin(x-k/20) with variables x and k is defined. The x
variable changes the angle, while the k variable causes the movement of the sine func-
tion on the x-axis.
In line 09, the most important function for the animation is defined, namely, v(k). The
y.set_data(x,f(x,k)) method in line 10 changes the value k for the shift on the x-axis
at each function call of v(k) in line 17. The y object returned in line 11 must be termi-
nated with a comma because the return value must be a tuple. If you omit the comma,
an error message will display.
Line 13 creates the fig and ax objects. The fig object is needed for the animation in line
17. The ax object is used to access the plot method.
In line 14, the linspace(0,4*np.pi,200) NumPy function stores 200 values in the x vari-
able for the range from 0 to 4π. When the y object is initialized in line 15, the 200 values
for the x angles and for k=0 are stored in this object. Thus, the y object contains a static
image for two sine waves. The y object must be followed by a comma again, otherwise
the animation will not be executed.
In line 17, the following method performs the animation:
ani=FuncAnimation(fig,v,interval=20,blit=True)
Notice that an explicit ani object must be created (line 17), although it is not used in the
program. The identifier of this object is freely selectable. If you do not create an explicit
object, then the animation will not be executed. This object has the task of controlling
an internal counter (timer) that accesses the explicitly created animation object ani. If
this is missing, then the implicit animation object will be collected by the automatic
memory management functionality (garbage collection) as data garbage, and the ani-
mation will be stopped. A static image will appear on the monitor.
The first parameter (fig) sets the properties of the drawing area in which the animation
will take place.
As the second parameter, the FuncAnimation() method expects the custom animation
function v(k), which must be called without the k argument.
The interval parameter determines the delay (in milliseconds) with which the individ-
ual images are to be generated. The standard value is 200 ms. The larger this value, the
greater the pauses between the generation of new images. The animation no longer
runs as “smoothly” and shows clear signs of “bucking.”
The frames=200 parameter sets the number of frames to be drawn. This parameter is not
needed in this animation and will be explained in more detail in later examples.
185
4 Function Plots and Animations Using Matplotlib
The blit parameter specifies whether blitting should be used to optimize dynamic
drawing. The default value is False. Blitting means the fast copying and moving of the
object to be moved. If blit=True, only the areas of the image that have changed will be
redrawn. The animations should run more or less “smoothly” due to blitting. If you
comment out this parameter, however, you’ll notice that hardly any change is per-
ceptible. You can learn more about blitting at https://matplotlib.org/stable/tutorials/
advanced/blitting.html.
The save_count parameter sets the number of frames to be stored in the cache. This
parameter is used only if no value is assigned to the frames parameter.
The cache_frame_data parameter prevents a memory overflow from occurring in the
cache. The default value is True. If the frames parameter is not assigned a value, you
should set cache_frame_data=False as of Matplotlib version 3.7. Otherwise, the Python
interpreter will issue the following warning:
UserWarning: frames=None which we can infer the length of, did not pass an
explicit *save_count* and passed cache_frame_data=True. To avoid a possibly
unbounded cache, frame data caching has been disabled. To suppress this warning
either pass `cache_frame_data=False` or `save_count=MAX_FRAMES`.
ani=FuncAnimation(fig,v,
You should test this program extensively by changing the individual parameters and
closely observing the effects on the animation.
Listing 4.34 enables you to animate the motion sequence of the oblique throw.
01 #34_animation_throw.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.animation import FuncAnimation
05 g=9.81
06 v0=10
07 throwing_angle=45
186
4.5 Animations
08 alpha=np.radians(throwing_angle)
09 tmax=2*v0*np.sin(alpha)/g
10 xmax=v0**2*np.sin(2*alpha)/g
11 ymax=v0**2*np.sin(alpha)**2/(2*g)
12 #Calculate trajectory
13 def throw(t):
14 x = v0*np.cos(alpha)*t
15 y = v0*np.sin(alpha)*t-0.5*g*t**2
16 ball.set_data([x],[y])
17 return ball,
18 #generate objects
19 fig,ax=plt.subplots()
20 ax.axis([0,xmax+0.5,0,ymax+0.5])
21 ball, = ax.plot([],[],'ro')
22 t=np.linspace(0,tmax,100)
23 ani=FuncAnimation(fig,throw,frames=t,interval=20,blit=True)
24 ax.set(xlabel="x in m",ylabel="y in m",title="Oblique throw")
25 plt.show()
Output
Figure 4.34 shows a snapshot of the animation of the oblique throw.
187
4 Function Plots and Animations Using Matplotlib
Analysis
Of course, the animated throwing process cannot be shown in this book. For testing
purposes, you can run the program with different initial velocities in line 06 and
throwing angles in line 07.
The custom throw(t) function in lines 13 to 17 calculates the new positions of the x and
y coordinates for each new function call by the FuncAnimation() method and stores
them in the ball object. As of Matplotlib version 3.7, the x and y arguments must be
enclosed in square brackets (line 16) because the set_data() method only accepts
sequences as arguments. If you omit the brackets, the following warning appears:
MatplotlibDeprecationWarning: Setting data with a non sequence type is deprecated
since 3.7 and will be remove two minor releases later.
Line 21 initializes the ball object with an empty list for the plot method. The ro param-
eter causes the ball to be displayed as a red dot. If you insert the markersize='15' param-
eter, the diameter of the ball will increase.
The FuncAnimation() method in line 23 executes the animation. The frames parameter
sets the number of frames to be displayed per second if the parameter is assigned an
integer. In this animation, frames is assigned a sequence t from 0 to tmax out of 100 val-
ues, which guarantees an almost smooth display. If you were to assign an integer to the
frames parameter, the ball would flash at a fixed position on the trajectory and not
move.
Listing 4.35 enables you to animate the motion sequence of a planet around a star.
01 #35_animation_elipse.py
02 import numpy as np
03 import matplotlib as mlt
04 import matplotlib.pyplot as plt
05 from matplotlib.animation import FuncAnimation
06 #Data
07 r1,r2=0.5,0.25
08 a,b=8,4 #Ellipse axes
09 width=10
10 #Initialization
11 def init():
12 planet.center=(1,2)
188
4.5 Animations
13 ax.add_patch(planet)
14 return planet,
15 #Trajectory calculation
16 def trajectory(t):
17 x,y=a*np.cos(np.radians(t)),b*np.sin(np.radians(t))
18 planet.center=(x,y)
19 return planet,
20 #Graphics area
21 fig,ax=plt.subplots()
22 ax.axis([-width,width,-width,width])
23 planet= mlt.patches.Circle((0,0),radius=r2, color='blue')
24 star= mlt.patches.Circle((2.5,0),radius=r1, color='red')
25 ax.add_artist(star)
26 ani=FuncAnimation(fig,trajectory,
init_func=init,frames=360,interval=20,blit=True)
27 ax.set_aspect('equal')
28 ax.set(xlabel='x',ylabel='y',title='elliptical orbit')
29 plt.show()
Output
Figure 4.35 shows a snapshot of the animation of a planetary orbit.
189
4 Function Plots and Animations Using Matplotlib
Analysis
Basically, the program has the same structure as the animation of the throwing parab-
ola. Line 08 defines the a and b axes of the elliptical orbit.
In lines 11 to 14, the init() function initializes the circular object planet created in line
23 for the x = 1 und y = 2 values (line 12) using the planet.center=(1,2) statement. These
values are chosen arbitrarily. When the program starts, this placement cannot be per-
ceived.
The custom trajectory(t) function in lines 16 to 19 contains the parameter equations
of the ellipse. The coordinate data is calculated in line 17. The planet.center=(x,y) state-
ment takes the coordinates of the orbit and stores them in the planet object.
The FuncAnimation() method in line 26 calls the trajectory function and the init func-
tion without parameters and creates the animation. In line 24, the star object is cre-
ated, and in line 25, this object is added to the center of the drawing area using the
add_artist(stern) method. Determining the number of frames is important. For exam-
ple, you can test the program with frames=300. Then, after 300°, you’ll observe the
jumping movement of the planet.
190
4.6 Project Task: Stirling Cycle
Gas
warm cold
The formula symbol n is the amount of substance of the gas in mols. The constant R is
the general gas constant for ideal gases (R = 8.31446261815324 J ⋅ mol-1 ⋅ K-1).
For the volume work done on the piston, the equation following applies:
When heating with the temperature Tw, the volume work defined by the following
equation is performed:
191
4 Function Plots and Animations Using Matplotlib
The index w stands for the warm state, and the index k stands for the cold state. The
indexes for work W12 and W34 are shown in Figure 4.37.
The utilizable mechanical work ∆W is calculated from the difference:
The usual convention in thermodynamics that the work done (W12) is given a negative
sign is not followed in this case, for once. Just think of the volume of work as amounts.
For the efficiency level, the following applies:
192
4.6 Project Task: Stirling Cycle
The dots symbolize the respective states for the pressures, volumes, and temperatures.
The transitions from one state to another state are referred to as a process. The individ-
ual process steps run as follows:
쐍 1 to 2: The air is heated by supplying thermal energy at a constant temperature Tw.
It expands, so the air volume increases, and the piston moves upward. Because of
p ⋅ V = const. the pressure decreases (isothermal change of state).
쐍 2 to 3: The air is cooled down. The volume does not change (isochoric change of
state).
쐍 3 to 4: The volume decreases from V2 to V1 with the low temperature Tk being kept
constant, and the pressure increases because p ⋅ V = const. (isothermal change of
state).
쐍 4 to 1: The temperature of the air is increased from the low temperature Tk to a
higher temperature Tw by supplying thermal energy. The volume remains constant
(isochoric change of state).
Listing 4.36 can generate the state diagram shown in Figure 4.37. The specification of
the amount of substance in line 09 of n = 0.045 mol corresponds to the volume of one
liter of air under normal conditions (T = 273.15 K, p = 1 bar).
01 #36_plot_cycle.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 Tw, Tk= 800, 400 #K
05 V1,V2=0.2,1 #dm^3
06 #Function definition p=f(V)
07 def p(V,T):
08 R=8.314 #J/(mol*K)
09 n=0.045 #mol
10 return 1e-2*n*R*T/V
11 #Graphics area
12 fig, ax = plt.subplots()
13 V=np.linspace(V1,V2,100)
14 ax.plot(V,p(V,Tw),'r-') #warm
15 ax.plot(V,p(V,Tk),'b-') #cold
16 #Dots
17 ax.plot([V1,V1],[p(V1,Tw),p(V1,Tk)],'ko')
18 ax.plot([V2,V2],[p(V2,Tw),p(V2,Tk)],'ko')
19 #vertical lines
20 ax.plot([V1,V1],[p(V1,Tk),p(V1,Tw)],'k-')
21 ax.plot([V2,V2],[p(V2,Tk),p(V2,Tw)],'k-')
22 ax.set_xlim(0.1,1.2)
193
4 Function Plots and Animations Using Matplotlib
23 ax.set_ylim(0,16)
24 #x,y labels
25 ax.text(V1-0.04,p(V1,Tw)-0.25,'1')
26 ax.text(V2+0.02,p(V2,Tw),'2')
27 ax.text(V2+0.02,p(V2,Tk)-0.25,'3')
28 ax.text(V1-0.04,p(V1,Tk)-0.25,'4')
29 ax.text(0.6,5.6,r'$Q_{to}$')
30 ax.text(0.5,1.8,r'$Q_{from}$')
31 ax.text(0.55,3.8,'∆W')
32 ax.set(xlabel='V in liters',ylabel='p in bars')
33 ax.fill_between(V,p(V,Tw),p(V,Tk),alpha=0.2,color='green')
34 plt.show()
Analysis
The temperatures must be specified in kelvin (line 4). The factor in line 10 1e-2 causes
the pressure to be converted from Pa to bar.
In lines 14 and 15, the plot method calculates the coordinate data for the warm and cold
temperatures. The r- and b- parameters create a red and blue solid line, respectively.
The ko parameter in lines 17 and 18 draws four black dots. In lines 20 and 21, the plot
method specifies the coordinate data for the volume boundary between volumes V1
and V2. Two vertical lines are drawn.
In lines 25 to 28, the text(x,y,'number') method marks the numberings for the individ-
ual process states from 1 to 4.
194
4.6 Project Task: Stirling Cycle
Listing 4.37 creates the program’s user interface from Figure 4.38. Again, an air volume
of one liter is assumed, which corresponds to a substance quantity of n = 0.045 mol
(line 05). The gas constant R and the amount of substance n are already declared at the
start of the program in lines 05 and 06 because they are needed in Python function
update(val) for the calculation of the volume work (line 23).
01 #37_sld_p_V_diagram.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.widgets import Slider
05 n=0.045 #mol
06 R=8.314 #J/(mol*K)
07 #p=f(V), isothermal, T as parameter
08 def p(V,T):
09 return 1e-2*n*R*T/V
10 #query slider
11 def update(val):
12 Tw, Tk = sldTw.val, sldTk.val #warm, cold
13 V1, V2 = sldV1.val,sldV2.val
195
4 Function Plots and Animations Using Matplotlib
14 Vx = np.arange(V1,V2,0.001)
15 y1.set_data(Vx,p(Vx,Tw)) #isotherm
16 y2.set_data(Vx,p(Vx,Tk))
17 point1.set_data([V1],[p(V1,Tw)]) #point1
18 point2.set_data([V2],[p(V2,Tw)]) #point2
19 point3.set_data([V2],[p(V2,Tk)]) #point3
20 point4.set_data([V1],[p(V1,Tk)]) #point4
21 line1.set_data([V1,V1],[p(V1,Tk),p(V1,Tw)]) #vertical line
22 line2.set_data([V2,V2],[p(V2,Tk),p(V2,Tw)]) #vertical line
23 W=n*R*(Tw-Tk)*np.log(V2/V1)
24 eta=1-Tk/Tw
25 txtW.set_text('W = %.2f J' %W)
26 txtEta.set_text(r'$\eta$ = %.2f' %eta)
27 #Graphics area
28 fig, ax = plt.subplots(figsize=(6,6))
29 txtW=ax.text(0.9,15,'')
30 txtEta=ax.text(0.9,14,'')
31 fig.subplots_adjust(left=0.12,bottom=0.25)
32 ax.set_xlim(0.1,1.2)
33 ax.set_ylim(0,16)
34 ax.set(xlabel='V in Liter',ylabel='p in bar',title='p-V diagram')
35 y1, = ax.plot([],[],'k-',lw=2) #ordinate
36 y2, = ax.plot([],[],'k-',lw=2) #ordinate
37 line1,line2 = ax.plot([],[],'r--',[],[],'r--')
38 point1,point2 = ax.plot([],[],'bo',[],[],'ro')
39 point3,point4 = ax.plot([],[],'go',[],[],'mo')
40 #x-, y-position, length, height
41 xyV1 = fig.add_axes([0.1, 0.12, 0.8, 0.03])
42 xyV2 = fig.add_axes([0.1, 0.08, 0.8, 0.03])
43 xyTw = fig.add_axes([0.1, 0.04, 0.8, 0.03])
44 xyTk = fig.add_axes([0.1, 0.0, 0.8, 0.03])
45 #create slider objects
46 sldTw=Slider(xyTw,r'$T_{w}$',501,800,valinit=800,valstep=1) #warm
47 sldTk=Slider(xyTk,r'$T_{k}$',400,500, valinit=400,valstep=1) #cold
48 sldV1=Slider(xyV1,r'$V_{1}$',0.2,0.5, valinit=0.2,valstep=0.01)
49 sldV2=Slider(xyV2,r'$V_{2}$',0.6,1, valinit=1.0,valstep=0.01)
50 #query changes
51 sldTw.on_changed(update)
52 sldTk.on_changed(update)
53 sldV1.on_changed(update)
54 sldV2.on_changed(update)
55 plt.show()
196
4.7 Project Task: Animating a Thread Pendulum
Analysis
The n and R variables are already declared at the beginning of the program (line 05 and
06), so that they are available within the update(val) Python function (line 11). Alterna-
tively, they could also be inserted as additional parameters in the custom p(V,T,R=
8.314,n) Python function (line 08). Although this approach would follow better pro-
gramming style, the programming effort would increase because a Python function
would need to be defined for the calculation of the volume work (line 23).
In lines 12 and 13, the current values of the slider settings are assigned to the state vari-
ables Tw, Tk, V1, and V2. In line 14, the volume limits for V1 and V2 are adjusted.
The calculations for the volume work W and the efficiency eta are performed in lines 23
and 24. The set_text() method causes the currently calculated results (lines 25 and 26)
to be output at the positions in the program’s user interface that were specified in lines
29 and 30.
The on_changed(update) method (lines 51 to 54) calls the update Python function and
passes it the current numerical values of the slider settings.
197
4 Function Plots and Animations Using Matplotlib
The current potential energy Epot = m⋅h and the current kinetic energy Ekin = 0.5⋅m⋅v2
need to be displayed in the user interface of the program. The height h can be calculated
using the diagram shown in Figure 4.40.
I
I–h
m
h
F ɔ
t
Fg
The total of the acceleration force Fa = m⋅a and the tangentially acting restoring force Ft
must be equal to 0 at any time of the pendulum motion:
To solve this differential equation numerically, it must be transformed into a 2nd order
differential equation system:
198
4.7 Project Task: Animating a Thread Pendulum
Using the sum algorithm (Euler method), this differential equation system can be
solved numerically in a particularly simple way:
The additionally added term d*w*dt considers the effect of the damping d. For the
damping, you can make the simplified assumption that it damps the oscillations pro-
portionally to the angular velocity 𝜔..
The sum algorithm is executed within a for loop.
Listing 4.38 shows the solution for this project task. In line 07, you can change the
deflection angle.
01 #38_animation_thread_pendulum.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from matplotlib.animation import FuncAnimation
05 #Data
06 l = 1.0 #pendulum length in m
07 angle = 60 #deflection angle
08 d = 0.0 #damping
09 m=10 #mass in kg
10 tmax = 50 #simulation duration
11 g = 9.81 #m/s^2
12 w02=g/l #square of the circular frequency
13 #solution of the differential equation using the Euler method
14 dt = 1e-3 #increment
15 phi,w=np.radians(angle), 0.0 #initial values
16 t = np.arange(0, tmax, dt)
17 x,y = np.empty((len(t))),np.empty((len(t)))
18 v=np.empty((len(t)))
19 x[0]=y[0]=0
20 for i in range(len(t)):
21 phi = phi + w*dt #deflection
22 w = w - w02*np.sin(phi)*dt - d*w*dt
23 v[i]=l*w
24 x[i],y[i] = l*np.sin(phi),-l*np.cos(phi) #x-y coordinates
25 #Animation function
26 def pendulum(j):
199
4 Function Plots and Animations Using Matplotlib
27 h=l+y[j]
28 Epot=m*h
29 Ekin=m*v[j]**2/2.0
30 txtEpot.set_text(f'$E_{{pot}}$={Epot:3.1f} J')
31 txtEkin.set_text(f'$E_{{kin}}$={Ekin:3.1f} J')
32 rod.set_data([0,x[j]],[0,y[j]])
33 sphere.set_data([x[j]],[y[j]])
34 return rod,sphere,txtEpot,txtEkin
35 #Graphics area
36 fig,ax= plt.subplots(figsize=(6, 6))
37 txtEpot=ax.text(-l,l,'',fontsize=12)
38 txtEkin=ax.text(-l,0.85,'',fontsize=12)
39 ival=1e3*dt
40 n=len(y)-1
41 width=1.1*l
42 ax.axis([-width,width,-width,width])
43 ax.set(xlabel='x',ylabel='y')
44 ax.set_aspect('equal')
45 ax.plot(0,0,'ko') #bearing
46 rod, = ax.plot([],[], 'b-', lw=1) #rod
47 sphere, = ax.plot([],[], 'ro', markersize='15') #sphere
48 ani = FuncAnimation(fig, pendulum,frames=n,interval=ival,blit=True)
49 plt.show()
Analysis
The program consists of four parts:
1. Inputs (lines 06 through 09)
2. Solution of the differential equation (lines 14 to 24)
3. Definition of the animation function (lines 26 to 34)
4. Graphics area (lines 36 to 49)
To animate different scenarios, you can change the deflection angle in line 07, change
the damping in line 08, and change the mass in line 09. You should not extend the set-
ting for the pendulum length because the animation won’t run as smoothly.
In line 14, you can adjust the increment dt to optimize the speed of the animation. Even
on the same computer, program execution speeds differ when tested with different
development environments. In lines 20 to 22, the sum algorithm is executed. For each
individual support point i, the trajectory velocity v[i] of the pendulum is calculated
from the angular velocity w and the pendulum length l in line 23. In line 24, the current
trajectory coordinates x[i] and y[i] are calculated.
200
4.8 Project Task: Animating a Transmission
Within the custom animation function pendulum(j) (lines 26 to 34), the current values
for the potential and kinetic energy (lines 28 and 29) are calculated and prepared for
output on the screen in lines 30 and 31 using the set_text() method. In lines 32 and 33,
the current coordinate data x[j] and y[j] are passed to the set_data() method and
stored in the rod and sphere objects.
In line 48, the FuncAnimation() method processes the values of the rod, sphere, txtEpot,
and txtEkin objects returned by the pendulum() function. In line 49, the show() method
displays the animation on the screen.
Meaning Formula
Tooth height
Tooth clearance
The animation program is based on the original by Magnus Benjes. Only the identifiers
of some variables were changed, a calculation part for the dimensioning of the trans-
mission was added, and compact statements were distributed across several program
lines.
In the program, the usual involute gearing is replaced by a trapezoidal one. The pitch
circle of the gear is simulated by a polygon. The number of polygon sides corresponds
to the number of teeth of a gear.
201
4 Function Plots and Animations Using Matplotlib
The x-y coordinates of the polygon corners are calculated using the Euler’s formula:
01 #39_animation_transmission.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import matplotlib.animation as ani
05 from matplotlib.patches import Polygon
06 m=0.5 #module
07 i=2 #transmission ratio
08 d1=8 #mean diameter
09 d2=i*d1
10 a=(d1+d2)/2
11 z1=d1/m #number of teeth
12 z2=i*z1
13 h=13*m/6 #tooth height
14 c=0.2*m #tooth clearance
15 i=complex(0,i)
16 x1=1/7
17 x2=1/3
18 tooth_shape=np.array([-x2,-x1,x1,x2])
19 frames=60
20 xmax=-11/16*d2,22/16*d2
21 ymax=-10/16*d2,10/16*d2
22 #Function definition for a gear
23 def gear(d,z,h):
24 r=d/2
25 alpha=2*np.pi/z #angle range
26 sector=tooth_shape*alpha
27 gear_section=np.array([r-h/2,r+h/2,r+h/2,r-h/2])-c
28 tooth=gear_section*np.exp(1j*sector)
29 return np.outer(np.exp(1j*alpha*np.arange(z)),tooth).ravel('C')
30 #Create gear objects
31 zr1=gear(d1,z1,h)
32 zr2=gear(d2,z2,h)*np.exp(1j*np.pi/z2)
33 step=2*np.pi/(z2*frames)
34 fig=plt.figure(figsize=(6,4))
35 ax=fig.add_axes([-0.2,-0.1,1.2,1.2])
36 image=[] #empty list
37 for k in range(frames):
38 zr1=zr1*np.exp(-i*step) #right turning
202
4.8 Project Task: Animating a Transmission
Output
Figure 4.41 shows a snapshot of the animation of a gear transmission.
Analysis
The program consists of a total of six parts:
1. The input of the gear data for module m, the transmission ratio i and the pitch circle
diameter d1 of the first gear (line 06 to 08).
2. The calculation of the pitch circle diameter d2 of the second gear, the distance a
between the gears, the number of teeth z1 and z2, the tooth height h and the tooth
clearance c (line 09 to 14).
3. The definition of the gear(d,z,h) function for the calculation of the geometric data
of a gear (line 23 to 29). The function expects three parameters when called: the
diameter d of the gear, the number z of teeth, and the height h of a tooth. The gear()
function returns the dyadic product of the term np.exp(1j*alpha*np.arange(z)) and
the gear object, flattened using NumPy method ravel().
203
4 Function Plots and Animations Using Matplotlib
4. The creation of two gear objects, zr1 and zr2 (lines 31 and 32). The second gear is
rotated one tooth position further by multiplying by the rotation factor np.exp(1j*
np.pi/z2) so that the teeth do not overlap.
5. The generation of the images from two polygons (lines 36 to 42). Inside the for loop,
the Polygon() constructors of the Polygon class create the P1 and P2 objects for the
images of the two gears. The x and y coordinates for the polygon corners calculated
in lines 38 and 39 are passed as the first parameter. The NumPy method reshape
(zr1.size,2) transforms the coordinate data into a two-dimensional array. The
image.append() method creates an array of 60 images from the two polygons
because the frames variable was assigned the value 60 in line 19.
6. The ArtistAnimation(fig,image,interval=20) method that performs the animation
(line 43). The first parameter passed is the fig object from line 34. The second param-
eter (image) contains all 60 images created within the for loop. They are displayed
repeatedly with a delay of 20 ms.
4.9 Tasks
1. Write a program that represents the following two functions in a diagram:
y1 = cos x
y2 = x
2. Write a program that plots the voltage drop U = 𝑓(I) and power P = 𝑓(I) for a 1-Ω resis-
tor in a function plot. On the left axis, plot the voltage U in volts and, on the right
axis, plot the power P in watts. To add scaling to the right axis, you must create a new
object using the twinx method a2=a1.twinx().
3. The gas consumption of a heating system for one week from Monday to Sunday is to
be represented in a diagram as a line graph. For this period, the total gas consump-
tion and its average value should be calculated and displayed within the diagram.
You can save the days of the week in a list:
days=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']#label x-axis
You can use the set_xticks(np.arange(n),days) method to label the x-axis. Write a
program that meets these requirements.
4. A rectangular function is to be approximated by the Fourier series y = ∑10·sin(kx)/k
(for k = 1, 3, 5, …). You can have the series calculated inside a for loop using for k in
range(1,n,2). The individual harmonics and the sum of these harmonics are to be
represented. Write an appropriate program.
5. The AC resistance for an inductance, and a capacitance is to be
plotted in two subplots one below the other. Write an appropriate program.
204
4.9 Tasks
6. Write a program that plots the time and location pattern of a sound wave (𝑓 = 440
Hz) in two subplots side by side as a cross of axes. The following applies:
ax1=fig.add_axes([0.1,0.1,0.8,0.8])#external
ax2=fig.add_axes([0.58,0.18,0.28,0.25])#internal
the amplitude, the damping and the period duration are to be changed using a
slider control. Write a program that meets these requirements.
13. Fourier synthesis with a slider control is to be simulated for a rectangular signal.
Only the sums of the individual harmonics are to be displayed. Write a program
that meets these requirements.
14. The slider control is intended to simulate the superposition of two oscillations. The
period duration is 20 ms. Both oscillations have an amplitude of 10. Only the phase
angle of the second oscillation is to be changed. Write an appropriate program.
15. Modify Listing 4.20 so that the circuit operates as a phase section control.
205
4 Function Plots and Animations Using Matplotlib
16. Write a program that animates the motion of two sine functions in the direction of
the x-axis. The first sine function should move from left to right, and the second
sine function should move from right to left. The sum of both functions should also
be represented.
17. A point is said to move on a sine curve . Write an animation program.
18. The Earth and Mars move in elliptical orbits around the Sun. The motion of Earth
and Mars should be animated with a phase shift and with different velocities. You
do not need to consider exact astronomical measurements. Only the qualitative
correlations need to be illustrated. Write a program that displays the planets using
the mlt.patches.Circle() method and animates them using the FuncAnimation()
method.
19. Animate the Moon-Earth-Sun rotation system using the FuncAnimation() method.
All three objects must be represented via the mlt.patches.Circle() method. You
should only consider qualitative relationships.
20. The sine curve oscillates up and down in the direction of the y-axis. Write an anima-
tion program for the process described by the following equation:
206
Chapter 5
Symbolic Computation Using SymPy
In this chapter, you’ll learn how to perform symbolic computations
using the SymPy module. This chapter covers standard topics in engi-
neering mathematics such as differentiations, integrations, differential
equations, and Laplace transformations.
SymPy is a Python library for computer algebra. The SymPy module is written entirely
in Python and consists of several hundred thousand program lines. The developers of
SymPy pursued the goal of creating a complete computer algebra system (CAS).
A CAS is a computer program for computing algebraic expressions. In this context,
mathematical operations are not performed with numbers but with symbols. A mini-
mal CAS consists of a user interface (Window-based version, terminal version); an
interpreter for parsing mathematical commands; and a system kernel that executes
the commands. For example, after entering a command such as diff(x^2,x);, you can
press (Shift) + (Return) (in CAS, Maxima), and the result 2x appears in the user inter-
face. In this way, you can write even extensive mathematical papers: Variables and
functions must be defined, then they are manipulated, linked, and parsed according to
the previously designed specifications. Comments can be inserted between the sym-
bolic computations. A user can then save the worksheet that results from these compu-
tations in LaTeX or other formats.
For custom extensions, every CAS provides a script language that can be integrated
into a worksheet. The CAS is the base system, while the scripting language has a com-
plementary function.
Python, however, goes the opposite way: The programming language is the base sys-
tem, and the respective desired functionality is provided as a module and imported
into the Python program as required. Thus, a particular advantage of the modular con-
cept is its flexibility.
Due to the SymPy module, Python can also be used like a conventional CAS (e.g., Max-
ima) in the terminal. Let’s consider a simple example like the following function:
Now, we want to calculate the first and the second derivative as well as the antideriva-
tive. Type the following statements into the Python console:
207
5 Symbolic Computation Using SymPy
The first step is to import the SymPy module. The asterisk operator (*) specifies that all
functions, methods, and mathematical constants of the sympy module should be
loaded. As a beginner, you should prefer this module import option so that the execu-
tion of your scripts won’t be blocked due to missing methods. As your experience
grows, you can then incorporate the specific submodules and methods you need for
your projects.
The second statement specifies the names of the mathematical variables. If multiple
variables are to be used, the statement is, for example, x,y,z=symbols('x y z').
In another CAS, the console input in the third line defines the function on which the
mathematical operations are to be performed.
The diff(y,x) command calculates the first derivative of the polynomial. After press-
ing (Return), the solution appears directly in the next line. The calculation of the sec-
ond derivative follows the same pattern, the difference being that a 2 must exist after
the independent x variable, separated by a comma. Notice in this context that the term
of the second derivative has already been simplified.
The integrate(y,x) command computes the antiderivative of the y polynomial. SymPy
does not output an integration constant.
To understand the next few examples, we’ve provided an overview of the most import-
ant functions of SymPy, which are listed in Table 5.1.
Function Description
208
Symbolic Computation Using SymPy
Function Description
Method Description
obj.doit() Analyzes obj objects that are not parsed by default, such as
sums, products, limits, derivatives, and integrals. The analysis
is recursive.
Z.evalf(n) Generates a float with n digits for the number Z of type Float.
You can use certain SymPy functions as if they were methods, as the following console
dialog shows:
For this reason, consistently referring to all SymPy functions as methods makes sense
for the sake of better readability.
209
5 Symbolic Computation Using SymPy
Note
In this chapter, all SymPy functions are referred to as methods. This choice of this term
has the additional advantage of avoiding confusion with the mathematical term func-
tion.
SymPy also provides its own mathematical constants, listed in Table 5.3. Using the
pi.evalf(10) method, for example, you can display the value of π with nine decimal
places.
π = 3.141592654 pi Pi
∞ oo Infinite
SymPy provides the trigonometric functions cos, sin, and tan as well as the e-function
exp() and the hyperbolic functions sinh, cosh, and tanh, so you don’t need to resort to
using the NumPy module. In fact, using the corresponding NumPy functions is not rec-
ommended to avoid conflicts between the namespaces of both modules.
Function Description
factorial(n) Factorial
210
5.1 Basic Mathematical Operations
>>> Add(2,3)
5
>>> Mul(2,3)
6
>>> Mul(6,1/3)
2.00000000000000
However, you don’t need to use this inconvenient notation. You can perform all basic
arithmetic operations using the infix operators.
The following examples show how SymPy performs these operations on symbolic vari-
ables.
5.1.1 Addition
Listing 5.1 shows the addition of the four terms T1, T2, T3, and T4, which are composed of
the variables a, b, c, and d.
01 #01_add.py
02 from sympy import *
03 a,b,c,d=symbols("a b c d")
04 T1=9*a+7*b-2*c+3*d
05 T2=8*a+2*b+3*c+4*d
06 T3=7*a-3*b+2*c+5*d
07 T4=4*a+2*b+5*c-6*d
08 T=T1+T2+T3+T4
09 print("Sum of terms")
10 pprint(T)
Output
Sum of terms
28・a + 8・b + 8・c + 6・d
211
5 Symbolic Computation Using SymPy
is justified because, if SymPy wants to compete with another CAS, the effort would no
longer be justifiable for a beginner if they had to explicitly import the required meth-
ods for each new task.
In line 02, you could use the following statement to import only the required methods:
However, this option is more complex and error prone in complex programs.
Line 03 specifies the symbols for the mathematical variables the program should use to
perform the computations. At this point, you can use the variable identifiers known
from mathematics, which means that SymPy allows user-defined variable names.
You’re free to decide which variable names you want to use, and you’re not locked into
x-y math.
The assignments in lines 04 to 07 define the terms that are added up in line 08. The
multiplication operator * must be placed between the factors and the variables, for
example, 7*b. The notation 7b would trigger an error message.
In line 10, the pprint() method outputs the formatted result in which Unicode charac-
ters are used. The first p is supposed to stand for pretty.
You can check the correctness of the result by doing your own recalculation.
Both print(type(a)) and print(type(T)) enable you to print the types of the a and T
variables, as shown in the following examples:
<class 'sympy.core.symbol.Symbol'> #a
<class 'sympy.core.add.Add'> #T
You can use print(srepr(T)) to display the internal structure of the term T, as shown in
the following examples:
The different parenthetical levels can be visualized in a tree structure. For an example
of the visualization of a tree structure, refer to https://docs.sympy.org/latest/tutorials/
intro-tutorial/manipulation.html.
01 #02_mul.py
02 from sympy import *
03 a,b,c=symbols("a b c")
04 T1=2*a+4*b-5*c
212
5.1 Basic Mathematical Operations
05 T2=4*a+2*b+3*c
06 T=T1*T2
07 print("Products of the terms")
08 pprint(T)
09 print("Terms multiplied")
10 print(expand(T))
11 print("Formatted output")
12 pprint(expand(T))
Output
Analysis
In line 06, the multiplication operation is executed, but the result that is output in line
08 does not match the expectation. For the parentheses to be multiplied out, the T
object must be passed to the expand(T) method (line 12).
print(type(T)) allows you to output the type of the T variable: <class 'sympy.core.
mul.Mul'>.
01 #03_mul_linear_factors.py
02 from sympy import *
03 x=symbols("x")
04 lf=(x-1)*(x-2)*(x-3)*(x-4)
05 p=expand(lf)
06 print("\nThe multiplication of the linear factors")
07 pprint(lf)
213
5 Symbolic Computation Using SymPy
Output
Analysis
In line 05, the linear factors from line 04 are multiplied using the expand(lf) method
and stored in the p object. The output in line 09 confirms the expected result.
5.1.4 Division
Listing 5.4 demonstrates the division of terms. The symbolic division operation is per-
formed using the / operator.
01 #04_div.py
02 from sympy import *
03 a,b,c=symbols("a b c")
04 T1=2*a+4*b-5*c
05 T2=4*a+2*b+3*c
06 T3=5*a-3*b+4*c
07 T=T1/(T2*T3)
08 print("Division of terms")
09 pprint(T)
10 print("Terms multiplied")
11 print(expand(T))
Output
Division of terms
2・a + 4・b - 5・c
──────────────────────────────────────
(4・a + 2・b + 3・c)⋅(5・a - 3・b + 4・c)
Terms multiplied
214
5.1 Basic Mathematical Operations
Analysis
Line 07 performs the symbolic division operation. The expand(T) method is used to cal-
culate three fractions in line 11 with the denominator terms that have been multiplied
with each other. The example clearly shows how SymPy’s CAS methods can greatly
facilitate the necessary computational work.
If you output the type of the T variable via print(type(T)), you’ll obtain the following
output: <class 'sympy.core.mul.Mul'>. From this result, we can conclude that no Div()
method exists.
5.1.5 Exponentiation
Symbolic exponentiation can be illustrated through an example with the binomial for-
mula:
01 #05_binom.py
02 from sympy import *
03 a,b=symbols("a b")
04 for n in range(7):
05 p=(a+b)**n
06 print(expand(p))
Output
1
a + b
a**2 + 2*a*b + b**2
a**3 + 3*a**2*b + 3*a*b**2 + b**3
a**4 + 4*a**3*b + 6*a**2*b**2 + 4*a*b**3 + b**4
a**5 + 5*a**4*b + 10*a**3*b**2 + 10*a**2*b**3 + 5*a*b**4 + b**5
a**6 + 6*a**5*b + 15*a**4*b**2 + 20*a**3*b**3 + 15*a**2*b**4 +
6*a*b**5 + b**6
215
5 Symbolic Computation Using SymPy
Analysis
This program illustrates that symbolic exponentiation is also possible using SymPy. In
line 06, the expand(p) method calculates the powers of the binomials from line 05.
Again, this example shows the capabilities of SymPy’s CAS functionality.
If you output the type of the p variable via print(type(p)), you’ll obtain the following
output: <class 'sympy.core.power.Pow'>. Thus, a method exists for the exponentiation
operation. You can test this method using Pow(2,100), for example.
I R0 R1
U1 R2 U2
Each DC power transmission system consists of the internal resistance of the voltage
source R0, the conductor resistance R1, and the consumer resistance R2. Based on this
information, you can derive a formula to calculate the efficiency of the network. For the
total resistance, the following equation applies:
The total current Ig is calculated from the input voltage and the total resistance:
216
5.2 Multiplying Matrixes
Using these equations, you can obtain the formula for the efficiency:
SymPy performs these symbolic arithmetic operations, as shown in Listing 5.6, and
establishes a general formula for calculating the efficiency of a power transmission sys-
tem for direct current.
01 #06_efficiency.py
02 from sympy import *
03 R0,R1,R2,U1,U2=symbols("R0 R1 R2 U1 U2")
04 Rg=R0+R1+R2
05 Ig=U1/Rg
06 P1=Rg*Ig**2
07 P2=R2*Ig**2
08 eta=P2/P1
09 print(u"\N{GREEK SMALL LETTER ETA}= ",eta)
Output
η = R2/(R0 + R1 + R2)
Analysis
This example illustrates that you can use any identifier for the required symbolic vari-
ables (line 03). In lines 04 to 08, symbolic computations are executed according to the
specifications. Line 09 outputs the expected result.
217
5 Symbolic Computation Using SymPy
Listing 5.7 shows how to multiply matrixes symbolically with each other using SymPy.
01 #07_matrix_mul1.py
02 from sympy import *
03 a,b,c,d = symbols("a,b,c,d")
04 e,f,g,h = symbols("e,f,g,h")
05 A=Matrix([[a,b],
06 [c,d]])
07 B=Matrix([[e,f],
08 [g,h]])
09 C=A*B
10 D=B*A
11 print("Product A*B\n")
12 pprint(C)
13 print("\nProduct B*A\n")
14 pprint(D)
Output
Product A*B
⎡a・e + b・g a・f + b・h⎤
⎣c・e + d・g c・f + d・h⎦
Product B*A
⎡a・e + c・f b・e + d・f⎤
⎣a・g + c・h b・g + d・h⎦
Analysis
The symbolic definition of a matrix is performed in lines 05 and 07 using the Matrix
([[row1],[[row2]]) method. The multiplications of matrixes A and B in lines 09 and 10
show that the commutative law does not apply to matrixes.
If the transmission function and the input voltage are given, then you can calculate the
output voltage using the following equation:
218
5.2 Multiplying Matrixes
Figure 5.2 shows a catenary circuit consisting of three inductances as longitudinal links
and two capacitances as cross links. The terminating resistor R = 1 Ω is also a cross link.
L1 L3 L5
U1 C2 C4 R=1 U2
A catenary circuit is composed of n cross links and m longitudinal links. The chain
parameters of these links are shown in Table 5.5.
To calculate the resulting chain matrix, you must multiply the matrixes of the cross
links and longitudinal links with each other.
Listing 5.8 multiplies the individual partial matrixes with each other. The resulting
chain matrix and the denominator polynomial of the transmission function are out-
put.
01 #08_matrix_mul2.py
02 from sympy import *
03 s,L1,C2,L3,C4,L5 = symbols("s L1 C2 L3 C4 L5")
04 A1=Matrix([[1, L1*s],
05 [0, 1]])
06 A2=Matrix([[1, 0],
07 [C2*s,1]])
219
5 Symbolic Computation Using SymPy
08 A3=Matrix([[1,L3*s],
09 [0, 1]])
10 A4=Matrix([[1, 0],
11 [C4*s, 1]])
12 A5=Matrix([[1,L5*s],
13 [0, 1]])
14 A6=Matrix([[1, 0],
15 [1, 1]])
16 A=A1*A2*A3*A4*A5*A6
17 print("Chain parameters")
18 print(A)
19 print("Denominator polynomial of the transmission function")
20 print(expand(A[0,0]))
Output
Chain parameters
Matrix([[C2*L1*s**2 + C4*s*(L1*s + L3*s*(C2*L1*s**2 + 1)) + L1*s +
L3*s*(C2*L1*s**2 + 1) + L5*s*(C2*L1*s**2 + C4*s*(L1*s + L3*s*(C2*L1*s**2 + 1)) +
1) + 1, L1*s + L3*s*(C2*L1*s**2 + 1) + L5*s*(C2*L1*s**2 + C4*s*(L1*s +
L3*s*(C2*L1*s**2 + 1)) + 1)], [C2*L3*s**2 + C2*s + C4*s*(C2*L3*s**2 + 1) +
L5*s*(C2*s + C4*s*(C2*L3*s**2 + 1)) + 1, C2*L3*s**2 + L5*s*(C2*s +
C4*s*(C2*L3*s**2 + 1)) + 1]])
Denominator polynomial of the transmission function
C2*C4*L1*L3*L5*s**5 + C2*C4*L1*L3*s**4 + C2*L1*L3*s**3 + C2*L1*L5*s**3 +
C2*L1*s**2 + C4*L1*L5*s**3 + C4*L1*s**2 + C4*L3*L5*s**3 + C4*L3*s**2 + L1*s +
L3*s + L5*s + 1
Analysis
The definition of the matrixes for the individual elementary two-port networks is per-
formed in lines 04 to 15. The matrix multiplication is performed in line 16. Special care
must be taken to ensure that the order of the multipliers corresponds to the circuit’s
structure. The outputs show that such complex operations can hardly be performed
manually.
5.3 Equations
Systems of linear equations play a central role in the calculation of node voltages and
mesh currents in electrical networks and the distribution of forces in trusses. In math-
ematics, partial fraction decomposition and the solving of linear differential equations
with constant coefficients and higher orders also require equations to be solved.
220
5.3 Equations
The objects g1 to gn represent the rows of a linear system of equations. The variables x1
to xn are the unknowns.
The individual lines of the equation system, such as 7*x1+5*x2-2*x3+7*x4=9 are trans-
formed so that the row elements of the result vector are on the left side of the equation
system: 7*x1+5*x2-2*x3+7*x4-9=0, where the zero is no longer considered in the solve
method.
Listing 5.9 shows the implementation.
01 #09_solve1.py
02 from sympy import *
03 x1,x2,x3,x4 = symbols('x1 x2 x3 x4')
04 #linear system of equations
05 g1=7*x1+5*x2-2*x3+7*x4-9
06 g2=6*x1+3*x2-4*x3+6*x4-8
07 g3=3*x1+2*x2-5*x3+5*x4-4
08 g4=2*x1+9*x2-6*x3+3*x4-2
09 #solution
10 L=solve((g1,g2,g3,g4),x1,x2,x3,x4)
11 #Output
12 print("Solution set\n",L)
Output
Solution set
{x1: 431/305, x2: -34/305, x3: -19/305, x4: -4/61}
Analysis
The rows of the rearranged equation system are assigned to objects g1 to g4 in rows
05 to 08. The solution follows in line 10 using the solve((g1,g2,g3,g4),x1,x2,x3,x4)
method. The rows of the equation system are passed as tuples. Next, follow the param-
eters of the unknown variables x1 to x4 separated by commas. As expected, the solution
set does not consist of real numbers but of rational numbers. Consequently, SymPy
computes exactly in this case.
221
5 Symbolic Computation Using SymPy
R L
1 2
U0 C C R U2
From the circuit shown in Figure 5.3, you can use nodal analysis to set up a system of
equations for the node voltages U1,0 and U2,0, for example:
01 #10_solve2.py
02 from sympy import *
03 s,U0,U1,U2,R,C,L = symbols("s,U0,U1,U2,R,C,L")
04 #node equations
05 I1=(1/R+C*s+1/(L*s))*U1-U2/(L*s)-U0/R
06 I2=-U1/(L*s)+(1/R+C*s+1/(L*s))*U2
07 #solving the node equations
08 U=solve((I1,I2),U1,U2)
09 print(U)
Output
{U1:-L*U0*s*(C*L*R*s**2+L*s+R)/(R**2-(C*L*R*s**2+L*s+R)**2),
U2:-L*R*U0*s/(R**2-(C*L*R*s**2+L*s+R)**2)}
222
5.3 Equations
Analysis
In lines 05 and 06, the rows of the equation system are assigned to the I1 and I2 objects.
Line 08 computes the general solution of the equation system. The output of the node
voltages U1 and U2 is performed using the dictionary data structure.
01 #11_solve3.py
02 from sympy import *
03 a,b,x=symbols("a,b,x")
04 #Equations
05 y1=x**4-7*x**3-13*x**2+79*x+84
06 y2=log(sqrt(x)-2)-1
07 y3=exp(sqrt(x)-2)-1
08 y4=sinh(x)-10
09 y5=cosh(x)-10
10 #Outputs
11 print("Solutions")
12 print("f(x)=%s|f(x=0)=%s" %(y1,solve(y1,x)))
13 print("f(x)=%s|f(x=0)=%s" %(y2,solve(y2,x)))
14 print("f(x)=%s|f(x=0)=%s" %(y3,solve(y3,x)))
15 print("f(x)=%s|f(x=0)=%s" %(y4,solve(y4,x)))
16 print("f(x)=%s|f(x=0)=%s" %(y5,solve(y5,x)))
Output
Solutions
f(x)=x**4 - 7*x**3 - 13*x**2 + 79*x + 84|f(x=0)=[-3, -1, 4, 7]
f(x)=log(sqrt(x) - 2) - 1|f(x=0)=[(2 + E)**2]
f(x)=exp(sqrt(x) - 2) - 1|f(x=0)=[4]
f(x)=sinh(x)-10|f(x=0)=[log(-10+sqrt(101))+I*pi,
223
5 Symbolic Computation Using SymPy
log(10 + sqrt(101))]
f(x)=cosh(x) - 10|f(x=0)=[log(10 - 3*sqrt(11)),
log(3*sqrt(11) + 10)]
Analysis
The mathematical functions defined in lines 05 to 09 are solved and output in lines 12
to 16 using the solve() SymPy method. The outputs provide the exact values (i.e., no
floats).
In output I*pi, I stands for the imaginary unit. Using plot(yi,(x,ug,og)), you can use
the function plots to illustrate the solutions (intersections with the x-axis).
Using Listing 5.11, the denominator polynomial of the transmission function for a fifth-
degree low-pass filter can be calculated in the following way:
As a result of the coefficient comparison, you can obtain the following system of equa-
tions:
For ohmic resistance, the value of 1 Ω is set. The calculated values for the inductances
and capacitances have the units 1 H (Henry) and 1 F (Farad), respectively. Listing 5.12
solves the nonlinear equation system we’ve set up and outputs the values for the vari-
ous components as a dictionary.
224
5.3 Equations
01 #12_solve4.py
02 from sympy import *
03 s,L1,C2,L3,C4,L5 = symbols("s L1 C2 L3 C4 L5")
04 #Butterworth coefficients
05 a=[0,3.236,5.236,5.236,3.236,1]
06 #nonlinear system of equations
07 g1=L1+L3+L5-a[1]
08 g2=C2*L1 + C4*L1 + C4*L3-a[2]
09 g3=C2*L1*L3 + C2*L1*L5 + C4*L1*L5-a[3]
10 g4=C2*C4*L1*L3-a[4]
11 g5=C2*C4*L1*L3*L5-a[5]
12 components=solve((g1,g2,g3,g4,g5),L1,C2,L3,C4,L5,dict=True)
13 #Output
14 print(components[1])
15 print(components[1].keys())
16 print(components[1].values())
17 for item in components[1].items():
18 print("%s = %.3f"%item)
Output
Analysis
Line 05 stores Butterworth coefficients in array object a. The value zero was provided as
the first element to make how Python indexes arrays match the indexing of the Butter-
worth coefficients.
Lines 07 to 11 contain the individual equations of the nonlinear system of equations. In
line 12, the system of equations is solved. The last parameter (dict=True) specifies that
the solution set is stored as a dictionary in the components object.
225
5 Symbolic Computation Using SymPy
Line 14 outputs the complete solution set as a dictionary. Line 15 outputs the keys, and
line 16 outputs the dictionary values. The values for the individual components are
output in line 18.
The values for the components are again the values normalized to 1 Ω, 1 F, and 1 H.
01 #13_simplify.py
02 from sympy import *
03 a,b,c,n,x,y=symbols("a b c n x y")
04 #Terms
05 t1=exp(log(x)+log(y))
06 t2=n*x**n/x
07 t3=a**3/((a-b)*(a-c))+b**3/((b-c)*(b-a))+c**3/((c-a)*(c-b))
08 t4=2*sqrt(1/x)-1/sqrt(x)
09 t5=(y**2 + y)/(y*sin(a)**2 + y*cos(a)**2)
10 #Outputs
11 print("1: exp(log(x)+log(y)), simplified:",t1)
12 print("2:",t2,",simplified:",simplify(t2))
13 print("3:",t3,"\n simplified:",simplify(t3))
14 print("4:",t4,",simplified",simplify(t4))
15 print("5:",t5,",simplified:",simplify(t5))
226
5.5 Series Expansion
Output
Analysis
As expected, SymPy uses the simplify() method to simplify all terms correctly except
the third term. For the third term, 2*sqrt(1/x)-1/sqrt(x), the expected output is
1/sqrt(x). Maple, for example, provides this result. Strictly speaking, however, this
result is only correct if you assume that the positive sign of the root term was meant.
This problem is also pointed out in the SymPy documentation. SymPy automatically
simplifies the first term when output via print().
01 #14_series_expansion.py
02 from sympy import *
03 x=symbols('x')
04 n=10
05 a=cos(x).series(x,0,n)
06 b=(sin(x)*I).series(x,0,n)
07 c=exp(x*I).series(x,0,n)
08 d=a+b
09 #Output
10 print("Series expansion cos\n",a)
11 print("\nSeries expansion sin\n",b)
12 print("\nSeries expansion cos+sin\n",c)
13 print("\nSeries expansion e-function\n",d)
227
5 Symbolic Computation Using SymPy
Output
Analysis
In lines 06 and 07, the sine function and the exponential function are multiplied by the
imaginary unit I. In line 08, the addition of the series for the cosine and sine functions
takes place. The outputs in lines 10 to 13 confirm the results from the literature. The val-
ues for the members from line 12 match the values of the series members from line 13.
As expected, the imaginary unit I disappears in members with even exponents.
By calculating the main denominator, the rational function for the complex conduc-
tance is obtained for L1 = 3 H, L2 = 2 H, and L3 = 1 H:
228
5.6 Partial Fractions
R R R=1
Y(s)
L1 L2 L3
In network synthesis, transmission functions are given in this form. The individual
components of the circuit must then be calculated using partial fraction decomposi-
tion. How we’ve described how to calculate the transmission function from a given cir-
cuit serves only to illustrate the synthesis procedure and to check the results for circuit
synthesis. The reverse way of calculating the values for the components of a circuit
from a transmission function Y(s) or Z(s) is thus easier to understand.
In the next circuit, shown in Figure 5.5, three parallel resonant circuits are connected in
a series.
C1 C2 C3
Z(s) L1 L2 L3
From this circuit, you can easily determine the partial fractions for the transmission
function of the impedance:
229
5 Symbolic Computation Using SymPy
From the partial fractions for Y(s) and Z(s), Listing 5.15 calculates the rational functions
and, from the rational functions again, calculates the partial fractions through the
apart(rationalfunction) method. By comparing coefficients, the normalized values of
inductances and capacitances can be calculated.
01 #15_partial_fraction.py
02 from sympy import *
03 s=symbols("s")
04 #Parallel circuit from R-L series circuits
05 Yb1=1/(s+1)+1/(2*s+1)+1/(3*s+1)
06 Yp1=cancel(Yb1)
07 # Series circuit from L-C parallel circuits
08 Zb2=s/(2*s**2+1)+2*s/(3*s**2+1)+3*s/(4*s**2+1)
09 Zp2=cancel(Zb2)
10 #Calculation of partial fractions
11 pb1=apart(Yp1)
12 pb2=apart(Zp2)
13 #Output
14 print(Yp1,"=\n",pb1)
15 print("\n",Zp2,"=\n", pb2)
Output
By comparing the coefficients, you should obtain the following results for the induc-
tances:
For calculating impedance, the program calculates the three partial fractions:
230
5.7 Continued Fractions
By comparing the coefficients, you should obtain the following results for the induc-
tances and capacitances:
In lines 05 and 08, three partial fractions are provided in each case so you can more eas-
ily check the result of the partial fraction calculation step. In lines 06 and 09, these par-
tial fractions are multiplied with the cancel() method in such a way that the two
fractional rational functions Y(s) and Z(s) are obtained as a result. In lines 11 and 12, par-
tial fraction decomposition is then performed using the apart() method. The outputs
in lines 14 and 15 confirm the expected results.
R1 R3 R5
Rg R2 R4 R6
From this circuit, you can read the following continued fraction:
You can calculate the continued fraction for 37/14 using the following Euclidean algo-
rithm:
231
5 Symbolic Computation Using SymPy
By comparing the continued fraction decomposition of the circuit shown in Figure 5.6
with the continued fraction representation, you’ll obtain the values for the resistors, as
listed in Table 5.6.
R1 R2 R3 R4 R5 R6
2 1/1 1 1/1 2 2
Listing 5.16 uses the Euclidean algorithm to calculate the continued fraction for 37/14
and, from the coefficients of the continued fraction again, calculates the following con-
tinued fraction.
01 #16_continued_fraction1.py
02 from sympy import *
03 z=37 #numerator
04 n=14 #denominator
05 #Calculate continued fraction
06 def kb(z,n):
07 r=[]
08 while n>0:
09 r.append(z//n)
10 z=z%n
11 z,n=n,z
12 return r
13 #Conversion to fraction
14 def ikb(ls):
15 a = Integer(0)
16 for i in reversed(ls[1:]):
17 a=a+i
18 a=1/a
19 return ls[0] + a
20 #calculates continued fraction
21 kb1=kb(z,n)
232
5.7 Continued Fractions
22 #calculates fraction
23 ikb1=ikb(kb1)
24 #Output
25 print("Coefficients:",kb1)
26 print("Fraction:",ikb1)
Output
Coefficients: [2, 1, 1, 1, 4]
Fraction: 37/14
Analysis
In lines 06 to 12, a function is defined that calculates the coefficients for a continued
fraction according to the Euclidean algorithm. When calling function kb(z,n) in line 21,
the numerator z and the denominator n of the fraction are passed as parameters. The
ikb(ls) defined in lines 14 to 19 calculates the fraction again from the coefficients of the
continued fraction.
SymPy facilitates the calculation of a continued fraction to a great extent. Listing 5.17
uses the continued_fraction_periodic(z,n) method to calculate the coefficients of a
continued fraction, while it uses the continued_fraction_reduce(continued fraction)
method from the coefficients to calculate again the continued fraction.
01 #17_continued_fraction2.py
02 from sympy import *
03 z=37 #numerator
04 n=14 #denominator
05 #Calculate continued fraction
06 kb=continued_fraction_periodic(z,n)
07 #Calculate fraction
08 fraction=continued_fraction_reduce(kb)
09 #Output
10 print("Coefficients:",kb)
11 print("Fraction:",fraction)
Output
Coefficients: [2, 1, 1, 1, 4]
Fraction: 37/14
233
5 Symbolic Computation Using SymPy
Analysis
The numerator z and the denominator n of the fraction are passed to the continued_
fraction_periodic(z,n) method in line 06. The result of the continued fraction decom-
position step is stored in the kb object. This object is passed to the continued_fraction_
reduce(kb) method in line 08. The outputs confirm the expected result.
5.8 Limits
The concept of limits is fundamental to the understanding of differential and integral
calculus. By calculating the limit, the slope of the tangent (first derivative) can be calcu-
lated in general from the secant slope of a function. Many antiderivatives (integrals)
can also be calculated from the limits for the upper and lower sums. SymPy calculates
the limit of a sequence or function using the limit(f,x,g) method. The f object stands
for a mathematical sequence or function; x is the independent variable; and, for g, the
limit can be specified.
n 1 2 3 4 5 6 7 8 9 10
an 1 4 6 8 10 12 14 16 18 20
bn 1 4 9 16 25 36 49 64 81 100
dn -1.33 2.57 1.81 1.73 1.74 1.75 1.78 1.8 1.81 1.828
The sequences listed in Table 5.7 are in accordance with the following formation laws:
234
5.8 Limits
For this sequence, the formation law cannot be read from the value table. The limit of
this sequence for is the Eulerian number e.
Listing 5.18 calculates the limits of the following sequences: an, bn, cn, dn, and en.
01 #18_limit1.py
02 from sympy import *
03 n=symbols("n")
04 #Sequences
05 a1=2*n
06 a2=n**2
07 a3=1/n
08 a4=(2*n**3+2)/(n**3+n**2-5)
09 a5=(1+1/n)**n
10 #Output
11 print("Limits for n towards ∞")
12 print("Limit of %s is: %s" %(a1,limit(a1,n,oo)))
13 print("Limit of %s is: %s" %(a2,limit(a2,n,oo)))
14 print("Limit of %s is: %s" %(a3,limit(a3,n,oo)))
15 print("Limit of %s is: %s" %(a4,limit(a4,n,oo)))
16 print("Limit of %s is: %s" %(a5,limit(a5,n,oo)))
Output
Analysis
In lines 12 to 16, the limits of the sequences defined in lines 05 to 09 are calculated and
output using the limit(a1,n,oo) method. As a first parameter, this method expects the
name of the sequence, the second parameter specifies the variable of the limit, and the
third parameter specifies against which limit the sequence should strive. The outputs
confirm the expected results.
235
5 Symbolic Computation Using SymPy
01 #19_limit2.py
02 from sympy import *
03 a,x=symbols("a x")
04 #Functions
05 y1=sin(x)/x
06 y2=tan(x)/x
07 y3=a*(1-exp(-x))
08 y4=1/x+2
09 y5=(x**2-1)/(x-1)
10 #Output
11 print("Limit of %s toward 0 is: %s" %(y1,limit(y1,x,0)))
12 print("Limit of %s toward 0 is: %s" %(y2,limit(y2,x,0)))
13 print("Limit of %s toward ∞ is: %s" %(y3,limit(y3,x,oo)))
14 print("Limit of %s toward ∞ is: %s" %(y4,limit(y4,x,oo)))
15 print("Limit of %s toward 1 is: %s" %(y5,limit(y5,x,1)))
Output
Analysis
In lines 05 to 09, those functions are defined whose limits must be calculated using the
limit() method. The arguments are passed in the same order as the sequences. The
outputs in lines 11 to 15 again confirm the expected results.
236
5.8 Limits
01 #20_limit3.py
02 from sympy import *
03 a,x,h,n=symbols('a x h n')
04 f1_1=((x+h)**n-x**n)/h #power rule
05 f1_2=(a**(x+h)-a**x)/h #exponential function
06 f1_3=(sin(x+h)-sin(x))/h #trigonometric function
07 f1_4=(sinh(x+h)-sinh(x))/h #hyberbolic function
08 f1_5=(sin(x+h)*cos(x+h)-sin(x)*cos(x))/h #product rule
09 #Output
10 print("Limits for h toward zero")
11 print("limes",f1_1," = ",simplify(limit(f1_1,h,0)))
12 print("limes",f1_2," = ",simplify(limit(f1_2,h,0)))
13 print("limes",f1_3," = ",simplify(limit(f1_3,h,0)))
14 print("limes",f1_4," = ",simplify(limit(f1_4,h,0)))
15 print("limes",f1_5," = ",simplify(limit(f1_5,h,0)))
Output
limes (-x**n + (h + x)**n)/h = n*x**(n - 1)
limes (-a**x + a**(h + x))/h = a**x*log(a)
limes (-sin(x) + sin(h + x))/h = cos(x)
limes (-sinh(x) + sinh(h + x))/h = cosh(x)
limes (-sin(x)*cos(x) + sin(h + x)*cos(h + x))/h = cos(2*x)
Analysis
Lines 04 to 08 define five difference quotients known from school math. The limit()
method calculates the limits of these difference quotients for h toward zero in lines 11
to 15. These outputs have been simplified using simplify() but confirm the expected
results.
237
5 Symbolic Computation Using SymPy
5.9 Differentiation
SymPy can calculate the derivatives of functions using the diff(f,x,k) method, where
the f object is the function to be derived, x is defined as the independent variable, and
the natural number k stands for the k-th derivative of the function to be differentiated.
If k does not occur, SymPy calculates the first derivative. Listing 5.21 calculates the first
derivative for each of the functions.
It also calculates the first, second and third derivatives for the following function:
01 #21_differential.py
02 from sympy import *
03 x,a,b,A=symbols("x a b A")
04 y1=x**4-3*x**3+x**2-20 #power rule
05 y2=sin(x)*cos(x) #product rule
06 y3=(x**3-4*x+3)/(x+4) #quotient rule
07 y4=A*exp(-a*x)*sin(b*x) #chain rule
08 #Calculations and outputs
09 print("1st derivative of:",y1,"\n", diff(y1,x))
10 print("1st derivative of:",y2,"\n", diff(y2,x))
11 print("1st derivative of:",y3,"\n", diff(y3,x))
12 print("1st derivative of:",y4,"\n", diff(y4,x,1))
13 print("2nd derivative of:",y4,"\n", diff(y4,x,2))
14 print("3rd derivative of:",y4,"\n", diff(y4,x,3))
Output
1st derivative of: x**4-3*x**3+x**2-20
4*x**3 - 9*x**2 + 2*x
1st derivative of: sin(x)*cos(x)
-sin(x)**2 + cos(x)**2
1st derivative of: (x**3-4*x+3)/(x+4)
(3*x**2 - 4)/(x + 4) - (x**3 - 4*x + 3)/(x + 4)**2
1st derivative of: A*exp(-a*x)*sin(b*x)
-A*a*exp(-a*x)*sin(b*x) + A*b*exp(-a*x)*cos(b*x)
2nd derivative of: A*exp(-a*x)*sin(b*x)
A*(a**2*sin(b*x) - 2*a*b*cos(b*x) - b**2*sin(b*x))*exp(-a*x)
3rd derivative of: A*exp(-a*x)*sin(b*x)
238
5.9 Differentiation
Analysis
The function types defined in lines 04 to 07 were selected based on the fact that they
represent known differentiation rules. Hidden from the user, however, is whether
SymPy uses these rules at all. You can check from the results that SymPy has differen-
tiated correctly. But checking the higher derivatives of the damped sine function y4
requires some effort.
Alternatively, you can calculate the derivative of a function such as y=𝑓(x) using the fol-
lowing statements:
The Derivative(y,x) method initially accepts the term x**2 without checking whether
the derivative can be calculated at all (unevaluated derivative). Only the doit() method
checks (evaluates) whether the calculation can be performed, and the derivative is then
calculated if necessary. This delayed execution of a symbolic computation operation is
useful when a term can be simplified further.
01 #22_curve_sketching.py
02 from sympy import *
03 x=symbols("x")
04
239
5 Symbolic Computation Using SymPy
05 def f(x):
06 #y=4*x**3-16*x
07 #y=x**3-x**2-4*x+4
08 y=-x**4+20*x**2-64
09 return y
10 #Derivatives
11 f_1=diff(f(x),x,1)
12 f_2=diff(f(x),x,2)
13 x0=solve(f(x),x) #zeros
14 xe=solve(f_1,x) #extremums
15 xw=solve(f_2,x) #inflection points
16 #Output
17 print(f(x))
18 print("Zeros:",x0)
19 print("Extremums:",xe)
20 print("Inflection points",xw)
21 #plot(f(x),(x,-5,5))
Output
-x**4 + 20*x**2 - 64
Zeros: [-4, -2, 2, 4]
Extremums: [0, -sqrt(10), sqrt(10)]
Inflection points [-sqrt(30)/3, sqrt(30)/3]
Analysis
In lines 05 to 09, the function to be examined is defined. If the commented-out func-
tions are supposed to be tested, you merely need to remove the corresponding com-
ments. In lines 11 and 12, the diff method calculates the first and second derivatives.
Line 13 calculates the zeros of the polynomial using the solve method. In line 14, the
extremums are calculated by setting the first derivative equal to zero. Line 15 calculates
the inflection points by setting the second derivative equal to zero. A case distinction
was deliberately omitted in favor of better clarity. The “exact” numbers of the outputs
remind the user that the calculations were performed using a CAS.
5.10 Integrations
Integrations represent the inverse operations of differentiations. If you form the deriv-
ative of a function, then the original function is the antiderivative, that is, the integral
of the derived function. However, the reversal is not always clear. As a result, there is
not an antiderivative F for every function 𝑓.
240
5.10 Integrations
By rearranging according to dy and integrating on both sides, you obtain the following:
The product can be interpreted as the surface element dA. By summing up (i.e.,
through integration), you obtain the total surface under a curve.
You need integral calculus not only to calculate surfaces under nonlinear function
graphs but also to calculate line lengths and volumes and to solve differential equa-
tions.
01 #23_integral1.py
02 from sympy import *
03 x=symbols("x")
04 print("∫%sdx=%s" %(1/x,integrate(1/x)))
05 print("∫%sdx=%s" %(exp(-x),integrate(exp(-x))))
06 print("∫%sdx=%s" %(exp(-x)*x**2,integrate(exp(-x)*x**2)))
07 print("∫%sdx=%s" %(sin(x),integrate(sin(x))))
08 print("∫%sdx=%s" %(exp(-x)*sin(x),simplify(integrate(exp(-x)*sin(x)))))
09 #plot(exp(-x),integrate(exp(-x)),(x,0,10))
Output
∫1/xdx=log(x)
∫exp(-x)dx=-exp(-x)
∫x**2*exp(-x)dx=(-x**2 - 2*x - 2)*exp(-x)
∫sin(x)dx=-cos(x)
∫exp(-x)*sin(x)dx=-sqrt(2)*exp(-x)*sin(x + pi/4)/2
241
5 Symbolic Computation Using SymPy
Analysis
SymPy calculates the indefinite integrals (antiderivatives) in lines 04 to 08 using the
integrate() method. With the plot method, you can output the function graphs and
the graphs of the antiderivative.
Alternatively, you can also calculate integrals using the following statements:
01 #24_integral2.py
02 from sympy import *
03 x=symbols("x")
04 a,b=1,2 #lower limits
05 F1=integrate(1/x,(x,a,b))
06 F2=integrate(exp(-x),(x,0,oo))
07 F3=integrate(exp(-x)*x**2,(x,0,oo))
08 F4=integrate(sin(x),(x,0,pi))
09 F5=integrate(exp(-x)*sin(x),(x,0,oo))
10 #Output
11 print("∫%s from %s to %s = %s" %(1/x,a,b,F1))
12 print("∫%s from 0 to ∞ = %s" %(exp(-x),F2))
13 print("∫%s from 0 to ∞ = %s" %(exp(-x)*x**2,F3))
14 print("∫%s from 0 to π = %s" %(sin(x),F4))
15 print("∫%s from 0 to ∞ = %s" %(exp(-x)*sin(x),F5))
16 #plot(exp(-x)*sin(x),(x,0,10))
Output
242
5.10 Integrations
∫x**2*exp(-x) from 0 to ∞ = 2
∫sin(x) from 0 to π = 2
∫exp(-x)*sin(x) from 0 to ∞ = 1/2
Analysis
In lines 05 to 09, the definite integrals of the test functions are calculated using the
integrate(func,(x,a,b)) method. The func function is passed as the first argument, fol-
lowed by the integration variable x, the lower limit a, and the upper limit b. The integra-
tion variable and the integration limits must be placed in parentheses. Again, this
example shows that you can also use constants as integration limits, such as pi (π) and
oo (infinity).
I R
U0 C Uc
The electrical power p(t) is the product of the voltage and current waveforms:
243
5 Symbolic Computation Using SymPy
To calculate the stored electrical energy Wel, the surface area under the power curve
must be calculated, according to the following formula:
Listing 5.25 calculates the stored electrical energy for a capacitor with a capacitance of
1F connected to a 10V voltage source.
01 #25_integral3.py
02 from sympy import *
03 t = symbols('t')
04 U0=10
05 R,C =1,1
06 I0=U0/R
07 tau=R*C
08 uc=U0*(1-exp(-t/tau)) #voltage curve
09 ic=I0*exp(-t/tau) #current curve
10 p=uc*ic #electrical power
11 Wel=integrate(p,(t,0,oo)) #electrical energy
12 #Output
13 print("Stored el. energy:",Wel.evalf(3),"Ws")
14 plt=plot(uc,ic,p,(t,0,5*tau),show=False,legend=True)
15 plt[0].line_color = 'b'
16 plt[0].label='Voltage'
17 plt[1].line_color = 'r'
18 plt[1].label='Current'
19 plt[2].line_color = 'g'
20 plt[2].label='Power'
21 #plt.save('power.png')
22 plt.show()
Output
Figure 5.8 shows what the output looks like in the function plot.
Analysis
In line 08, the voltage curve is stored in the uc object. In line 09, the current curve is
stored in the ic object. To calculate the power curve, the only operation that needs to
be performed is the multiplication p=uc*ic in line 10. Notice that—unlike what was nec-
essary with NumPy—no array needs to be defined to make all power values available
244
5.11 Differential Equations
for the integration step in line 11. Also, the upper integration limit for this numerical
problem is not five times the time constant, as is usually the case, but oo (∞). As a result,
the program calculates the “exact” value for the stored electrical energy with 50 Ws
without rounding errors.
To illustrate this result, in line 14, the voltage, the current, and the power curve are
stored in the plt variable. To display all three function plots in one diagram, you must
set the show=False parameters in the plot method. Then, all three function plots will be
saved into a list, and you can assign an individual color to each function plot. The
stored electrical energy corresponds to the surface area below the green curve.
In line 21, you can save the graphic. Supported file formats include eps, jpeg, jpg, pdf,
pgf, png, ps, raw, rgba, svg, svgz, tif, tiff, and webp.
The function 𝑓(x) can be any continuous function or a constant. The function g(x) is
referred to as a perturbation function.
245
5 Symbolic Computation Using SymPy
Thus, the listed energy stores differentiate the angular velocity of the rotating
mass, the voltage across a capacitor, and the current i(t) flowing through a coil.
This relationship seems trivial. But real systems can be quite complex. The art of mod-
eling a complex technical system consists of setting up the adequate differential equa-
tions. For this task, you need extensive expertise and a lot of hands-on experience.
Once you have found the differential equation, SymPy does the often-tedious work of
solving it for you. SymPy solves differential equations using the dsolve(dgl,y) method.
Note
SymPy can only solve linear differential equations.
246
5.11 Differential Equations
At any time during the discharging process, the capacitor voltage must be equal to the
voltage drop across the resistor:
When the capacitor is discharged, the current flows in the opposite direction than
during the charging process, which is why the current receives a negative sign. By sub-
stituting the capacitor current in the output equation, you can obtain a first-order dif-
ferential equation:
By rearranging and using the time constant , we obtain the following equation:
The integration constant K1 must not become zero because it represents the initial con-
dition U0. You must exponentiate both sides of the equation with the base e, and you’ll
get the general solution of the differential equation with the new integration constant
K:
At the point at which the discharging operation starts, the capacitor is charged to the
voltage . Thus, for the initial condition, applies:
If you connect a series circuit of capacitor and resistor to a voltage source with voltage
U0, you obtain the following differential equation:
247
5 Symbolic Computation Using SymPy
By exponentiating on both sides, you can thus obtain the general solution of the differ-
ential equation:
And for the initial condition , you can obtain the special solution:
For any first-order linear differential equation with perturbation function g(x)
you can find the following general solution formula in the literature:
These voltage sources represent the perturbation functions of the differential equation
and are called perturbation elements.
01 #26_first_order_differential_equation.py
02 from sympy import *
03 t=symbols("t")
04 u=Function("f")(t)
05 R,C=1,2
06 U0=10
07 tau=R*C
08 #first-order differential equation
09 dgl1=tau*Derivative(u,t)+u
10 dgl2=tau*Derivative(u,t)+u-U0
11 dgl3=tau*Derivative(u,t)+u-U0*t
12 dgl4=tau*Derivative(u,t)+u-U0*exp(t)
13 dgl5=tau*Derivative(u,t)+u-U0*sin(t)
248
5.11 Differential Equations
Output
Eq(f(t), C1*exp(-t/2))
Eq(f(t), C1*exp(-t/2) + 10)
Eq(f(t), C1*exp(-t/2) + 10*t - 20)
Eq(f(t), (C1 + 10*exp(3*t/2)/3)*exp(-t/2))
Eq(f(t), C1*exp(-t/2) + 2*sin(t) - 4*cos(t))
Analysis
In line 04, the Function("f")(t) method specifies that the dependent variable u should
be a function of time t. In lines 09 to 13, the differential equations are defined and
assigned to the dgl1 to dgl5 objects. The definition of a differential equation is per-
formed using the Derivative(u,t) method. This method represents the first derivative
of the differential equation. The independent variable u is noted as the first parameter
and the dependent variable t is noted as the second parameter. The first derivative is
followed by the independent variable u. Thus, SymPy’s syntax formally corresponds to
the usual mathematical notation for a differential equation. The perturbation func-
tions must all have a negative sign because they have been moved from the right-hand
side of the differential equation to the left-hand side.
In lines 15 to 19, the dsolve(dgl,u) method solves the five differential equations. This
method is passed the dgl object as the first parameter. Instead of the dgl object, the
complete notation of the differential equation could also be written in this case. As a
second parameter, dsolve() expects the independent variable u. The solutions are
stored in objects L1 to L5.
The outputs (line 21) confirm the expected solutions, which are performed using the
Eq(f(t),...) method, where the abbreviation Eq stands for equation. C1 stands for the
integration constant—not be confused with a capacitance C. A procedure for determin-
ing this integration constant is described next.
249
5 Symbolic Computation Using SymPy
I R L
U1 C Uc
Using the mesh rule for voltage drops, we can read the following from the circuit:
Due to the law of induction and the voltage drop across the ohmic resistor, we obtain
the following:
You can use the characteristic equation to find the general solution of the differential
equation:
250
5.11 Differential Equations
The special solution is the total of the homogeneous and the inhomogeneous solu-
tions:
Depending on how large the values for R, L and C turn out, you have to distinguish
three cases:
쐍 Case 1: If the following applies:
Then the differential equation gets the general solution with the following:
Then the differential equation looks as follows where parameter a must always have
a negative value:
Listing 5.27 calculates the voltage across the capacitor for a series resonant circuit for all
three cases:
01 #27_second_order_differential_equation.py
02 from sympy import *
03 t=symbols("t")
04 u=Function("f")(t)
05 U1=10 #input voltage
06 R1,L1,C1=5/2,1,1
07 R2,L2,C2=2,1,1
08 R3,L3,C3=2,1/2,1/4
09 #second-order differential equation
10 dgl1=L1*C1*Derivative(u,t,t)+R1*C1*Derivative(u,t)+u-U1
11 dgl2=L2*C2*Derivative(u,t,t)+R2*C2*Derivative(u,t)+u-U1
12 dgl3=L3*C3*Derivative(u,t,t)+R3*C3*Derivative(u,t)+u-U1
13 #solution of the differential equation
14 L1=dsolve(dgl1,u)
251
5 Symbolic Computation Using SymPy
15 L2=dsolve(dgl2,u)
16 L3=dsolve(dgl3,u)
17 #Output of the solution
18 print(L1,"\n",L2,"\n",L3)
Output
Analysis
In lines 06 to 08, the values for R, C, and L were chosen so that all three possible solu-
tions occur. To make clear that the solution of the differential equation is a symbolic
procedure, I deliberately avoided choosing real numbers.
The definitions of the differential equations in lines 10 to 12 follow the same pattern as
shown in Listing 5.26, the only difference being that, for the second derivative in the
Derivative(u,t,t) method, the independent variable t must occur twice.
The calculated solutions meet the expectations. C1 and C2 are again the integration con-
stants.
252
5.11 Differential Equations
By comparing coefficients, you can obtain the following result for the angular fre-
quency:
SymPy also provides a different syntax for the notation of the differential equation:
dgl=Eq(uc(t).diff(t,2)+2*D*w0*uc(t).diff(t,1)+w0**2*uc(t),Us)
If initial conditions must be taken into account, then you must pass the ics={…}
parameter to the dsolve() method in addition to the dgl object:
dsolve(dgl,uc(t),ics=aw)
The abbreviation ics stands for initial conditions. SymPy sets the initial conditions with
a dictionary:
aw={uc(0):0, uc(t).diff(t,1).subs(t,0):0}
For the differential equation solved using Listing 5.28, the initial conditions
and are valid.
01 #28_differential_equation_special_solution.py
02 from sympy import *
03 t=symbols("t")
04 uc=Function("uc")
05 U1=10
06 R=2
07 L=1/2
08 C=1/4
09 Us=U1/(L*C) #perturbation function
10 w0=sqrt(1/(L*C)) #angular frequency
11 D=R/(2*L*w0) #damping
12 dgl=Eq(uc(t).diff(t,2)+2*D*w0*uc(t).diff(t,1)+w0**2*uc(t),Us)
13 #initial values
14 aw={uc(0):0, uc(t).diff(t,1).subs(t,0):0}
15 ua_t=dsolve(dgl,uc(t)) #general solution
16 us_t=dsolve(dgl,uc(t),ics=aw)#special solution
17 uc_t=us_t.rhs
18 #plot(uc_t,(t,0,5))
19 #Output
20 print("general solution\n",ua_t)
253
5 Symbolic Computation Using SymPy
21 print("special solution\n",us_t)
22 print("right side of function uc(t) =",uc_t)
Output
general solution
Eq(uc(t), (C1*sin(2.0*t) + C2*cos(2.0*t))*exp(-2.0*t) + 10.0)
special solution
Eq(uc(t), (-10.0*sin(2.0*t) - 10.0*cos(2.0*t))*exp(-2.0*t) + 10.0)
right side of function
uc(t) = (-10.0*sin(2.0*t) - 10.0*cos(2.0*t))*exp(-2.0*t) + 10.0
Analysis
In line 12, the differential equation is defined using the diff() method within the Eq()
method and assigned to the dgl object. The searched uc(t) function is linked via the dot
operator with the diff() method to uc(t).diff(t,2). The first parameter is the inde-
pendent variable t, and the second parameter specifies which derivative is meant. A 2
represents the second derivative, while a 1 represents the first derivative. The parame-
ters for the damping and the angular frequency of the differential equation are
adopted according to the mathematical representation. The perturbation function Us is
the second parameter, separated by a comma, after the definition of the differential
equation in the parentheses of the Eq() method. The initial values are set as a dictio-
nary in line 14 and assigned to the aw object. For t = 0, the first derivative of the capacitor
voltage and the voltage across the capacitor should be zero. uc(0) and uc(t).diff(t,1).
subs(t,0) are the keys of the dictionary.
In line 16, the dsolve(dgl,uc(t),ics=aw) method solves the differential equation with
the given initial values. The third parameter contains the initial values: The ics object
is assigned the initial values aw, where ics is supposed to stand for initial conditions.
In line 17, rhs (right hand side) is used to determine the right-hand side of the function
equation uc = 𝑓(t). If you remove the comment in line 18, you can also display the course
of the function graphically.
In lines 20 and 21, the general and the special solutions are output. In line 22, the right-
hand side of the function equation is output.
254
5.12 Laplace Transform
set up algebraic equations according to the computational rules of the Laplace trans-
form or according to the theorems of network theory in the image. The solution in the
image is an algebraic function consisting of a numerator and denominator polyno-
mial. This algebraic function is then transformed back into the time domain using cor-
respondence tables.
Three steps are required to solve this equation and any other differential equation of
this type:
1. Transforming the differential equation into the image
2. Solving the differential equation in the image
3. Transforming the image function back to the original domain
255
5 Symbolic Computation Using SymPy
The perturbation functions calculated using SymPy are listed in Table 5.8.
Table 5.8 Correspondence Table for Transforming the Perturbation Functions into the Image
Domain
256
5.12 Laplace Transform
Step 3: Transforming the Image Function Back into the Time Domain
The inverse Laplace transform is performed using the following inverse integral:
SymPy calculates the inverse Laplace transform using the following method:
>>>inverse_laplace_transform(Fs,s,t)
Except for equation No. 9 from Table 5.9, all transforms from the image domain to the
time domain were performed using SymPy and compared to the data from the litera-
ture.
Table 5.9 Correspondence Table for the Transformation into the Time Domain
257
5 Symbolic Computation Using SymPy
SymPy calculates all transformations of the image functions 1 through 8 (see Table 5.9)
without any problem in a runtime that’s still acceptable. However, SymPy fails at the
transformation of the third-degree polynomial (No. 9) with the symbolic variables D
and . If, on the other hand, numerical values are used for the damping and the angu-
lar frequency, SymPy provides the correct result, as the following example shows.
Putting the individual transforms together yields the image function of the differential
equation:
By rearranging, you obtain the image function with the values for the components R =
2Ω, L = ½H and C = ¼F:
258
5.12 Laplace Transform
The transformation back into the time domain is done using equation No. 9 from the
correspondence table:
By inserting it into the time function from the correspondence table, the time function
is then obtained for the voltage curve at the capacitor:
SymPy calculates the following for the transformation back into the time domain:
By rearranging, you can obtain the output voltage U2(s) in the image domain:
259
5 Symbolic Computation Using SymPy
R sL
Based on the voltage divider rule, the transmission function H(s) shown in Figure 5.10
can be set up with the following equation:
In the image, the current I(s) can also be calculated using Ohm’s law:
The transformation back into the time domain is performed using the following
method:
inverse_laplace_transform(F(s),s,t)
Listing 5.29 calculates the step response of the output voltage and the capacitor current
for the circuit from Figure 5.10 using the inverse Laplace transform method.
01 #29_inv_laplace1.py
02 from sympy import *
03 s,C,L,R = symbols("s C L R")
04 t = symbols("t",positive=True)
05 U1=10
260
5.12 Laplace Transform
06 R=2
07 L=Rational(1,2)
08 C=Rational(1,4)
09 U1_s=U1/s
10 Z_s=R+L*s+1/(C*s)
11 I_s=U1_s/Z_s
12 H_s=1/(R + L*s + 1/(C*s))/(C*s)
13 H_s=expand(H_s) #transmission function
14 U2_s=U1_s*H_s #step response
15 uc=inverse_laplace_transform(U2_s,s,t)
16 ic=inverse_laplace_transform(I_s,s,t)
17 #Outputs
18 print("Transmission function\n",H_s)
19 print("Voltage at capacitor\n","uc =",simplify(uc))
20 print("Capacitor current\n","ic =",ic)
21 plt=plot(uc,ic,(t, 0, 5),show=False)
22 plt[0].line_color = 'b'
23 plt[1].line_color = 'r'
24 plt.show()
Output
Transmission function
4/(s**2/2 + 2*s + 4)
Voltage at capacitor
uc = 10 - 10*sqrt(2)*exp(-2*t)*sin(2*t + pi/4)
Capacitor current
ic = 10*exp(-2*t)*sin(2*t)
Analysis
In line 03, the symbols for the components and the variables for the time and image
domains are defined. The positive=True parameter causes the suppression of the out-
put of the Heaviside(t) expression. In line 05, the level of the voltage jump for the
input voltage is set to 10V.
In lines 06 to 08, you can enter other values for the components. You can use only val-
ues of the int type. For example, if you assign the value 2. to the R variable, the program
will not be executed. If you want to assign rational numbers as values to the compo-
nents, you must convert them into a fraction using the Rational(numerator,denomina-
tor) method. You can use print(type(L)) to display the type of the variable L: <class
'sympy.core.numbers.Half'>.
261
5 Symbolic Computation Using SymPy
Line 09 defines the step function for the image. Line 10 shows the total resistance in the
image. The current that flows through all components in the image (series connection)
is calculated in line 11 using Ohm’s law. Line 12 contains the transmission function. It
was deliberately not brought to the usual form as a fractional rational function to show
that SymPy can cope with elementary terms (double fractions, partial fractions). In line
13, the expand() method converts the term from line 12 into the common fractional
rational function. In line 14, the step response in the image is calculated. In lines 13 and
14, the transformation from the image domain to the time domain is performed using
the inverse_laplace_transform(F(s),s,t) method.
The results calculated by the inverse Laplace transform for the current and voltage
curves at the capacitor are also output as a function plot (lines 21 to 24).
This example clearly shows how effectively a step response can be simulated using the
inverse Laplace transform: The transmission function is set up directly in the image
domain according to the rules of network theory, multiplied by the image function 1/s
for the unit step, and then transformed into the time domain using the inverse_
laplace_transform() method. As elegant and effective as this procedure seems, it
unfortunately comes up against SymPy’s limited resources, as the next project task
example shows.
262
5.13 Project Task: Step Response of a Catenary Circuit
which means that the circuit is a fifth-order low-pass filter. For the internal resistance
of the voltage source and the terminating resistor, R = 1Ω is specified in each case.
R L L
U1(s) C C C R U2(s)
Listing 5.30 calculates the transmission function for the circuit shown in Figure 5.12
using symbolic matrix multiplication from the elementary two-port elements of the
catenary circuit (longitudinal and cross links). SymPy calculates the course of the out-
put voltage (step response) using the inverse Laplace transform.
01 #30_inv_laplace2.py
02 from sympy import *
03 s,C,L,R = symbols("s C L R")
04 t = symbols("t",positive=True)
05 #Values of the components
06 R=1
07 L=5
08 C=10
09 #Matrixes of the longitudinal and cross links
10 A1=Matrix([[1, R],
11 [0, 1]])
12 A2=Matrix([[1, 0],
13 [C*s,1]])
14 A3=Matrix([[1,L*s],
15 [0, 1]])
16 A4=Matrix([[1, 0],
17 [C*s, 1]])
18 A5=Matrix([[1, L*s],
19 [0, 1]])
20 A6=Matrix([[1, 0],
21 [C*s,1]])
22 A7=Matrix([[1, 0],
23 [1/R,1]])
263
5 Symbolic Computation Using SymPy
24 #Matrix multiplication
25 A=A1*A2*A3*A4*A5*A6*A7
26 #Transmission function
27 H_s=1/A[0,0]
28 U2_s=H_s/s
29 u2=inverse_laplace_transform(U2_s,s,t)
30 #Outputs
31 print("Transmission function\n",expand(H_s))
32 plot(u2,(t,0,100))
Output
Transmission function
1/(25000*s**5+5000.0*s**4+2250.0*s**3+300.0*s**2+40.0*s+2.0)
Analysis
The circuit consists of three capacitors as cross links and two coils as longitudinal links,
which can store electrical energy and magnetic energy, respectively. Thus, the denom-
inator polynomial of the transmission function is a fifth-degree polynomial.
264
5.14 Project Task: Bending a Beam That Is Fixed at One End
This project task combines all the programming techniques covered so far: the sym-
bolic multiplication of matrixes and the inverse Laplace transform. The matrixes of the
A parameters for the longitudinal and cross links are defined in lines 10 to 23. The mul-
tiplication is performed in line 25. For the calculation of the transmission function in
line 27, only the A[0,0] parameter is needed. In line 28, the transmission function is
multiplied by the step function of the unit step, 1/s. The transformation to the time
domain is carried out in line 29.
When running the program, the long translation time shows that SymPy has almost
reached its limits when transforming a fifth-degree transmission function into the
time domain.
To ensure that the entire cross-sectional area is covered, you must integrate in the y
and z directions. For this reason, you must compute a double integral in the following
way:
265
5 Symbolic Computation Using SymPy
dA
01 #31_moment_of_area.py
02 from sympy import *
03 Iy,y,z,h,b =symbols('Iy,y,z,h,b')
04 Iy=z**2
05 zI=integrate(Iy,(y,-b/2,b/2),(z,-h/2,h/2))
06 print("Moment of area\n Iy =",zI)
Output
Moment of area
Iy = b*h**3/12
266
5.14 Project Task: Bending a Beam That Is Fixed at One End
Analysis
In line 03, the symbolic variables are declared. Line 04 contains the calculation rule for
the second moment of area. In line 05, the integration is carried out using the inte-
grate(Iy,(y,-b/2,b/2),(z,-h/2,h/2)) method. First, the inner integral with the inte-
gration variable y and then the outer integral with the integration variable z are
inserted. The integrals must be enclosed in parentheses or square brackets with their
lower and upper limits. If you swap the order of inner and outer integrals, you’ll get the
same result.
For the calculation of bending girders, a common practice is to choose a coordinate sys-
tem where the x-axis points in the direction of the beam. The y-axis is perpendicular to
the drawing plane, and the z-axis points downwards.
l–x F
267
5 Symbolic Computation Using SymPy
The deflection w(x) depends on the modulus of elasticity E, the second moment of area
ly, and the bending moment M. The bending moment, acting at point x, is described by
the following linear equation:
The bending moment is greatest at the fastening point x = 0. Its magnitude decreases
linearly down to the point x = l, where its value is zero.
If you insert this torque equation into the above differential equation, you obtain the
following:
This differential equation can be solved simply by integrating twice. Due the two
boundary conditions w(0) = 0 und w´(0) = 0, you can determine the special solution.
Listing 5.32 calculates the general and the special solutions of the differential equation.
The course of the bending line is also shown as function plot w = 𝑓(x). For the elasticity
modulus, let’s use the value for steel E = 2,1 ⋅ 105 N/mm2 (see line 21).
01 #32_bending_line.py
02 from sympy import *
03 F,Iy,E,l=symbols('F,Iy,E,l')
04 x = symbols('x')
05 w = Function('w')(x)
06 #Inputs
07 b=20 #width in mm
08 h=30 #height in mm
09 lx=1e3 #length in mm
10 Fz=1e2 #force in N
11 #solution of the differential equation
12 dgl=Eq(w.diff(x,2),F/(E*Iy)*(l-x))
13 aL=dsolve(dgl) #general solution of the differential equation
14 rb={ #boundary conditions
15 w.subs(x,0):0,
16 w.diff(x,1).subs(x,0):0
17 }
18 sL=dsolve(dgl,ics=rb) #special solution
19 rL=sL.rhs #right-hand side of the equation
20 mL=sL.rhs.subs(x,l)
21 wmax=mL.subs(F,Fz).subs(l,lx).subs(E,2.1e5).subs(Iy,b*h**3/12)
22 wx=rL.subs(F,Fz).subs(l,lx).subs(E,2.1e5).subs(Iy,b*h**3/12)
23 #Outputs
24 print("Width b =",b, "mm")
25 print("Height h =",h, "mm")
26 print("Length l =",lx, "mm")
268
5.14 Project Task: Bending a Beam That Is Fixed at One End
Output
Width b = 20 mm
Height h = 30 mm
Length l = 1000.0 mm
Force F = 100.0 N
General solution
Eq(w(x), C1 + C2*x + F*l*x**2/(2*E*Iy) - F*x**3/(6*E*Iy))
special solution
Eq(w(x), F*l*x**2/(2*E*Iy) - F*x**3/(6*E*Iy))
Right-hand side of the equation
w(x) = F*l*x**2/(2*E*Iy) - F*x**3/(6*E*Iy)
w(x) = -1.7636684303351e-9*x**3 + 5.29100529100529e-6*x**2
Maximum deflection
w(x=l) = F*l**3/(3*E*Iy) = 3.53 mm
269
5 Symbolic Computation Using SymPy
Analysis
In line 03, the symbolic variables are determined. A separate line is reserved for the
independent variable x to highlight its importance (line 04). Line 05 specifies the func-
tional relationship between the deflection w and the variable x.
In lines 07 to 09, you can change the geometric data defining the beam. Line 10 speci-
fies the load Fz at the end of the beam in the direction of the z-axis.
Line 12 contains the mathematical term of the differential equation; it is assigned to the
dgl object. In line 13, the dsolve(dgl) method solves the differential equation to derive
its general solution.
In lines 14 to 17, the boundary conditions are specified for the deflection at point x = 0,
i.e., w(0) = 0, and for the first derivative at point x = 0, i.e., w'(0) = 0. They are stored in a
dictionary—{w.subs(x,0):0, w.diff(x,1).subs(x,0):0}. In line 18, the dsolve() method
calculates the special solution of the differential equation. The expression ics (initial
conditions) stands for the boundary conditions of the differential equation.
Using rhs (right-hand side) the right-hand side of the special solution sL is determined
(line 19). Lines 20 and 21 use the mL object to calculate the maximum deflection of the
beam at point x = l. In line 22, the course of the function, w = 𝑓(x)is calculated using the
notation rL.subs(variable,value).
In line 33, the plot data is stored in the p object. The p.show() method causes the graphic
to be displayed on the screen (line 36). You can also use the p object to save the function
plot in a file format of your choice (png or svg) (lines 34 and 35). To determine which file
formats are still supported, you can try saving the graphic in gif format. This format is
not supported, and you’ll receive an error message indicating what other file formats
are available as storage options.
In a chemical reaction, substance A (the reactant) gives rise to substance B (the interme-
diate), which in turn gives rise to substance C (the product). Each substance has the
molar concentration c at a certain point in time. This value is the ratio of the amount of
substance n and the volume V of the substance and is defined in the following way:
270
5.15 Project Task: Reaction Kinetics
The rates at which the reactions proceed are determined by the reaction rate constant
k. Its unit is s-1.
A consecutive chemical reaction produces substance B from substance A; and sub-
stance C, from substance B. During the course of this reaction, the molar concentra-
tions cA, cB, and cC of these reactants change. The reaction rate constants k1 and k2
influence the time course of the molar concentrations. This kinetic reaction can be
described by the following linear differential equation system:
Using the example of the breeding of plutonium-239 from uranium-238, let’s look at
how such a differential equation system can be solved using SymPy. For the start reac-
tion, the following applies:
The half-lives are indicated above the arrows. From the half-lives, the rate constants can
be calculated:
Then you obtain the following values for the rate constants k1 and k2:
In Listing 5.33, the dsolve_system(equations) SymPy method solves the linear differen-
tial equation system for the breeding of plutonium-239 from uranium-238. In lines 08
and 09, you can enter the corresponding rate constants for other consecutive reac-
tions.
01 #33_differential_equation_system.py
02 from sympy import symbols,Eq,Function,plot,N
03 from sympy.solvers.ode.systems import dsolve_system
04 t = symbols("t")
05 cA = Function("cA") #mol/dm^3
06 cB = Function("cB")
07 cC = Function("cC")
271
5 Symbolic Computation Using SymPy
08 k1=0.0295 #1/min, U in Np
09 k2=2.0483e-4 #1/min, Np in Pu
10 #differential equation system
11 dgl1=Eq(cA(t).diff(t,1),-k1*cA(t)) #reactant
12 dgl2=Eq(cB(t).diff(t,1), k1*cA(t)-k2*cB(t)) #intermediate
13 dgl3=Eq(cC(t).diff(t,1), k2*cB(t)) #product
14 #initial values
15 aw={
16 cA(0): 1,
17 cB(0): 0,
18 cC(0): 0
19 }
20 #Solution of the differential equation system
21 equations = [dgl1,dgl2,dgl3]
22 aL=dsolve_system(equations) #general solution
23 sL=dsolve_system(equations,ics=aw) #special solution
24 gA=sL[0][0].rhs #reactant
25 gB=sL[0][1].rhs #intermediate
26 gC=sL[0][2].rhs #product
27 #Outputs
28 print("General solution\n",aL)
29 print("Special solution")
30 print("cA(t) =",N(gA,3))
31 print("cB(t) =",N(gB,3))
32 print("cC(t) =",N(gC,3))
33 p=plot(gA,gB,gC,(t,0,600),show=False,legend=True)
34 p.title='Consecutive reaction'
35 p.xlabel='t in min'
36 p.ylabel='Concentration'
37 p[0].line_color='blue'
38 p[0].label='Uranium'
39 p[1].line_color='green'
40 p[1].label='Neptunium'
41 p[2].line_color='red'
42 p[2].label='Plutonium'
43 p.show()
Listing 5.33 Solving a Linear Differential Equation System of Equations Using SymPy
Output
General solution
[[Eq(cA(t), 143.021871796124*C1*exp(-0.0295*t)), Eq(cB(t),
-144.021871796124*C1*exp(-0.0295*t) - 1.0*C2*exp(-0.00020483*t)), Eq(cC(t),
1.0*C1*exp(-0.0295*t) + 1.0*C2*exp(-0.00020483*t) + 1.0*C3)]]
272
5.15 Project Task: Reaction Kinetics
Special solution
cA(t) = 1.0*exp(-0.0295*t)
cB(t) = -1.01*exp(-0.0295*t) + 1.01*exp(-0.00020483*t)
cC(t) = 1.0 + 0.00699*exp(-0.0295*t) - 1.01*exp(-0.00020483*t)
Analysis
In line 02, all methods necessary for the program are imported. Line 03 imports the
dsolve_system method, which is to solve the differential equation system. We set t as
the independent variable (line 04). In lines 05 to 07, the concentrations cA, cB and cC are
declared as functions.
In lines 08 and 09, you can assign different values to the rate constants k1 and k2 for
other consecutive reactions.
Lines 11 to 13 contain the terms of the differential equations of the differential equation
system. The data of the individual differential equations are stored in objects dgl1, dgl2,
and dgl3.
In lines 15 to 19, the initial values for the individual concentrations are specified. Only
reactant A has an initial value. The concentrations of intermediate B and product C are
zero at the beginning of the reaction.
In line 21, the dgl1, dgl2, and dgl3 objects are combined into a list and stored in the equa-
tions object.
273
5 Symbolic Computation Using SymPy
In lines 22 and 23, the dsolve_system(equations) method calculates the general and spe-
cial solutions of the differential equation system.
In lines 24 to 26, the data of the right-hand side of the equation is determined and
stored in objects gA, gB, and gC. The print function outputs the general and special solu-
tions stored in these objects in lines 28 to 32.
In line 33, the data of the function plot is stored in the p object. Lines 34 to 42 use this
object to specify the colors of the function graphs and the labels for the legend.
x1 x2
The dual mass oscillator consists of two masses (m1 and m2), which are connected (cou-
pled) by three springs. The vibration behavior of the system is influenced by the
masses m, the spring constants c, and the damping d.
The vibration behavior of the dual mass oscillator can be described by the following dif-
ferential equation system (see Vöth: 80):
The dsolve_system() SymPy method calculates the deflection x(t), the velocity v(t), and
the acceleration of the masses a(t). You can pass the left-hand side of the equation
terms to this method as arguments without transformations.
Listing 5.34 solves the differential equation system of the dual mass oscillator and dis-
plays the deflections x1 and x2 of the masses graphically on the screen.
01 #34_differential_equation_dual_mass_oscillator.py
02 from sympy import symbols,Eq,Function,plot,N
03 from sympy.solvers.ode.systems import dsolve_system
04 t = symbols("t")
274
5.16 Project Task: Dual Mass Oscillator
05 x1 = Function("x1")(t)
06 x2 = Function("x2")(t)
07 m=1000 #kg
08 c=1e7 #N/m
09 d=1e3 #kg/s
10 m1,m2=m,2*m
11 c1,c2,c3=c,c,c
12 d1,d2,d3=d,d,d
13 #differential equation system
14 dgl1=Eq(m1*x1.diff(t,2)+d1*x1.diff(t,1)+d2*(x1.diff(t,1)\
15 -x2.diff(t,1))+c1*x1+c2*(x1-x2),0)
16 dgl2=Eq(m2*x2.diff(t,2)+d2*(x2.diff(t,1)-x1.diff(t,1))\
17 +d3*x2.diff(t,1)+c2*(x2-x1)+c3*x2,0)
18 #initial values
19 aw={
20 x1.subs(t,0): 0.01, #m
21 x2.subs(t,0): 0,
22 x1.diff(t,1).subs(t,0):0,
23 x2.diff(t,1).subs(t,0):0
24 }
25 #Solution of the differential equation system
26 equations = [dgl1,dgl2]
27 aL=dsolve_system(equations) #general solution
28 sL=dsolve_system(equations,ics=aw) #special solution
29 gX1=sL[0][0].rhs #deflection for m1
30 gX2=sL[0][1].rhs #deflection for m2
31 #Outputs
32 #print("General solution\n",aL)
33 #print("Special solution")
34 #print("x1(t) =",N(gX1,3))
35 #print("x2(t) =",N(gX2,3))
36 p=plot(gX1,gX2,(t,0,0.2),show=False,legend=True)
37 p.xlabel='t'
38 p.ylabel='Deflection in m'
39 p[0].line_color='blue'
40 p[0].label='x1'
41 p[1].line_color='red'
42 p[1].label='x2'
43 p.show()
Listing 5.34 Solution of the Differential Equation System for the Dual Mass Oscillator
The general and special solutions have not been printed here because they are quite
long and complex.
275
5 Symbolic Computation Using SymPy
Output
Analysis
The numerical values for the masses, spring constants, and damping are taken from
Vöth: 79f (lines 07 to 09). In that source, for the spring constants, c2 = 2c, c3=3c and for
the damping d2= 2d, d3 = 3d is used. If you test the program with these values, you’ll find
that the differential equation system is no longer solved or that the computation takes
an unacceptable amount of time.
In lines 14 and 16, the two differential equations are passed as arguments to the Eq()
method. All symbolic data of these differential equations is stored in the dgl1 and dgl2
objects.
The initial values are defined in lines 19 to 24. Only the mass m1 is shifted with the initial
value x1 = 0.01 m. All other initial values are set to 0.
In lines 27 and 28, the dsolve_system() SymPy method solves the differential equation
system. The general solution and the special solution are stored in the aL and sL objects.
The statements in lines 29 and 30 cause only the deflections x1 and x2 to be selected
from the entire solution set.
Lines 36 to 43 contain the statements for the graphical output. You can use p.save
( 'dual_mass_oscillator.png') to save the graphic in PNG format.
276
5.17 Tasks
Note
You should not use SymPy to solve differential equation systems if their equations are
composed of complicated mathematical terms. In Chapter 6, I show you how to solve
the differential equation system of the dual mass oscillator more effectively numeri-
cally using the SciPy function solve_ivp().
5.17 Tasks
1. Simplify the following term:
277
5 Symbolic Computation Using SymPy
Then, transform the individual partial fractions into the time domain using a corre-
spondence table.
Check the result using the following method:
inverse_laplace_transform(Fs,s,t)
7. Calculate the transfer function for an unbalanced third-order low-pass filter. The
internal resistance of the voltage source and the terminating resistor each have a
value of 1Ω.
278
Chapter 6
Numerical Computations and
Simulations Using SciPy
This chapter describes how you can carry out numerical differentiations
and integrations using SciPy, how to numerically solve differential equa-
tions, and how SciPy can support you in analyzing non-sinusoidal waves.
The acronym SciPy stands for Scientific Python. SciPy is a Python-based collection of
open-source software for mathematics, science, and engineering.
This module provides users with numerous user-friendly and efficient routines for
numerical differentiation, integration, interpolation, optimization, linear algebra, and
statistics.
In contrast to SymPy, the SciPy module performs engineering mathematical calcula-
tions numerically rather than symbolically. Since the numerical representation of the
results of extensive numerical computations (such as solving differential equations) is
not particularly meaningful, the SciPy module is almost always used in combination
with the Matplotlib module to visualize the solutions. The NumPy module provides
the linspace() and arange() functions for creating one-dimensional arrays. The Mat-
plotlib method plot() is used for visualizing the results. Thus, the NumPy and Matplot-
lib modules form the basis of SciPy.
From the large range of functions, only a limited selection can be discussed in this
book, covering as broad a range of topics in engineering mathematics as possible. For
this reason, this chapter is limited to the exemplary treatment of submodules most rel-
evant to engineering and science: optimize, interpolate, integrate, fft, and signal.
In accordance with convention, the modules for NumPy and Matplotlib can be
included in a Python source code using the names np and plt:
import numpy as np
import matplotlib.pylot as plt
279
6 Numerical Computations and Simulations Using SciPy
01 #01_zeros.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.optimize import root
05
06 def f(x):
07 #y=(x-1)*(x-2)*(x-5)*(x-10)
08 y=10*np.exp(-0.5*x)*np.sin(x)
09 return y
10
11 x = np.linspace(0,15,100)
12 x0=[0,2,6,9,12] #start values
13 #hybr,lm,broyden1,broyden2,anderson
14 #linearmixing,diagbroyden,excitingmixing,krylov,df-sane
15 xn=root(f,x0,method='hybr')
16 print(xn.x)
17 fig, ax = plt.subplots()
18 ax.plot(x,f(x),"r-",lw=2)
19 ax.scatter(xn.x,[0,0,0,0,0],color="k",marker="x")
20 ax.set(xlabel="x",ylabel="y")
21 ax.grid(True)
22 plt.show()
280
6.1 Numerical Computation of Zeros
Output
Analysis
Line 04 imports the root function from the optimize submodule. The commented-out
polynomial in line 07 serves as a test function. The list in line 12 contains the initial val-
ues for the calculation of the zeros. The specification of the start values is actually the
critical part of the program because their approximate position must be known before
the calculation of the exact values. Here, the function plot from Figure 6.1 is useful. The
commented-out lines 13 and 14 contain the names of the possible mathematical meth-
ods to calculate zeros. For testing purposes, you can use them in line 15 to replace hybr.
In this line, the SciPy function root(f,x0,method='hybr') computes the zeros of func-
tion f using the mathematical method, hybr. More details about the mathematical
methods of zero calculation can be found in the SciPy documentation. The x0 parame-
ter contains the list of start values. The zeros are stored in the xn object and output with
xn.x in line 16.
The Matplotlib method scatter() in line 19 marks the zeros with a cross. The length of
the list with zeros (second parameter) must correspond to the length of the x0[] list
from line 12.
281
6 Numerical Computations and Simulations Using SciPy
6.2 Optimizations
From school mathematics, we know the problem of how to calculate the smallest pos-
sible surface area for a tin can (cylinder) to minimize material consumption. SciPy pro-
vides the minimize(fun,x0,...) function from the optimize submodule for this kind of
optimization task. This function must be passed an initial value x0 in addition to a
mathematical function fun, which describes the optimization problem.
The following applies to the volume of a cylinder:
The surface is composed of the two flat surfaces and the curved surface:
If you set the first derivative of the surface function to equal zero, you’ll obtain the opti-
mum radius of a tin can with minimal surface:
01 #02_minimum.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.optimize import minimize
05 V=1 #volume
06
07 def f(r):
08 h=V/(np.pi*r**2)
09 A=2*np.pi*r**2+2*np.pi*r*h
10 return A
11
12 x = np.linspace(0.1, 2, 100)
13 opt=minimize(f,0.5)
14 #Output
15 print("r=%4.6f A=%4.6f" %(opt.x[0],opt.fun))
282
6.2 Optimizations
16 fig, ax = plt.subplots()
17 ax.plot(x,f(x),"b-",lw=2)
18 ax.plot(opt.x[0],opt.fun,"rx",lw=2)
19 ax.set(xlabel="Radius",ylabel="Surface")
20 ax.grid(True)
21 plt.show()
Output
r=0.541926 A=5.535810
Analysis
In line 04, the SciPy function minimize is imported from the optimize submodule. Line
05 sets the volume of the cylinder to 1 volume unit (V). In line 13, the minimize(f,0.5)
function calculates the optimal radius for a minimum cylinder surface for the f(r)
function defined in line 07 and stores the result in the opt object. With the help of this
object, the optimal values opt.x[0],opt.fun for the radius x and the cylinder surface fun
can be output in line 15 and used to mark the minimum in line 18.
283
6 Numerical Computations and Simulations Using SciPy
6.3 Interpolations
Interpolation determines a value between two points on a curve. For any given discrete
data set (e.g., measured values), a continuous function must be found that maps this
data. Interpolation is used to predict trends or curve progressions.
The SciPy function interp1d(x,y,kind='linear',...) from the interpolate submodule
can solve interpolation problems in the plane. The x and y parameters are arrays with
the x-y coordinates of the measuring points. The third parameter specifies the interpo-
lation method, such as linear, next, previous, quadratic, or cubic.
The discrete data shown in Figure 6.3 represents some sampled values of a sine wave.
Let’s now search for the interpolating function.
Listing 6.3 calculates the interpolating function using the kind='cubic' interpolation
method.
01 #03_interpolation.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.interpolate import interp1d
05 ta=np.arange(0,12)
06 ti=np.arange(0,11,0.01)
07 #sampled signal
08 s=np.sin(ta)
284
6.3 Interpolations
09 #interpolation methods
10 #linear,next,previous,quadratic,cubic
11 f = interp1d(ta, s, kind='cubic')
12 fig, ax = plt.subplots()
13 ax.plot(ta,s, 'rx') #points
14 ax.plot(ti,f(ti),'b-') #interpolated
15 ax.set(xlabel='Time', ylabel='Signal')
16 plt.show()
Output
Figure 6.4 shows the recovered signal.
Analysis
Line 04 imports SciPy function interp1d. The array in line 05 contains twelve interpola-
tion values. From these values, the sine function in line 08 generates the sampled
amplitudes. In line 11, the SciPy function interp1d(ta,s,kind='cubic') reconstructs the
original signal from these sampled amplitude values. The interpolation method used is
cubic interpolation (cubic), which uses a third-degree polynomial as the interpolation
function. For further testing purposes, you can try the interpolation methods com-
mented out in line 10.
285
6 Numerical Computations and Simulations Using SciPy
Note
As of SciPy version 2.0.0, the misc submodule has been removed. Thus, the deriva-
tive() function is no longer available. The SciPy documentation recommends using
the Derivative() function from the numdifftools module instead. In the download
area of https://www.rheinwerk-computing.com/5852/ or https://drsteinkamp.de, you
can find source code versions in which derivatives are still calculated using the SciPy
function derivative().
def derivative(f,x,h=1e-9):
return (f(x+h)-f(x-h))/(2.0*h)
Listing 6.4 calculates for the following parabola, , the slope of the secant or
tangent at point xs = 2 for the distance h = 10-6 on the x-axis using the central difference
quotient and the Derivative() function from the numdifftools module.
01 #04_slope1.py
02 import numpy as np
03 from numdifftools import Derivative
286
6.4 Numerical Differentiation
04 #Function
05 def f(x):
06 return 0.25*x**2
07 #central difference quotient
08 def df(x,h):
09 return (f(x+h)-f(x-h))/(2*h)
10
11 xs=2 #place of the slope
12 h=1e-6 #accuracy
13 mS=df(xs,h) #secant slope
14 mT=Derivative(f,n=1) #tangent slope
15 a1=np.degrees(np.arctan(mS))
16 a2=np.degrees(np.arctan(mT(xs)))
17 print("Secant slope m=%2.6f %s=%2.1f°"%(mS,chr(945),a1))
18 print("Tangent slope m=%2.6f %s=%2.1f°"%(mT(xs),chr(945),a2))
Output
Analysis
Line 03 imports the Derivative function from the numdifftools module. The slope at
point xs=2 (line 11) is calculated in line 13 using the custom Python function df() and in
line 14 using the Derivative(f,n=1) function. The accuracy (increment) of the slope cal-
culation has been set to a small value (h=1e-6 in line 12). Surprisingly, the custom func-
tion df(x,h) from line 08 provides the same result as the Derivative function.
From the slope m at point (xs,ys) the straight-line equation of the tangent can be de-
rived.
Figure 6.5 shows for the parabola the tangent calculated using Listing 6.5 at point x = 2.
287
6 Numerical Computations and Simulations Using SciPy
01 #05_slope2.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from numdifftools import Derivative
05 #Function
06 def f(x):
07 return x**2/2
08 #Tangent
09 def f2(x,xs):
10 m=Derivative(f,n=1)
11 x0=xs-f(xs)/m(xs)
12 return m(xs)*(x-x0)
13
14 x = np.linspace(0, 5, 100)
15 xs=2 #place of the slope
16 fig, ax = plt.subplots()
17 ax.plot(x, f(x),"g-", lw=2) #function
18 ax.plot(x, f2(x,xs),"b-", lw=1) #tangent
19 ax.plot(xs, f(xs), "or") #red point
20 ax.set(xlabel="x",ylabel="y")
21 ax.grid(True)
22 plt.show()
Output
288
6.4 Numerical Differentiation
Analysis
In lines 09 to 12, the function f2(x,xs) is defined for the calculation of the tangent
slope. Figure 6.5 shows the result.
The tangent has a slope of 2 at point xs = 2, which corresponds to a slope angle of 63.43°.
Note that the x and y axes are scaled unequally.
i(t) L
UL
If an impressed current i = 𝑓(t) flows through a coil with inductance , then an inductive
voltage drop is created at the coil according to the induction law:
01 #06_slope3.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from numdifftools import Derivative
05 L=1 #H
06 f=50 #frequency in Hz
07 omega=2*np.pi*f #1/s
08 imax=1. #A
09 #power source
10 def i(t):
11 return imax*np.sin(omega*t*1e-3)#t in ms!
12 #inductive voltage drop
13 def u(t):
14 df=Derivative(i)
15 return 1e3*L*df(t)
16
17 t = np.linspace(0,20,500) #ms
18 fig,(ax1,ax2)=plt.subplots(2,1)
19 ax1.plot(t, i(t), 'r-', lw=2)
289
6 Numerical Computations and Simulations Using SciPy
Output
The output of Listing 6.6 is shown in Figure 6.7.
Analysis
In line 14, the Derivative(i) function calls function i(t) from line 10. The t parameter
must not be passed here. All information of function i(t) is stored in the df object. In
line 15, this df(t) object is passed the argument t, and it calculates the first derivative
for the i(t) current for the values of the array from line 17. The scaling factor 1e3
undoes the scaling of the time axis in ms from line 11.
As expected, a cosine function is displayed in line 23 as the result.
290
6.4 Numerical Differentiation
The exact formulation of the distance-time law can only be derived from the measured
acceleration due to gravity g by means of the integral calculus. In our case, we’ll use the
distance-time law to illustrate using the Derivative() function to compute from the
distance s the velocity and from the velocity the acceleration a of a falling sphere.
01 #07_slope4.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from numdifftools import Derivative
05 g=9.81
06 #distance
07 def s(t):
08 return g*t**2/2
09 #velocity
10 def v(t):
11 df=Derivative(s,n=1)
12 return df(t)
13 #acceleration
14 def a(t):
15 df=Derivative(v,n=1)
16 return df(t)
17 #time
18 t = np.linspace(0,5,100)#seconds
19 fig, ax = plt.subplots(3,1,figsize=(6,6))
20 #distance
21 ax[0].plot(t, s(t), 'b-', lw=2)
22 ax[0].set(ylabel='s in m',title='Distance')
23 #velocity
24 ax[1].plot(t, v(t), 'r-', lw=2)
25 ax[1].set(ylabel='v in m/s',title='Velocity')
291
6 Numerical Computations and Simulations Using SciPy
26 #acceleration
27 ax[2].plot(t, a(t), 'g-', lw=2)
28 ax[2].set(xlabel='t in s',ylabel='a',title='Acceleration')
29 ax[2].set_ylim(0,12)
30 [ax[i].grid(True) for i in range(len(ax))]
31 fig.tight_layout()
32 plt.show()
Output
The free fall of the sphere is graphically summarized by the diagrams for distance,
velocity, and acceleration shown in Figure 6.8.
Analysis
In lines 12 and 16, the derivatives for velocity v(t) and acceleration a(t) are calculated
and returned via the return statement in the function calls in lines 24 and 27. They are
shown as expected.
292
6.5 Numerical Integration
In line 15, you can also calculate the acceleration a(t) from the second derivative of the
distance function s(t) using the df=Derivative(s,n=2) statement.
Method Description
fixed_quad Calculates a given integral using the Gaussian quadrature rule for a given
order n
quadrature Calculates a given integral using the Gaussian quadrature rule for a given
tolerance
The SciPy function quad(func, a, b, ...) expects three parameters. The name of the
mathematical function func is passed as the first parameter without a function argu-
ment, so instead of f(x) simply f is passed. The second and third parameters set the
293
6 Numerical Computations and Simulations Using SciPy
lower and upper integration limits. Other parameters are optional. They can be taken
from the SciPy documentation.
Listing 6.8 calculates the definite integral using a custom function of rectangle sums
and the integration methods listed in Table 6.1.
01 #08_integral_comparison.py
02 import scipy.integrate as integral
03
04 def f(x):
05 return x**2
06 #rectangle sums
07 def rect(f,a,b,h=1e-6):
08 n=int((b-a)/h)
09 s=0
10 for k in range(1,n):
11 x=a+k*h
12 s=s+f(x)
13 return s*h
14
15 a=0 #lower limit
16 b=2 #upper limit
17 A1=rect(f,a,b)
18 A2=integral.quad(f,a,b)#[0]
19 A3=integral.fixed_quad(f,a,b,n=4)#[0]
20 A4=integral.quadrature(f,a,b,tol=1e-6)#[0]
21 A5=integral.romberg(f,a,b,tol=1e-6,show=False)
22 #Outputs
23 print("Rectangle sums\t: ",A1)
24 print("quad\t\t:",A2)
25 print("fixed_quad\t:",A3)
26 print("quadrature\t:",A4)
27 print("romberg\t\t: ",A5)
Output
294
6.5 Numerical Integration
Analysis
As expected, the custom function rect() calculates the integral less accurately than the
SciPy functions. Line 02 imports the integrate submodule and sets the integral alias.
In lines 18 to 21, this alias is used to access the SciPy integration functions. The results
are saved in objects A2 to A5. The quadrature() and romberg() functions enable you to
specify a value for the error tolerance. In lines 24 and 26, the quad() and quadrature()
functions output a second value with an error estimation in addition to the surface
areas. You can suppress the output of this error estimation if you remove the com-
ments in lines 18 to 20.
01 #09_area_parabola.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import scipy.integrate as integral
05 from scipy.optimize import root
06
07 def f(x):
08 return -(x-4)**2+10
09
10 x = np.linspace(0,8,100)
11 x0=[0,5]
12 xn=root(f,x0,method='hybr')
13 a,b=xn.x[0],xn.x[1]
14 A=integral.quad(f,a,b)[0]
15 #Output
16 print("Zeros:",a,b)
17 print("Area:",A)
18 #Display
19 fig, ax = plt.subplots()
20 ax.plot(x, f(x), "b-", lw=2)
21 ax.grid(True)
295
6 Numerical Computations and Simulations Using SciPy
22 ax.set(xlabel="x",ylabel="f(x)")
23 ax.fill_between(x,f(x),where=f(x)>=0,color='g',alpha=0.2)
24 plt.show()
Output
Analysis
This example should illustrate on the one hand how you must calculate a certain inte-
gral with SciPy and on the other hand, how you can color the area between the zeros. As
expected, the values calculated by the program for the zeros and the area meet the
requirements for sufficient accuracy.
296
6.5 Numerical Integration
C
i(t)
U
c
01 #10_constant_integration1.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import scipy.integrate as integral
05 C=1 #F
06 imax=10
07 #Constant current source
08 @np.vectorize
09 def i(t):
10 return imax
11 #Capacitor voltage
12 @np.vectorize
13 def u(t):
14 uc=(1/C)*integral.quad(i,0,t)[0]
15 return uc
16
17 x = np.linspace(0, 20, 500)
18 fig, (ax1,ax2)=plt.subplots(2,1)
19 ax1.plot(x, i(x), 'r-', lw=2)
20 ax1.set(ylabel='Current in A',title='Voltage at capacitor')
21 ax2.plot(x, u(x),'b-',lw=2)
22 ax2.set(xlabel='t in s',ylabel='$u_c(t)$ in V')
23 ax1.grid(True);ax2.grid(True)
24 plt.show()
297
6 Numerical Computations and Simulations Using SciPy
Output
Analysis
The integration of a constant is by no means trivial. In line 10, the custom function i(t)
returns a constant named imax. This constant must be converted to an array via
@np.vectorize so that it can be numerically integrated and plotted using the plot func-
tion, as shown in Figure 6.11. In lines 08 and 12, the @np.vectorize statement creates a
NumPy array with 500 elements from the custom functions i(t) and u(t). The number
of elements is specified using NumPy function np.linspace(0, 20, 500) in line 17.
298
6.5 Numerical Integration
01 #11_constant_integration2.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import scipy.integrate as integral
05 g=9.81
06 @np.vectorize
07 def a(t):
08 return g
09 @np.vectorize
10 def v(t):
11 return integral.quad(a, 0, t)[0]
12 @np.vectorize
13 def s(t):
14 return integral.quad(v, 0, t)[0]
15 #time values
16 t = np.linspace(0,5,100)
17 fig, ax=plt.subplots(3,1,figsize=(6,8))
18 ax[0].plot(t, a(t), 'g-', lw=2)
19 ax[0].set(ylabel='a in $m/s^2$',title='Acceleration')
20 ax[0].set_ylim(0,12)
21 ax[1].plot(t, v(t), 'r-',lw=2)
22 ax[1].set(ylabel='v in m/s',title='Velocity')
23 ax[2].plot(t, s(t), 'b-', lw=2)
24 ax[2].set(xlabel='t in s',ylabel='s in m',title='Distance')
25 [ax[i].grid(True) for i in range(len(ax))]
26 fig.tight_layout()
27 plt.show()
Output
The graphical output of this free fall computation is shown in Figure 6.12.
Analysis
Basically, the program does not contain any new programming elements. The custom
functions a(t), v(t), and s(t) must be vectorized again using @np.vectorize so that
they can be integrated numerically and their time courses can be displayed. The func-
tions a(t) and v(t) can be called and integrated directly after the return statement
(lines 11 and 14).
299
6 Numerical Computations and Simulations Using SciPy
R i(t)
UR
U0 C Uc
300
6.5 Numerical Integration
You can compute the stored electrical energy using the improper integral from the
electrical power:
The NumPy constant np.inf is used as the upper limit for the term ∞. Listing 6.12 shows
the implementation.
01 #12_improper_integral.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import scipy.integrate as integral
05 U0=10
06 R=1
07 C=1
08 tau=R*C
09 #voltage profile at the capacitor
10 def u(t):
11 return U0*(1-np.exp(-t/tau))
12 #capacitor current
13 def i(t):
14 return U0*np.exp(-t/tau)/R
15 #power
16 def p(t):
17 return u(t)*i(t)
18 #upper limit->infinity
19 g=np.inf
20 t = np.linspace(0,5,1000)
21 W=integral.quad(p,0,g)[0]
22 print("Stored electrical energy:",W,"Ws")
23 fig, ax = plt.subplots()
24 ax.plot(t,u(t),"b-",lw=2,label="Voltage")
25 ax.plot(t,i(t),"r-",lw=2,label="Current")
26 ax.plot(t,p(t),"k-",lw=1,label="Power")
27 ax.fill_between(t,p(t),where=p(t)>=0,color='g',alpha=0.2)
28 ax.legend(loc="best")
29 ax.annotate(r"$W_{el}$",xy=(2,1),xytext=(1,10))
30 ax.set(xlabel="Time",ylabel="i, u, p")
31 plt.show()
Output
301
6 Numerical Computations and Simulations Using SciPy
Figure 6.14 shows what the whole thing looks like in a function plot.
Analysis
The custom functions u(t) and i(t) for the voltage and current profiles already contain
the NumPy function np.exp() as an array, which is why they do not need to be vector-
ized anymore. Line 19 sets the limit g=np.inf. In line 21, this value is passed to SciPy
function integral.quad(p,0,g)[0]. The zero [0] suppresses the output of the error esti-
mation. The numerically calculated value for the stored electrical energy comes close
to the theoretically calculated value of 50 Ws.
A root expression must be integrated that contains the first derivative of a function
𝑓(x), which describes the course of the line. A typical application is the calculation of the
length of a catenary, which is mathematically described by the hyperbolic cosine with
the following curvature radius :
302
6.5 Numerical Integration
Listing 6.13 shows how to calculate the length of a rope suspended between two posts
with a distance of 20 meters.
01 #13_catenary.py
02 import numpy as np
03 import scipy.integrate as integral
04 from numdifftools import Derivative
05 a=10 #curvature radius
06 def k(x):
07 return a*np.cosh(x/a)
08 #calculate length
09 def dl(x):
10 df=Derivative(k)
11 return np.sqrt(1+df(x)**2)
12 #Distances
13 x1,x2=-10,10
14 length=integral.quad(dl,x1,x2)[0]
15 print("Length of the catenary %3.2f m" %length)
Output
Analysis
Lines 06 and 07 contain the function definition for catenary a*np.cosh(x/a). In lines 09
to 11, the dl(x) function is defined for a line element of the catenary. In line 10, the
Derivative(k) function calls the custom Python function k(x) from line 06. The x
parameter must be omitted. The data of the cosh function is stored in the df object. In
line 11, the derivative is squared. Line 14 uses the quad(dl,x1,x2)[0] function to calcu-
late the defined integral of the catenary for the given limits between ±10 m. Thus, the
chain is 3.5 m longer than the distance.
303
6 Numerical Computations and Simulations Using SciPy
Listing 6.14 shows how to calculate the volume and the surface area of a rotating
paraboloid within the limits from 0 to 1.
01 #14_integral7.py
02 import numpy as np
03 import scipy.integrate as integral
04 from numdifftools import Derivative
05 #function definition
06 def f(x):
07 #return x
08 return np.sqrt(x)
09 #square function f(x)
10 def f2(x):
11 return f(x)**2
12 #for curved surface
13 def m(x):
14 df=Derivative(f)
15 return f(x)*np.sqrt(1+df(x)**2)
16 #limits
17 a,b=0,1
18 V=np.pi*integral.quad(f2,a,b)[0] #volume
19 M=2*np.pi*integral.quad(m,a,b)[0] #curved surface
20 print("Volume %3.6f" %V)
21 print("Curved surface %3.6f" %M)
Output
Volume 1.570796
Curved surface 5.330414
Analysis
In line 18, the volume V and in line 19, the curved surface M of the rotating paraboloid is
calculated for the limits 0 to 1 using the quad() function. The commented-out function
in line 07 (f(x)=x) creates a cone that rotates around the x-axis. This function makes it
easy to check if the program calculates correctly.
304
6.5 Numerical Integration
dblquad(func, a, b, c, d, ...)
In this context, the func parameter expects a mathematical function of the form z =
𝑓(x,y). Parameters a and b represent the outer integration limits, and parameters c and
d represent the inner integration limits.
Listing 6.15 calculates the second area moment for a beam with a rectangular cross-sec-
tion. The girder has a width of 5 centimeters and a height of 10 centimeters.
01 #15_double_integral.py
02 import scipy.integrate as integral
03 b=5 #width in cm
04 h=10 #height in cm
05 #function definition
06 def f(y,z):
07 return z**2
08 #calculation
09 Iy=integral.dblquad(f,-h/2,h/2,-b/2,b/2)[0]
10 print("Iy =",Iy,"cm^4")
11 print("Iy =",b*h**3/12,"cm^4 exactly")
Output
Iy = 416.66666666666674 cm^4
Iy = 416.6666666666667 cm^4 exactly
Analysis
In lines 06 and 07, the Python function f(y,z) is used to define the second area
moment. In line 09, the SciPy function dblquad(f,-h/2,h/2,-b/2,b/2)[0] calculates the
second area moment Iy. The accuracy of the result is stunning.
305
6 Numerical Computations and Simulations Using SciPy
For further program testing, try reversing the order of the arguments in line 06 and the
integration limits in line 09.
A typical example of using a triple integral is the calculation of the air mass for a given
base area and height of an air column. The density of the air decreases exponentially
with increasing height h:
The mass m of an air column with the base area ab and the height h is calculated using
the following the triple integral:
The x1 and x2 parameters define the integration limits on the x-axis. The y1 and y2
parameters determine the integration limits on the y-axis. The z1 and z2 parameters
define the integration limits on the z-axis.
Listing 6.16 calculates the mass of an air column for a height of 8,223 meters with a base
area of 1m2 and compares the result with the analytically calculated value.
01 #16_triple_integral.py
02 import numpy as np
03 import scipy.integrate as integral
04 g=9.81 #gravitational acceleration
05 rho_0=1.28 #air density
06 p0=10e5 #air pressure
07 alpha=g*rho_0/p0
08
306
6.6 Solving Differential Equations Numerically
09 def density(z,x,y):
10 return rho_0*np.exp(-alpha*z)
11
12 a=1 #x2
13 b=1 #y2
14 h=8.223e3 #z2 height of the air column in m
15 #mass of the air column
16 m1=a*b*rho_0*(1-np.exp(-alpha*h))/alpha
17 #x1,x2,y1,y2,z1,z2
18 m2=integral.tplquad(density,0,a,0,b,0,h)[0]
19 print("Mass of air column m1:",m1, "kg")
20 print("Mass of air column m2:",m2, "kg")
Output
Analysis
In line 09, the density(z,x,y) function is defined for the calculation of the air mass. An
important requirement is that the function arguments are passed in the specified
order. The SciPy function tplquad(density,0,a,0,b,0,h)[0] calculates the air mass for a
height of 8,223 meters on a footprint of 1m2 in line 18. The comparison with the calcula-
tion in line 16 shows that the triple integral was calculated correctly.
307
6 Numerical Computations and Simulations Using SciPy
In other words, must be on the left side of the equal sign, and the term of the differ-
ential equation must be on the right side of the equal sign (rhs). For starters, let’s con-
sider an analytical, easily solvable linear first-order differential equation:
This equation will serve as a test function for checking the accuracy of the numerical
solution methods. The solution function y = 𝑓(x) can be determined by separating the
variables:
The simplest numerical method for solving first-order differential equations is the
Euler method. The basic idea of this method to approximate the course of the solution
function with a polygonal line.
The Euler algorithm computes the values for the independent variable xk from the k-
fold of increment h:
The respective new discrete function value yk + 1 of the solution function is calculated
using the sum algorithm from the sum of the old function value yk and the slope
:
Thus, the slopes are added to the respective function values of the solution function.
Listing 6.17 and Figure 6.15 visualize this process.
01 #17_differential_equation1.py
02 import math as math
03 import matplotlib.pyplot as plt
04
05 def f(x,y):
06 return x*y
07
08 x0=0
09 xk=2
10 y=1 #initial value
11 h=0.25 #increment
12 n=int((xk-x0)/h)
13 lx,lyu,lyg =[],[],[]
308
6.6 Solving Differential Equations Numerically
14 for k in range(n):
15 x=x0+k*h
16 y=y+h*f(x,y) #Euler method
17 yg=math.exp(x**2/2) #exact
18 lx.append(x)
19 lyu.append(y)
20 lyg.append(yg)
21 fig, ax = plt.subplots()
22 ax.plot(lx,lyu,"b--",label="inexact")
23 ax.plot(lx,lyg,"r-",label="exact")
24 ax.set(xlabel="x",ylabel="y")
25 ax.legend(loc="best")
26 plt.show()
Output
Figure 6.15 Numerical Solution of a Differential Equation Based on the Euler Method
Analysis
Line 11 defines the increment h. In this case, the increment must not be too small, so
that the polygonal lines are also clearly visible. The empty lists created in line 13 are
used to store the results for saving the exact values and the values calculated using the
Euler method. In lines 14 to 20, the calculations within the for loop are then performed,
and their results are stored in the empty lists. In lines 22 and 23, the output is done
using the plot method. Clearly visible is the fact that the values calculated using the
Euler method are somewhat smaller than the exact values of the solution.
309
6 Numerical Computations and Simulations Using SciPy
The Runge-Kutta algorithm exists in many different variants. As you can easily see
from its structure, it optimizes the Euler algorithm in terms of accuracy but worsens
runtimes. When you apply these algorithms, you must be aware of the restriction that
only first-order differential equations can be solved with them. Higher-order differen-
tial equations must be transformed into a first-order system, and they must be in the
explicit form.
Note
쐍 All higher-order differential equations must be transformed into a first-order dif-
ferential equation system.
쐍 These differential equations must be in explicit form.
You can use the SciPy functions odeint() and solve_ivp() to solve differential equa-
tions and systems of differential equations numerically. The odeint() function is an
interface function that accesses the ODEPACK Fortran library. From this library,
odeint() uses the LSODA solution method, which switches between optimal solution
methods in case of problem behavior (numerical instability, too large error).
However, the odeint() function is now considered deprecated. The SciPy documenta-
tion recommends using the solve_ivp() function for new code instead. Although
odeint() has been declared obsolete, both methods will be compared here.
The odeint(func, y0, t, args=(...), ...) function requires at least three parameters:
The func name of a first-order differential equation is passed as the first parameter. The
second parameter odeint() expects an array with the initial values y0. The third param-
eter represents the independent variable t. The fourth parameter, which is optional, can
contain additional arguments necessary for the solution of the differential equation.
310
6.6 Solving Differential Equations Numerically
The SciPy function solve_ivp(fun, t_span, y0, method='RK45', ...) must be passed at
least the first three arguments when called: The first parameter fun stands for the name
of the differential equation. The second parameter t_span is the integration interval.
The third parameter to be passed (y0) must be an array with the initial values of the dif-
ferential equation. The default value for the integration method is the Runge-Kutta
method RK45 of order 5(4).
Listing 6.18 solves the differential equation y´ = xy in the interval [0,1] using odeint()
and solve_ivp(). The exact value is also output so that the accuracy of both integration
methods can be compared.
01 #18_differential_equation_comparison.py
02 import numpy as np
03 from scipy.integrate import odeint,solve_ivp
04
05 def dgl(x,y):
06 dy_dx=y*x
07 return dy_dx
08
09 n=5
10 xmax=1
11 y0=[1] #initial value
12 xi=[0,xmax] #integration range
13 x = np.linspace(0,xmax,n)
14 #solutions
15 y1 = np.exp(x**2/2)#exact
16 y2 = odeint(dgl,y0,x)
17 #methods for solve_ivp
18 #RK45, RK23, DOP853, Radau, BDF, LSODA
19 z = solve_ivp(dgl,xi,y0,method='RK45',dense_output=True)
20 y3 = z.sol(x)
21 #comparison
22 print("Exactly :",y1)
23 print("odeint:",y2.reshape(n,))
24 print("ivp :",y3.reshape(n,))
Output
311
6 Numerical Computations and Simulations Using SciPy
Analysis
A comparison of the outputs clearly shows that the deprecated interface function
odeint() provides much more accurate values than the SciPy function solve_ivp().
However, you can fix this deficiency by passing two additional arguments to the solve_
ivp() function in line 19, namely, the constant for the relative tolerance rtol=1e-12 and
the constant for the absolute tolerance atol=1e-12. In this case, the solve_ivp() func-
tion calculates the exact value. You can use the commented-out solution methods in
line 18 for testing purposes in line 19.
The dense_output=True parameter and the y3 = z.sol(x) statement in line 20 are ex-
plained in the next example.
In lines 23 and 24, the outputs are linearized. You should test the outputs also without
linearization by using the NumPy method flatten().
R i(t)
UR
U0 L UL
At time t = 0, the switch will be closed. We are searching for the current profile i(t).
At any point in the transient state, the following equation applies:
With the voltage drops at the coil and at the resistor, we obtain the following equation:
By rearranging, we get the explicit form of the differential equation for the current pro-
file:
312
6.6 Solving Differential Equations Numerically
Listing 6.19 solves the differential equation and displays it graphically, as shown in
Figure 6.17.
01 #19_differential_equation3.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
05 U0=10
06 R,L=1,1
07 Tau=L/R
08 tmax=5*Tau
09 ti=[0,tmax] #integration interval
10 IL0 =[0] #initial value
11 #First-order differential equation
12 def dgl(t,ia):
13 i=ia #initial value
14 di_dt=(U0-R*i)/L
15 return di_dt
16 #t.shape (500, )
17 t = np.linspace(0,tmax,500)
18 #solution of the differential equation
19 z = solve_ivp(dgl,ti,IL0,dense_output=True)
20 iL = z.sol(t) #iL.shape (1,500)
21 #Display of the solution
22 fig, ax = plt.subplots()
23 ax.plot(t, iL.flatten(),"r-",lw=2) #iL.flatten().shape (500, )
24 ax.set(xlabel="t",ylabel="$I_L(t)$")
25 ax.grid(True)
26 plt.show()
Output
The graphical output is shown in Figure 6.17.
Analysis
Line 09 sets the integration interval ti=[0,tmax], and line 10 determines the initial
value IL0=[0] of the current. Note that the initial values are enclosed in square brackets.
Line 19 solves the differential equation and stores the data of the solution in the z
object. This object can then be used to access the sol(t) function in line 20. The solu-
tion vector is stored in the iL object and prepared for plotting in line 23 using the
Matplotlib method plot(t,iL.flatten()). The NumPy method flatten() converts the
two-dimensional iL array with the shape (1,500) into a one-dimensional array with the
shape (500, ). If you do not perform this operation, you’ll get an error message because,
313
6 Numerical Computations and Simulations Using SciPy
in the plot method, only one-dimensional arrays are allowed as arguments for the
independent and dependent variables. You can determine the dimension and type
(shape) of the arrays using print(name.ndim) and print(name.shape), respectively.
This parameter allows you to use the sol(t) function. In the documentation, this func-
tion is referred to as an object (Found solution as `OdeSolution` instance). In fact, sol()
should be referred to as a method because it accesses the z object. sol(t) computes the
numerical solution of the differential equation for the time values specified in NumPy
array t. You can use print(z) to display the contents of the z object:
message: The solver successfully reached the end of the integration interval.
success: True
status: 0
t: [0.000e+00 1.000e-04 … 5.000e+00]
y: [[ 0.000e+00 1.000e-03 … 9.735e+00 9.931e+00]]
sol: <scipy.integrate._ivp.common.OdeSolution object at 0x141659c30>
t_events: None
y_events: None
nfev: 56
njev: 0
nlu: 0
314
6.6 Solving Differential Equations Numerically
The contents of z can also be output individually in the Python shell via print(z.mes-
sage). You can find out the meaning of each statement by typing help(solve_ivp) in the
Python shell.
R L i(t)
UR UL
U0 C UC
Figure 6.18 Equivalent Circuit Diagram for a DC Motor with Separate Excitation
Our series circuit of a resistor, an inductor, and a capacitor can model the dynamic
behavior of a DC motor with separate excitation. The resistance R, the inductance L,
and the capacitance C represent the ohmic resistance of the copper winding, the arma-
ture inductance, and the what’s called the dynamic capacitance of a DC motor with sep-
arate excitation. The dynamic capacitance describes the mechanical inertia of the
armature (rotor) of a DC motor. The rotational energy stored in the armature is equal to
the electrical energy stored in a capacitor.
The dynamic capacitance is calculated from the moment of inertia of the working
machine J, the rated armature current IAN, and the rated torque MN:
The course of the rotational frequency n(t) corresponds to the voltage profile at the
capacitor ic(t). If the value of the battery voltage U0 is 100V, then the normalized rota-
tional frequency profile can be read on the y-axis as a percentage value for each time of
the transient state.
To obtain the differential equation for the capacitor voltage, you must first set up the
mesh equation for the voltage drops on the components:
315
6 Numerical Computations and Simulations Using SciPy
The voltage drop across the coil is determined by the law of induction:
And substituting the capacitor current into the mesh equation provides:
By rearranging, you obtain a system of two differential equations. The first is for the
capacitor voltage:
Because this differential equation system was set up in explicit form, it can be coded
directly as source code, as shown in Listing 6.20.
01 #20_differential_equation_second_order.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
05 U0 = 100 #input voltage in V
06 R = 1.5 #armature resistance in Ohms
07 L = 0.025 #armature inductance in H
08 Mn=150 #rated torque in Nm
09 In=50 #rated current in A
10 J=0.2 #moment of inertia in kgm^2
11 tmax=0.5 #time in seconds
12 #system of differential equations
13 def dgl(t,initial_values,R,L,C):
14 uc,i = initial_values
15 duc_dt = i/C
16 di_dt = (U0 - R*C*duc_dt-uc)/L
17 return [duc_dt, di_dt]
18
19 C = J*(In/Mn)**2 #dynamic capacitance
316
6.6 Solving Differential Equations Numerically
Output
317
6 Numerical Computations and Simulations Using SciPy
Analysis
In lines 13 to 17, the function for the differential equation system dgl(t,initialval-
ues,R,L,C) is defined. This function returns the array [duc_dt, di_dt] as the solution
set.
In line 23, this differential equation is solved. The additional parameters for the values
of the components are passed as tuples args=(R,L,C). Since the z object contains the
solution set for the voltage and current profiles, they must be separated in line 24. The
solution of the differential equation is stored in the tuple uc, ic.
I
I–h
m
h
Ft ɔ
Fg
A sphere of mass m is attached to a rod. The rod is assumed to be massless, while the
entire mass of the system is concentrated as a point mass in the sphere. Bearing
318
6.6 Solving Differential Equations Numerically
friction is also neglected. However, the air friction of the sphere, which is described by
the following equation, should not be neglected:
For the force component of the acceleration, the following equation applies:
At any point in time, the sum of all forces must equal zero:
Using the abbreviations for the friction and for the angular fre-
The frequency of the pendulum movement decreases with the pendulum length and
the period duration increases with the length of the pendulum:
The pendulum swings back and forth on a circular path after being deflected because
the potential energy of the sphere is converted into kinetic energy and the kinetic
energy of the sphere is in turn converted into potential energy. This process is damped
by air friction and bearing friction.
319
6 Numerical Computations and Simulations Using SciPy
The sphere of the pendulum moves along a circular path. The maximum orbital veloc-
ity can be calculated from the energy balance with the following equation:
The second-order differential equation of the pendulum motion can be solved using
the solve_ivp() function only if it is transformed into a system of two first-order differ-
ential equations.
With , we can obtain the explicit first-order differential equation system:
As shown in Listing 6.21, we can simulate Foucault’s pendulum, which was a famous
experiment held at the Panthéon in Paris on March 26, 1851. This pendulum had a
length of 67 meters and a spherical mass of 28 kilograms.
Although the deflection angle of 179° specified in line 07 is unrealistic for a pendulum
length of 10 meters (line 05), it was deliberately chosen so that the nonlinearities of the
motion sequence become clearly visible, as shown in Figure 6.21.
01 #21_differential_equation_simple_pendulum.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
05 l=10 #length of the pendulum in m
06 d=1 #diameter of the sphere in dm
07 phi0=179 #deflection
08 cw=0.3 #drag coefficient for the sphere
09 rho_K=7.85 #density of steel kg/dm^2
10 rho_L=1.28 #Density of air kg/m^3
11 g=9.81 #gravitational acceleration
12 tmax=50
13 #system of differential equations
14 def dgl(t,ya,b,w02):
15 phi, w = ya
16 dphi_dt = w
17 dw_dt = -b*np.abs(w)*w-w02*np.sin(phi)
18 return [dphi_dt,dw_dt]
19 #computations
20 r=d/2 #radius of the sphere in dm
21 A=np.pi*(0.1*r)**2 #circle area in m^2
22 m=rho_K*4/3*np.pi*r**3 #mass of the sphere in kg
23 b=cw*rho_L*A*l/(2*m) #damping constant
24 w02=g/l
320
6.6 Solving Differential Equations Numerically
25 T=2.0*np.pi*np.sqrt(l/g)
26 f=1.0/T #frequency
27 vmax=np.sqrt(2*g*l*(1-np.cos(np.radians(phi0))))
28 #solution of the differential equation
29 y0 = [np.radians(phi0),0]
30 t = np.linspace(0, tmax, 500)
31 z=solve_ivp(dgl,[0,tmax],y0,args=(b,w02),dense_output=True)
32 phi, w = z.sol(t)
33 v=l*w
34 #Output
35 fig,ax=plt.subplots(2,1)
36 #Deflection
37 ax[0].plot(t, np.degrees(phi),'r-',lw=2)
38 ax[0].set(ylabel=r"$\varphi$ in °",title="Simple pendulum")
39 #Velocity
40 ax[1].plot(t, v,'b-',lw=2)
41 ax[1].set(xlabel='Time in s',ylabel="v in m/s")
42 [ax[i].grid(True) for i in range(len(ax))]
43 fig.tight_layout()
44 print("Mass of the sphere %3.2f kg"%m)
45 print("Period duration %3.2f s"%T)
46 print("Frequency %3.2f Hz"%f)
47 print("Damping %3.4f"%b)
48 print("Maximum velocity %3.2f m/s"%vmax)
49 plt.show()
Output
Figure 6.21 shows the graphical output of the program.
Analysis
The structure of the simulation program results from the previously derived formulas.
At a deflection angle of 179°, the nonlinearity of the curves is clearly visible. If you
reduce the angle in line 07 to about less than 90°, then the nonlinearity is already no
longer visible to the naked eye. For the variation of the simulations, you can change the
diameter of the sphere in line 06 and the density of the material in line 09.
321
6 Numerical Computations and Simulations Using SciPy
If you want to simulate Foucault’s pendulum, then you must enter 67m for the pendu-
lum length and 1.896dm for the sphere diameter. A deflection angle of 1.71° corresponds
to a deflection of about 2m.
x1 x2
The masses and are supposed to only move horizontally along the x-axis, in the
horizontal direction. The deflections x1 and x2 refer to the idle state. The physical
behavior of the two springs is determined by the damping constants d1, d2, and d3 and
the spring constants c1, c2, and c3 .
322
6.6 Solving Differential Equations Numerically
Both sides must be divided by the masses to obtain the explicit form:
Listing 6.22 solves this differential equation system using the solve_ivp() function and
visualizes the motion sequences of the coupled spring-mass system, as shown in
Figure 6.23.
01 #22_differential_equation_two-mass_oscillator.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
05 m=1e3 #mass in kg
06 c=1e7 #spring constant N/m
07 d=1e3 #damping kg/s
08 m1,m2=m,2*m
09 c1,c2,c3=c,2*c,3*c
10 d1,d2,d3=d,2*d,3*d
11 tmax=0.2 #seconds
12 #system of differential equations
13 def dgl(t,xa,c1,c2,c3,d1,d2,d3,m1,m2):
14 x1,v1,x2,v2=xa
15 dx1_dt=v1
16 dv1_dt=-(d1*v1+d2*(v1-v2)+c1*x1+c2*(x1-x2))/m1
17 dx2_dt=v2
18 dv2_dt=-(d3*v2+d2*(v2-v1)+c3*x2+c2*(x2-x1))/m2
19 return np.array([dx1_dt,dv1_dt,dx2_dt,dv2_dt])
323
6 Numerical Computations and Simulations Using SciPy
Listing 6.22 Solution of the Differential Equation System for a Two-Mass Oscillator
Output
Analysis
The values for the masses, damping, and spring constants were taken from Vöth: 79f.
(lines 05 to 10).
The function definition dgl() in lines 13 to 19 contains the code for the differential
equation system in explicit form. The derivatives are returned as a NumPy array in line
19. This approach shortens the computation time compared to the return with lists.
324
6.6 Solving Differential Equations Numerically
In line 24, the solve_ivp() function solves the differential equation system. The solu-
tion data is stored in the z object. In line 25, the solutions are separated and stored in
the tuple x1,v1,x2,v2.
The graphics area for the graphic representation of the mass deflections m1 and m2
begins in line 26. The factor 1e3 in lines 28 and 30 causes the values for the x-axis to be
converted to ms and the values for the y-axis to be converted to mm.
Compared to the SymPy method dsolve_system(), the numerical calculation using the
SciPy function solve_ivp() is much faster and more effective.
I1
ɔ1
m1
I2
ɔ2
m2
The two masses are connected by rigid rods. The bearing friction and air friction as well
as the moment of inertia of the rods can be neglected.
A double pendulum is a complex oscillating system whose motion can be described by
two nonlinear differential equations, in this scenario from http://www.physics.usyd.
edu.au/~wheat/dpend_html/.
325
6 Numerical Computations and Simulations Using SciPy
01 #23_differential_equation_double_pendulum.py
02 import numpy as np
03 from numpy import sin,cos
04 import matplotlib.pyplot as plt
05 from scipy.integrate import solve_ivp
06 #pendulum data
07 g = 9.81 #gravitational acceleration
08 l1,l2 = 2,1 #pendulum lengths
09 m1,m2 = 5,1 #pendulum masses
10 phi1, phi2 = 120, -10 #deflection
11 tmax=20
12 #system of differential equations
13 def dgl(t,ya,l1, l2, m1, m2):
14 phi1,w1,phi2,w2 = ya
15 delta=phi2-phi1; m=m1+m2 #abbreviation for angle and mass
16 phi1_dt = w1 #1. Derivative top angle
17 w1_dt=(m2*l1*w1**2*sin(delta)*cos(delta)\
18 +m2*g*sin(phi2)*cos(delta)+m2*l2*w2**2*sin(delta)-m*g*sin(phi1))\
19 /(m*l1-m2*l1*cos(delta)**2)
20 phi2_dt = w2 #1. Derivative bottom angle
21 w2_dt=(-m2*l2*w2**2*sin(delta)*cos(delta)\
22 + m*(g*sin(phi1)*cos(delta)-l1*w1**2*sin(delta)-g*sin(phi2)))\
23 /(m*l2-m2*l2*cos(delta)**2)
24 return np.array([phi1_dt, w1_dt, phi2_dt, w2_dt])
25 #Solution of the differential equation system
26 omega1 = omega2 = 0
27 ya =[np.radians(phi1),omega1,np.radians(phi2),omega2]
28 t = np.linspace(0,tmax,1000)
29 z=solve_ivp(dgl,[0,tmax],ya,args=(l1,l2,m1,m2),dense_output=True)
326
6.6 Solving Differential Equations Numerically
Output
327
6 Numerical Computations and Simulations Using SciPy
Analysis
As clearly indicated by the trajectories shown in Figure 6.25, pendulum 1 is forced to
move on a circular path, while pendulum 2 performs chaotic movements. By changing
the pendulum lengths in line 08 and the pendulum masses in line 09, you can perform
various simulations.
In lines 13 to 24, the differential equation system dgl for the pendulum motions is
implemented as required by the specifications. In line 29, the solve_ivp() function
solves this differential equation system and stores the solution vector in the z object.
In line 30, the solutions are separated and stored in the tuple phi1, w1, phi2, w2.
In line 32, the x-y coordinates of the pendulum motion are calculated for pendulum 1
and in line 33 for pendulum 2.
The smaller the total harmonic distortion, the better the “quality” of the sine wave.
For a rectangular function, the following equation applies:
According to Shannon’s sampling theorem, the signal must be sampled with at least
twice the frequency to detect these harmonics. Mathematically, the sampling process
is described by the discrete Fourier transform. This transformation determines the fre-
quency components of a non-sine wave. This approach transforms a signal s(t) from
the time domain to the frequency domain , using the following algorithm:
328
6.7 Discrete Fourier Transform
The inverse Fourier transform is used to transform back into the time domain:
SciPy provides the Fourier transform functions in the fft subpackage. The fft(x) func-
tion transforms the x array from the time domain to the frequency domain. It calcu-
lates the one-dimensional discrete Fourier transform (DFT) for N sample points using
the efficient fast Fourier transform (FFT) algorithm. The first letter f ( fast) in the identi-
fier of the function refers to the efficiency of the algorithm.
The inverse ifft(x) function transforms the signal back to the time domain s(t).
01 #24_fourier1.py
02 from scipy.fft import fft,ifft
03 u_t1=[0,1,2,3,4,5]
04 #transformation to the frequency domain
05 U_fft=fft(u_t1)
06 #transformation to the time domain
07 u_t2=ifft(U_fft)
08 print("Original signal :\n",u_t1)
09 print("Transformed signal:\n",U_fft)
10 print("Reconstructed signal:\n",u_t2.real)
Output
Original signal:
[0, 1, 2, 3, 4, 5]
Transformed signal:
[15.-0.j -3.+5.19615242j -3.+1.73205081j -3.-0.j
-3.-1.73205081j -3.-5.19615242j]
Reconstructed signal:
[0. 1. 2. 3. 4. 5.]
Analysis
In line 05, the fast Fourier transform function fft(u_t1) from the fft submodule trans-
forms the u_t1 list from line 03 to the frequency domain. The output in line 09 is an
array of complex numbers whose practical usability cannot be seen at this point. In line
329
6 Numerical Computations and Simulations Using SciPy
07, the inverse Fourier transform function ifft(U_fft) transforms the U_fft function
from the frequency domain back to the time domain. The output in line 10 shows that
the reverse transformation is exact. By specifying the real property, the outputs of the
imaginary parts, which are zero anyway, are suppressed.
01 #25_fourier_rectangle.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.fft import fft,fftfreq
05 f=50 #frequency in Hz
06 T=1/f #period duration
07 umax=325 #amplitude
08 N=6000 #number of samplings
09 Ta=T/N #sampling time
10 t=np.linspace(0,T,N)
11 #compute total harmonic distortion
12 def total_harmonic_distortion(u):
13 z=0
14 for i in range(1,len(u)):
15 z=z+u[i]**2
16 return np.sqrt(z)/u[0]
17 #rectangle function
18 @np.vectorize
19 def ur(t):
20 if t < T/2:
21 return umax
22 else:
23 return -umax
24 #sawtooth
25 @np.vectorize
26 def us(t):
27 return 1e4*(t-T/2)
28 #triangle
29 @np.vectorize
30 def ud(t):
31 m=100
32 if t < T/2:
330
6.7 Discrete Fourier Transform
33 return m*t
34 else:
35 return -m*(t-T/2)+m*T/2
36 #parabolic arcs
37 @np.vectorize
38 def up(t):
39 if t < T/2:
40 return -(t-T/4)**2+(T/4)**2
41 else:
42 return (t-3*T/4)**2-(T/4)**2
43 u_t=ur(t)
44 #transformation to the frequency domain
45 U_fft = fft(u_t)
46 #magnitudes of the amplitudes in the frequency domain
47 U_f=2*np.abs(U_fft)/N
48 #compute harmonics
49 fk=fftfreq(N,Ta)
50 pos=np.where(fk>0) #only positive frequencies
51 #create subdiagrams
52 print("Total harmonic distortion %2.3f"%total_harmonic_distortion(U_f[pos]))
53 fig,ax=plt.subplots(2,1)
54 #display square wave display
55 ax[0].plot(1e3*t,u_t,"b-",lw=2)
56 ax[0].set(xlabel="t in ms",ylabel="U in V")
57 #display frequency spectrum
58 ax[1].stem(fk[pos],U_f[pos])
59 ax[1].set(xlabel="f in Hz",ylabel="Amplitudes")
60 ax[1].set_xlim(0,1000)
61 fig.tight_layout()
62 plt.show()
Output
Figure 6.26 shows the graphical output of the program.
Analysis
All custom functions must be vectorized via @np.vectorize so that they can be repre-
sented on the time axis. In line 43, the function call of the custom signal function takes
place. By changing the function name, you can select the desired waveform (rectangle
ur(t), sawtooth us(t), triangle ud(t), or parabolic arcs up(t)) for the calculation of the
total harmonic distortion and the display of the frequency spectrum.
331
6 Numerical Computations and Simulations Using SciPy
The total harmonic distortion (u) function for the calculation of the total harmonic dis-
tortion is defined in lines 12 to 16. The algorithm sums up the individual amplitudes of
the frequency spectrum (so-called harmonics) within a for loop. Note that the loop pass
must start with index 1 so that the fundamental frequency u[0] is not added up with it.
In line 45, the SciPy function fft(u_t) transforms the time function u_t into the fre-
quency domain. Since the transformation produces complex frequencies, the NumPy
function abs() in line 47 computes the amounts of the amplitudes. In line 49, the fft-
freq(N,Ta) function determines the harmonics from the number of sampling points N
and from the sampling time Ta. In line 50, the NumPy function where(fk>0) suppresses
all negative harmonics and stores them in the pos variable.
In line 55, the plot method prepares the display of the temporal signal wave u_t. The
time axis was realistically scaled to milliseconds. In line 58, the matplotlib method
stem(x,y) plots the amplitudes of the frequency spectrum (i.e., the harmonics) as verti-
cal lines for each calculated x-y position, as shown in Figure 6.26.
The total harmonic distortion of 0.483 calculated for the square wave function corre-
sponds to the theoretically determined value.
332
6.7 Discrete Fourier Transform
frequencies (such as noise) in the frequency domain. Above a certain frequency, what’s
called the cutoff frequency 𝑓g, these frequencies are no longer taken into account in the
frequency domain by a comparison 𝑓<𝑓g when transforming back into the time
domain. This method allows you to suppress certain harmonics or noise. Listing 6.26
shows how a noisy sine wave is filtered in the frequency domain and transformed back
into the time domain using the inverse Fourier transform ifft(F).
01 #26_low-pass_filter.py
02 import numpy as np
03 from matplotlib import pyplot as plt
04 from scipy.fft import fft,ifft,fftfreq
05 f=50 #frequency in Hz
06 T = 1/f #period duration
07 fg=1.1*f #cutoff frequency
08 N=6000 #number of samplings
09 Ta=T/N #sampling time
10 t = np.linspace(0,T,N)
11 u_t=10*np.sin(2*np.pi*f*t)+8*np.random.randn(t.size)
12 #transformation to the frequency domain
13 U_fft = fft(u_t)
14 #compute sampling frequency
15 fk = fftfreq(u_t.size,Ta)
16 #filter signal
17 F_g=U_fft*(np.abs(fk) < fg)
18 #transformation back to the time domain
19 u_g = ifft(F_g)
20 fig, ax=plt.subplots(2,1,figsize=(6,6))
21 #noisy signal
22 ax[0].plot(1e3*t, u_t)
23 ax[0].set(xlabel="t in ms",ylabel="u(t)",title="Noisy signal")
24 #filtered signal
25 ax[1].plot(1e3*t, u_g.real,lw=2)
26 ax[1].set(xlabel="t in ms",ylabel="u(t)",title="Filtered signal")
27 fig.tight_layout()
28 plt.show()
Output
The graphical output of the noisy and filtered signals is shown in Figure 6.27.
333
6 Numerical Computations and Simulations Using SciPy
Analysis
In line 11, a sine function u_t with a superimposed noisy signal is defined. In line 13, the
transformation into the frequency domain takes place.
The crucial operation takes place in line 17 where all harmonics greater than the cutoff
frequency fg are suppressed by a simple mathematical comparison operation. In the
F_g variable, only those harmonics that are below the cutoff frequency are stored.
The original sine wave is reconstructed by the reverse transformation into the time
domain (line 19).
334
6.8 Writing and Reading Sound Files
the function expects the sampling rate rate. The third parameter (data) expects a
NumPy array containing the sampled audio signals.
Listing 6.27 produces the concert pitch A (440 Hz). A program that can play WAV files
can make this sound audible. The program can also be used for a hearing test if you
increase the frequencies in line 05 step by step up to about 20,000 Hz.
01 #27_pitch_generation.py
02 import numpy as np
03 from scipy.io import wavfile
04 samplingrate = 44100
05 f=440 #frequency in Hz
06 t = np.linspace(0,1,samplingrate)
07 amp = np.iinfo(np.int16).max
08 pitch = amp*np.sin(2*np.pi*f*t)
09 print("Amplitude:",amp)
10 wavfile.write("sinus440Hz.wav", samplingrate, pitch)
Output
Amplitude: 32767
Analysis
Line 04 specifies that the signal is sampled 44,100 times in 1 second. Line 05 determines
the frequency f of the pitch to be generated. If necessary, you can vary the frequency of
the sound in this line. In line 06, 1 second must be entered for the duration of the tone
because the sampling rate refers to the interval from 0 to 1 second. In line 07, you can
also enter other values for the amplitude (amp) of the signal. By superimposing different
sinusoidal oscillations, other sounds can also be generated in line 08. In line 10, the fol-
lowing SciPy function generates a sound file in WAV format:
wavefile.write("sinus440Hz.wav",samplingrate,pitch)
This file is saved in binary format as sinus440Hz.wav to the hard disk. The sampling rate
(samplingrate) and the pitch object must be passed to the wavefile.write() function.
335
6 Numerical Computations and Simulations Using SciPy
01 #28_read_pitch.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.io import wavfile
05 sampling_rate,data = wavfile.read("sinus440Hz.wav")
06 t = np.linspace(0, 1, sampling_rate)
07 fig, ax=plt.subplots()
08 ax.plot(t,data)
09 ax.set_xlim(0,0.01)
10 ax.set(xlabel="Time in seconds")
11 plt.show()
Output
Analysis
The output shows that the data of the sound file sinus440Hz.wav was read correctly: In
10 ms, about four periods of the 440 Hz signal with the amplitude of about 33,000 are
displayed.
In line 05, the SciPy function wavfile.read("sinus440Hz.wav") reads the data of the
sinus440Hz.wav file and saves it in the samplingrate,data tuple. The sampling rate
(samplingrate) is needed in line 06 to define the distances on the t-axis.
In line 08, the coordinate data t,data is passed to the plot method. To ensure that indi-
vidual oscillations of the signal remain recognizable, the t-axis was limited to a final
value of 10 ms in line 09.
336
6.9 Signal Processing
b,a=butter(N,Wn,btype='low',analog=False,output='ba',fs=None)
Parameter Description
btype Type of filter: Options include lowpass, highpass, bandpass, and band-
stop. The default setting is lowpass.
To plot the frequency response of a filter, you’ll need the w,h=freqs(b,a) function. This
function returns two arrays with the values of the frequency response h calculated
from the numerator and denominator coefficients b and a and the corresponding
frequency values w as tuples. Using the Matplotlib method plot() or even better, the
semilogx() method, the frequency response of an analog filter can be displayed as a
function plot.
337
6 Numerical Computations and Simulations Using SciPy
01 #29_tp_frequency_response.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.signal import butter,freqs
05 g=3 #degree of the filter
06 fg=1 #cutoff frequency in Hz
07 #numerator, denominator coefficients
08 b,a=butter(g,fg,'lowpass',analog=True)
09 #angular frequency, frequency response
10 omega,h_t = freqs(b,a)
11 fig, ax=plt.subplots(figsize=(8,6))
12 ax.semilogx(omega, 20*np.log10(abs(h_t)))
13 #ax.plot(omega, 20*np.log10(abs(h_t)))
14 ax.set_title('Butterworth lowpass')
15 ax.set_xlabel('f in Hz')
16 ax.set_ylabel('Amplitude in dB')
17 ax.margins(0, 0.1)
18 ax.grid(which='both', axis='both')
19 ax.axvline(fg, color='red') #cutoff frequency
20 plt.show()
Output
338
6.9 Signal Processing
Analysis
A third-degree lowpass has an attenuation of 60 dB per decade, which is confirmed by
the function plot shown in Figure 6.29.
Strictly speaking, the cutoff frequency fg in line 06 is an angular frequency. However,
since the angular frequency is plotted on the frequency axis, the "error" undoes itself.
In line 08, the butter(g,fg,'lowpass',analog=True) function computes from the signal
submodule the coefficients a of the denominator polynomial and the coefficients b of
the numerator polynomial of the Butterworth lowpass of the transmission function.
These coefficients are used in line 10 by the freqs(b,a) function to compute the fre-
quency response. The frequency response data is stored in the omega,h_t tuple and
passed to the Matplotlib method semilogx() in line 12.
You can also use the butter() function to compute Butterworth coefficients for various
filter types such as lowpass, highpass, bandpass, and bandreject. The results can be out-
put as numpy.ndarray via the print function.
01 #30_crossover.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.signal import butter,freqs
05 g=3 #degree of the filter
06 fgu=500 #lower cutoff frequency in Hz
07 fgo=5000 #upper cutoff frequency in Hz
08 #numerator, denominator coefficients
09 bt,at=butter(g,fgu,'lowpass',analog=True)
10 bb,ab=butter(g,[fgu,fgo],'bandpass',analog=True)
11 bh,ah=butter(g,fgo,'highpass', analog=True)
12 #angular frequency, frequency response
13 f1, ht_t = freqs(bt,at)
14 f2, hb_t = freqs(bb,ab)
15 f3, hh_t = freqs(bh,ah)
16 fig, ax = plt.subplots(figsize=(8,6))
17 ax.semilogx(f1,20*np.log10(abs(ht_t)))
18 ax.semilogx(f2,20*np.log10(abs(hb_t)))
339
6 Numerical Computations and Simulations Using SciPy
19 ax.semilogx(f3,20*np.log10(abs(hh_t)))
20 ax.set_title('Crossover')
21 ax.set_xlabel('f in Hz')
22 ax.set_ylabel('Amplitude in dB')
23 ax.set_xlim(50,20e3)
24 ax.set_ylim(-20,3)
25 ax.margins(0, 0.1)
26 ax.grid(which='both', axis='both')
27 ax.axvline(fgu, color='red')
28 ax.axvline(fgo, color='red')
29 plt.show()
Output
Figure 6.30 shows the frequency response of the crossover.
Analysis
Basically, the program does not contain any new code. In lines 09 to 11, the butter()
function computes the coefficients of the transmission functions for the lowpass, the
bandpass, and the highpass. In lines 13 to 15, the freqs() function computes the fre-
quency responses of the filters.
340
6.9 Signal Processing
01 #31_tpfilter.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 import scipy.signal as signal
05 g=10 #degree of the filter
06 f1=10
07 f2=30
08 fs=1e3 #sampling frequency
09 fg=1.1*f1 #cutoff frequency
10 tmaxes=1
11 t = np.linspace(0,tmaxes,2000)
12 u_t = np.sin(2*np.pi*f1*t) + np.sin(2*np.pi*f2*t)/3
13 #u_t = 10*np.sin(2*np.pi*f1*t) + 5*np.random.randn(t.size)
14 TPK = signal.butter(g,fg,'low',analog=False,output='sos',fs=fs)
15 filter = signal.sosfilt(TPK, u_t)
16 fig, ax = plt.subplots(2, 1)
17 #mixed signal
18 ax[0].plot(t, u_t)
19 ax[0].set(ylabel='u(t)',title='10-Hz und 30-Hz Signal')
20 #filtered signal
21 ax[1].plot(t, filter)
22 ax[1].set(xlabel='t in s',ylabel='u(t)',title='Filtered signal:
10 Hz')
23 plt.tight_layout()
24 plt.show()
Output
Figure 6.31 shows the distorted and the filtered signals.
Analysis
As expected, Figure 6.31 shows ten periods within a time of 1 second. The filtered signal
therefore has a frequency of 10 Hz.
In line 14, the following function computes the data for the filtered signal and stores it
in the TPK object:
butter(g,fg,'low',analog=False,output='sos',fs=fs)
341
6 Numerical Computations and Simulations Using SciPy
The output parameter must be assigned the sos (second-order sections) value. As a
result, the filter function of butter() will be activated. The lowpass is a digital filter
because the analog parameter has not been set to True. The last parameter is assigned
the sampling frequency fs defined in line 08. In line 15, the sosfilt(TPK,u_t) function
suppresses all frequency components that are above the cutoff frequency of 11 Hz.
342
6.10 Project Task: Simulation of a Rolling Bearing Damage
In this project task, we need to develop a program that simulates bearing damage. For
this purpose, only two waveforms, one with a lot of harmonics and one with only a lit-
tle, must be generated from sine functions. The SciPy function fft() then transforms
both waveforms into the frequency domain. By displaying the harmonics, a diagnosis
is then made as to whether a bearing is damaged. If the signals were available in WAV
format, they could also be read using the read function and analyzed using the fft()
function. Listing 6.32 simulates the frequency spectrum of a damaged bearing and
compares it with the frequency spectrum of an undamaged bearing. The shaft rotates
at a frequency of 1,200 rpm.
01 #32_bearing_damage.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.fft import fft,fftfreq
05 f1=20 #1200 1/min
06 fn=[f1,2*f1,3*f1,4*f1,5*f1]
07 a1=[4,5,4,3,2,1] #defect
08 a2=[4,0.22,0.21,0.15,0.11] #undamaged
09 #Frequency
10 T=20
11 N=5000
12 Ta=T/N #sampling time
13 t = np.linspace(0,T,N)
14 #noisy signals
15 ur = a1[0]*np.sin(2*np.pi*fn[0]*t)\
16 +a1[1]*np.sin(2*np.pi*fn[1]*t)\
17 +a1[2]*np.sin(2*np.pi*fn[2]*t)\
18 +a1[3]*np.sin(2*np.pi*fn[3]*t)\
19 +a1[4]*np.sin(2*np.pi*fn[4]*t)\
20 +np.random.normal(size=N)
21
22 ug = a2[0]*np.sin(2*np.pi*fn[0]*t)\
23 +a2[1]*np.sin(2*np.pi*fn[1]*t)\
24 +a2[2]*np.sin(2*np.pi*fn[2]*t)\
25 +a2[3]*np.sin(2*np.pi*fn[3]*t)\
26 +a2[4]*np.sin(2*np.pi*fn[4]*t)\
27 +np.random.normal(size=N)
28 #transformation to the frequency domain
29 U_fftd = fft(ur) #defect
30 U_fftg = fft(ug)
31 fk=fftfreq(N,Ta)
32 pos=np.where(fk>0)
33 #amplitude magnitudes
34 Usd=2.0/N*np.abs(U_fftd) #defect
343
6 Numerical Computations and Simulations Using SciPy
35 Usf=2.0/N*np.abs(U_fftg)
36 #Frequency spectrum
37 fig,ax=plt.subplots(3,1,figsize=(6,6))
38 #Time domain
39 ax[0].plot(t,ur,"b-",lw=1)
40 ax[0].set_xlim(0,10)
41 ax[0].set(xlabel="t in s",ylabel="a",title="Noisy signal")
42 #damaged bearing
43 ax[1].plot(fk[pos],Usd[pos],"r-",lw=2)
44 ax[1].set(xlabel="f in Hz",ylabel="a",title="Damaged bearing")
45 #undamaged bearing
46 ax[2].plot(fk[pos],Usf[pos],"g-",lw=2)
47 ax[2].set(xlabel="f in Hz",ylabel="a",title="Undamaged bearing")
48 fig.tight_layout()
49 plt.show()
Output
Figure 6.32 shows the frequency spectrum of an undamaged and a damaged bearing.
344
6.11 Project Task: Predator-Prey Model
Analysis
The rotation frequency of 20 Hz specified in line 05 corresponds to a speed of 1,200
rpm. In line 06, in the fn list, you can enter which harmonics should occur for the sim-
ulation of a damaged bearing. The a1 list in line 07 contains the Fourier coefficients for
the simulation of a damaged bearing, and the a2 list in line 08 stores the Fourier coeffi-
cients for an undamaged bearing.
Lines 15 to 20 contain the code for the Fourier series of the defective bearing. Lines 22 to
27 contain the code for the Fourier series of the undamaged bearing. A noisy signal is
superimposed on both signals to simulate the disturbances of real operation.
In lines 29 and 30, the fft() function transforms both time signals ur and ug into the
frequency domain. In lines 34 and 35, the amplitudes for the amplitude spectra are cal-
culated.
In the output shown in Figure 6.32, notice the clear difference between the defective
bearing and the undamaged bearing. The undamaged bearing has only one amplitude,
at 20 Hz, which is the specified rotational frequency, while in the case of the defective
bearing, multiple harmonics are pronounced.
For the derivation of the differential equation system, the notion of the growth rate is
crucial. This rate is the number of births minus the number of deaths related in a given
time period divided by the total number of the population.
For example, let’s suppose a population of 1,000 individuals (equivalent to 500 pairs)
were to have 1,000 offspring in 1 year. Then, 500 deaths would result in the following
growth rate:
345
6 Numerical Computations and Simulations Using SciPy
This equation describes exponential growth for a positive growth rate, which can real-
istically occur only exceptionally and temporarily in nature.
By rearranging, you get the differential equation for what’s called logistic growth:
For time t = 0 and when the number N of a population has reached the capacity limit K,
the slope of the searched function N(t) takes the value zero. Thus, an S-shaped course
for N(t) between the bounds N = 0 and N = K is to be assumed.
In task 10 (see Section 6.13), we’ll simulate both exponential growth and logistic growth.
346
6.11 Project Task: Predator-Prey Model
By rearranging, you obtain the following differential equation for the prey animals:
For the predator, a mortality rate s is assumed instead of the growth rate because, with-
out the existence of the prey animals, it would become extinct. The population of pred-
ators can only grow if prey animals also exist and if these could be hunted successfully.
The probability of hunting success is again to be described by the prey probability b.
For the predator growth rate, the following applies:
By rearranging, you obtain the following differential equation for the predators:
Listing 6.33 simulates the predator-prey relationship for the assumption that the
growth of both populations is limited by capacity constraints.
01 #33_predator_prey.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
347
6 Numerical Computations and Simulations Using SciPy
348
6.12 Project Task: Simulation of an Epidemic
Output
Analysis
The predator-prey relationship is described in the phase diagram by a spiral running
from the outside to the inside, as shown in Figure 6.33. The number N1 of prey stabilizes
at a mean of 644 individuals, and the number N2 of predators stabilizes at a mean of 229
individuals. These values are illustrated by the predator-prey ratio endpoint in the
phase diagram.
In lines 16 to 20, the differential equation system dgl for logistic growth is imple-
mented according to the specifications and solved in line 23. The separation of the solu-
tion is performed in line 24: N1,N2= z.sol(t). In lines 29 and 30, the plot method
prepares the visualization for the relation N1, N2 = 𝑓(t).
In the first graph, the mean value of both populations is visualized by a dash-dot line.
349
6 Numerical Computations and Simulations Using SciPy
three groups: the healthy S (Susceptible), the infected I (Infective), and the recovered R
(Removed).
The following conditions should apply when we create the model:
쐍 Deaths and births are to be disregarded. As a result, at all times, the following
applies: .
쐍 Infected individuals are immediately contagious.
쐍 Healthy individuals are infected at an infection rate of b > 0.
쐍 Infected individuals recover at a recovery rate of g > 0.
Listing 6.34 simulates the infection process for a population of N = 1,000 individuals
over a period of 120 days. For the recovery rate and infection rate, the assumptions are
g = 0.04 and b = 0.4.
01 #34_epidemic.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy.integrate import solve_ivp
05 tmax=120
06 S0=997 #not immune healthy individuals
07 I0=3 #infected
08 R0=0 #recovered
09 N=S0+I0+R0 #Population
10 b=0.4 #infection rate
11 g=0.04 #recovery rate
12 #system of differential equations
13 def dgl(t,ya):
14 S,I,R=ya
15 dS_dt=-b*S*I/N #not immune healthy individuals
16 dI_dt=b*S*I/N-g*I #infected
17 dR_dt=g*I #recovered
18 return [dS_dt,dI_dt,dR_dt]
19 #Initial values
20 y0 = [S0,I0,R0]
21 t = np.linspace(0, tmax, 500)
22 z=solve_ivp(dgl,[0,tmax],y0,dense_output=True)
350
6.12 Project Task: Simulation of an Epidemic
23 S, I, R = z.sol(t)
24 fig, ax = plt.subplots()
25 ax.plot(t, S,'b-',label="Healthy")
26 ax.plot(t, I,'r--',label="Infected")
27 ax.plot(t, R,'g-.',label="Recovered")
28 ax.legend(loc='best')
29 ax.set(xlabel="Time",ylabel="Individuals")
30 ax.grid(True)
31 plt.show()
Output
Analysis
In lines 06 to 08, the initial values for the healthy S0, infected I0, and recovered R0 are
defined. Line 09 uses these values to compute the total number N of the population in
which the epidemic is spreading. By changing the infection rate b in line 10 and the
recovery rate g in line 11, you can simulate different infection profiles.
The code of the differential equation system consists of three lines (lines 15 to 17). In line
23, the entire solution set of the differential equation system is assigned to the S, I,R
tuple.
The output shown in Figure 6.34 clearly and plausibly shows that, during the course of
infection, the number of uninfected S decreases and the number of infected I increases
351
6 Numerical Computations and Simulations Using SciPy
6.13 Tasks
1. A triangular current with imax = 1 A runs through a coil. The period duration is 20 ms.
Write a program that visualizes the current profile and calculates and graphs the
voltage profile.
2. Write a program that calculates the length of a logarithmic spiral defined by the fol-
lowing:
Write a program that computes the amount of charge Q contained in the cuboid
using the tplquad() function.
8. Write a program that solves the following differential equations in the interval [-1,1]
numerically:
The results should be presented as a function plot. The following applies to the ini-
tial values: y(0) = 1.
352
6.13 Tasks
9. The pendulum motion of a rod with length is described by the following differen-
tial equation:
The damping d has the value 0.5. Write a program that computes the deflection φ ,
the angular velocity , and the trajectory. The solutions of the differential equation
should be represented in each case in a subdiagram.
10. Write a program that simulates exponential growth and logistic growth. The differ-
ential equations should be solved using the solve_ivp() function.
11. A spring pendulum oscillating in the direction of the y-axis is described by the dif-
ferential equation . The mass is m = 0.5kg. The damping has a
value of d = 0.5. The value of the spring constant is c = 10 N/m. Solve the initial value
problem using solve_ivp() for y0 = 0.1m and v0 = 0. The deflection y(t), the velocity
v(t), and the phase diagram v=𝑓(y) should each be shown in a subdiagram.
12. Simulate Foucault’s pendulum. The pendulum has a length of l = 67m. The steel
sphere has a diameter of d = 1.896dm. The pendulum is deflected by 2m from the
rest position in the direction of the x-axis. In addition, the trajectory and the phase
diagram v = 𝑓(x) should be shown in two additional subdiagrams. Supplement the
code provided in Listing 6.21 accordingly.
13. An oscillatory system consisting of two string pendulums whose masses are cou-
pled with a helical spring is described by the following system of differential equa-
tions:
The spring constant has a value of c = 1N/m. The two masses each have a value of m
= 0.2kg. The threads both have a length of l = 0.2m. The differential equation system
is to be solved using the solve_ivp() function. The deflections of angles 𝜑1 and 𝜑2
should each be plotted in a subplot. Write an appropriate program.
14. Write a program that will solve the following differential equation system in the
interval of [0,10]:
Use the solve_ivp() function for y1(0) = y2(0) = 1. For the coefficients: a = b = c = 1 and
d = 0.1. The result should be displayed as a function plot.
353
6 Numerical Computations and Simulations Using SciPy
15. Write a program that will solve the following differential equation system in the
interval of [0,1]:
Use the solve_ivp() function for y1(0)=1 and y2(0)=0. The result should be presented
in three subdiagrams.
16. Write a program that solves the following fourth-order differential equation in the
interval of [0,5]:
The following assumptions apply: N = 1,000, t = 120 days, recovery rate g = 0.035,
infection rate b = 0.4, and mortality rate m = 0.005.
18. Write a program that computes the harmonic distortion and frequency spectrum
of a sinusoidal AC voltage rectified by a one-way rectifier.
19. Write a program that computes the Butterworth coefficients for a fifth-degree low-
pass for a cutoff frequency of 1Hz using the butter() function.
20. Simulate the frequency response of a third-degree Butterworth bandstop using the
butter() function for the cutoff frequencies 50 Hz and 500 Hz.
354
Chapter 7
3D Graphics and Animations
Using VPython
In this chapter, you’ll learn how to represent and animate objects in 3D
space using the VPython module. Sophisticated animations of physical
processes will showcase the capabilities of VPython.
The letter “V” in the name of the VPython module stands for visual, referring to the rep-
resentation and movement of objects in 3D space. The dynamics of physical relation-
ships and processes are no longer depicted as function plots, as done with the SciPy
module, but rather as how an observer perceives them in reality. It is important to con-
sider that many physical processes, such as an oblique throw, may not be captured in
detail by the human eye. VPython provides the ability to slow down (slow motion) or
speed up processes using the rate(frequency) function as needed. When real move-
ments are simulated on a computer, the field of computer graphics refers to this pro-
cess as animation, which means giving “life” to objects.
The canvas where the objects are represented is referred to as a scene. Within a scene,
you can rotate the displayed object around the x-y-z axis by holding down the right
mouse button and moving the mouse cursor. This task allows the observer to view an
object from different perspectives.
Starting from version 7, the VPython module is imported using the from vpython import
* statement. Other modules do not need to be imported. After starting the program,
the default browser opens, and the program runs within Web Graphics Library (WebGL).
WebGL is a JavaScript programming interface that allows hardware-accelerated 3D
graphics to be displayed in a web browser without additional extensions.
The position of an object in 3D space can be determined using the vector(x, y, z)
method and can be changed if the object is supposed to move. Since the concept of vec-
tors plays a central role in VPython, let’s briefly discuss this topic using a small console
example:
355
7 3D Graphics and Animations Using VPython
<5, 7, 9>
>>> type(v1)
<class 'vpython.cyvector.vector'>
In the second and third lines, the vector(x,y,z) method creates the v1 and v2 objects as
three-dimensional vectors with the data for the x-y-z coordinates. In the fourth line,
the addition of these vectors takes place. Besides the addition, the scalar product and
the cross product are also implemented. Instead of vector(), the abbreviation vec() is
also allowed.
A body object obj is created using the following statement:
obj=body(pos=vec,size=vec,axis=vec,color=color.color,...)
In this context, the body method represents basic shapes such as box(), sphere(), cylin-
der(), cone(), and so on, which are provided by the VPython module. If you need other
bodies, you can create them from the basic bodies (k1, k2, etc.) using the compound
([k1,k2, ...]) method.
Start the program, which will run in your default browser after a short time delay. Then,
right-click on the canvas, hold the right mouse button down, and rotate the coordinate
system so that you can observe as many perspectives as possible. The gray point with
356
7.1 The Coordinate System
the coordinates (5,5,0) inserted in the coordinate system facilitates your orientation in
space and to clarify the mode of action of the coordinate transformation in VPython.
01 #01_coordinates.py
02 from vpython import *
03 h=10. #height
04 b=10. #width
05 t=10. #depth
06 scene.title="<h2>Coordinate system of VPython</h2>"
07 scene.width=scene.height=600
08 scene.background=color.white
09 scene.center=vector(0,0,0)
10 scene.range=1.5*b
11 x0=vector(-b,0,0)
12 y0=vector(0,-h,0)
13 z0=vector(0,0,-t)
14 #x-axis is red
15 arrow(pos=x0,axis=vector(2*b,0,0),shaftwidth=0.15,color=color.red)
16 #y-axis is green
17 arrow(pos=y0,axis=vector(0,2*h,0),shaftwidth=0.15,color=color.green)
18 #z-axis is blue
19 arrow(pos=z0,axis=vector(0,0,2*t),shaftwidth=0.15,color=color.blue)
20 label( pos=vec(b,-1,0),text="x",height=30,box=False,opacity=0)
21 label( pos=vec(-1,h,0),text="y",height=30,box=False,opacity=0)
22 label( pos=vec(-1,0,t),text="z",height=30,box=False,opacity=0)
23 points(pos=vector(5,5,0))
24 scene.caption="\nPress right mouse button and drag"
Analysis
In line 02, the vpython module is imported with all available methods and properties.
Lines 03 to 05 define the height, width, and depth of the display window (canvas).
The scene.title="..." command in line 06 outputs the headline of the program with
the HTML tag <h2> ... </h2> in the browser.
In line 07, the scene.width=scene.height=600 command sets the width and height of the
display window to 600 pixels each.
In line 08, scene.background=color.white sets the background of the canvas to the color
white. The default setting is the color black.
The scene.center=vector(0,0,0) statement in line 09, which sets the origin of the coor-
dinate system, is actually not necessary because the default value places the origin
357
7 3D Graphics and Animations Using VPython
exactly in the center of the canvas. We only included it in this example so we could
carry out further program tests.
Due to scene.range=1.5*b (line 10), the display range is enlarged so that enough space
on the canvas exists when you rotate the coordinate system.
In lines 11 to 13, the VPython method vector() is used to move the coordinate axes.
A negative sign causes a shift in positive direction of the coordinate axis to the right
(x-axis) or upwards (y-axis) or forwards (z-axis). All three axes have a length of 20 units.
The arrow() method draws the coordinate axes as arrow objects in lines 15, 17, and 19.
The label() method labels the coordinate axes (lines 20 to 22).
The point created using the points(pos=vector(5,5,0)) method should confirm the
correctness of the coordinate transformations: The point lies exactly on half of the
intercept of the x- and y-axis.
Using scene.caption="...", you can output any text in the browser (line 24).
Exercise
The point should be represented in the second, third, and fourth quadrants. Change
the source code in each case and restart the program. Check the positions of the point
by rotating the coordinate system within the scene.
obj=body(pos=vec(x0,y0,z0),axis=vec(x,y,z),size=vec(a,b,c),
color=color.red)
The first parameter specifies the position of the body in 3D space. The default setting
for the position is pos=vector(0,0,0). The position can be changed within the anima-
tion loop using obj.pos=vector(x,y,z). If only the position in x-direction needs to be
changed, the obj.pos.x= value statement is sufficient.
The axis vector defines the orientation of the body object. For example, if vector
(1,0,0) is assigned to axis, the body will be aligned in the direction of the x-axis. The
same applies to the y- and z-axes.
The size vector determines the dimensions of the body object. For example, the
box(size=vector(10,5,2) method creates a cuboid object with a width of 10 units of
length (LE), a height of 5 LE, and a depth of 2 LE.
358
7.2 Basic Shapes, Points, and Lines
The color property can be varied via the obj.color=vector(R,G,B) vector. The colors red,
green, and blue can have values between 0 and 1. The default color setting is gray.
7.2.1 Cylinder
A cylinder object has the following properties: position, length, orientation, radius, and
color. The pos=vector(x0,y0,z0) vector defines the position (center of the base of the
cylinder) in space. The axis=vector(x,y,z) vector determines the length and orienta-
tion of a cylinder object. Using the following method, a cylinder object of the cylin-
der() class can be created from the VPython module:
cylinder(pos=vector(x0,y0,z0),axis=vector(x,y,z),radius=r,...)
The default color (the default value) is gray. The radius has the default value radius=1.
For example, if x0=-20, x=40, and all other values of the position and axis vector are zero,
then a cylinder object with the length of 40 units of length (LE) is created, shifted left by
20 LE on the x-axis. The centerline of the cylinder lies exactly on the x-axis.
Alternatively, the dimensions of a cylinder object can be specified using the size=
vector(length,height,width) property.
Listing 7.2 shows how to create a cylinder object in VPython.
01 #02_cylinder.py
02 from vpython import *
03 scene.title="<h2>Cylinder</h2>"
04 scene.autoscale=True
05 scene.background=color.white
06 scene.width=600
07 scene.height=600
08 scene.center=vector(0,0,0)
09 scene.range=30
10 #Position: x0,y0,z0
11 p=vector(-20,0,0)
12 #alignment and length
13 a=vector(40,0,0)
14 r=10. #radius
15 #col=color.gray(0.5)
16 #red, green, blue
17 col=vector(1,0,0)
18 cylinder(pos=p,axis=a,radius=r,color=col,opacity=0.5)
19 #length, height, width
20 #cylinder(pos=p,size=vector(40,20,20),color=col)
21 scene.caption="\nPress the right mouse button and rotate the object"
359
7 3D Graphics and Animations Using VPython
Output
Figure 7.2 shows the cylinder created by Listing 7.2 in the canvas.
Analysis
The scene.range=30 property (line 09) changes the width of the display area. A value
smaller than 30 (line 09) enlarges the cylinder object, and a value larger than 30
reduces the cylinder object.
The p=vector(-20,0,0) vector in line 11 specifies that the cylinder is moved 20 LE to the
left on the x-axis. The a=vector(40,0,0) vector (line 13) defines the length of the cylin-
der of 40 LE. The centerline of the cylinder lies on the x-axis because the y- and z-com-
ponents of the alignment vector axis have the value zero.
The color of a body is determined as an RGB value in line 17 by the col=vector(1,0,0)
vector. The color saturation can be set with values from 0 to 1. The opacity property
determines the opacity of a color. A value between 0 and 1 is permissible. If opacity has
the value 0, the body is completely transparent.
360
7.2 Basic Shapes, Points, and Lines
cylinder(pos=p,axis=a,radius=r,color=col,opacity=0.5)
This cylinder object has its properties stored in the objects p, a, r, and col (lines 11, 13, 14,
and 17).
Exercise
Change the property of the scene.range canvas in line 09 to 40, 50, and 60. What
exactly is happening here?
Change the colors, the positions, and the dimensions of the cylinder. Restart the pro-
gram after each individual change. Check if the changes have the expected effects.
Comment out line 18 and remove the comment in line 20. Restart the program and
observe the result by rotating the cylinder within the scene. Vary the values of the
size() vector and restart the program after each change.
7.2.2 Cuboid
A cuboid object can be created using the following method:
box(pos=vec(x0,y0,z0),axis=vec(x,y,z),size=vec(L,H,B), ...)
The pos vector defines the position of the cuboid. In contrast to the cylinder object, the
position in this case does not refer to one end of the object, but to the center of the
cuboid. The axis vector defines the orientation, while the size vector defines the
dimensions (length, height, width) of the cuboid object.
Listing 7.3 creates a cuboid with length 40, height 20, and width 10.
01 #03_cuboid.py
02 from vpython import *
03 scene.title="<h2>Cuboids and other shapes</h2>"
04 scene.autoscale=True
05 scene.background=color.white
06 scene.width=600
07 scene.height=600
08 scene.center=vector(0,0,0)
09 #x0,y0,z0
10 p=vector(0,0,0)
11 #alignment
12 a=vector(1,0,0)
13 #dimensions: length, height, width
14 dim=vector(40,20,10)
15 scene.range=30
361
7 3D Graphics and Animations Using VPython
16 #rotation
17 d=vector(0,0,0)
18 c=color.gray(0.5)
19 box(pos=p,axis=a,size=dim,up=d,color=c)
20 #cone(pos=vec(-5,0,0),axis=vector(10,0,0),radius=5,color=c)
21 #ellipsoid(pos=vec(0,0,0),axis=vec(1,0,0),size=vec(10,5,5),color=c)
22 #pyramid(pos=vec(0,5,0),axis=vec(0,1,0),size=vec(10,12,12),color=c)
23 #ring(pos=vec(0,0,0),axis=vec(0,0,1),radius=10,thickness=3,color=c)
24 scene.caption="\nPress the right mouse button and rotate the object"
Output
The cuboid that is output to the canvas is shown in Figure 7.3.
362
7.2 Basic Shapes, Points, and Lines
Analysis
In line 10, the p=vector(0,0,0) vector specifies that the cuboid is positioned exactly in
the center of the drawing area. The a=vector(1,0,0) vector (line 12) determines the
orientation of the cuboid: Its centerline lies on the x-axis. The dim=vector(40,20,10)
vector in line 14 provides the dimensions of the box: It has a length of 40 LE, a height of
20 LE, and a width of 10 LE. Using the d=vector(0,0,0) vector in line 17, you can rotate
the object around its own axis.
In line 19, the box() method creates the cuboid object with the given properties.
Exercise
Change the alignment vector in line 12 a=vector(0,1,1) and restart the program. What
exactly is happening here?
Change the d=vector(0,0,1) vector in line 17 for the property up in line 19 and restart
the program. What exactly is happening now?
Test the program with the basic shapes that have been commented out.
7.2.3 Points
Point objects can be created using the following method:
The pos property expects a list of vectors containing the positions of the point objects.
Specifying the radius property is not necessary. According to the VPython documenta-
tion, the radius should have a default value of 2.5 pixels, even if assigned the value zero.
Listing 7.4 represents the center and the vertices of a cube as point objects, resulting in
the graphical output shown in Figure 7.4.
01 #04_points.py
02 from vpython import *
03 scene.width=600
04 scene.height=600
05 scene.background=color.white
06 e=1.
07 scene.center=vector(e/2,e/2,e/2)
08 scene.range=1.2*e
09 v=[(0,0,0),(0,0,e),(0,e,0),(0,e,e),
10 (e,0,0),(e,0,e),(e,e,0),(e,e,e),(e/2,e/2,e/2)]
11 box(pos=vector(e/2,e/2,e/2),size=vector(e,e,e),
12 axis=vector(1,0,0),opacity=0.5)
13 points(pos=v,color=color.red)
363
7 3D Graphics and Animations Using VPython
Output
Analysis
Line 06 specifies the edge length e of the cube. In line 07, the center of the coordinate
system is scaled to half the edge length. Line 09 contains a list named v containing the
positions for the center and the vertices of the cube. In line 11, the box() method creates
a transparent cube with the edge length e. In line 13, the points() method creates nine
red point objects with the default radius.
7.2.4 Lines
The following method draws a line between two points:
For example, let’s suppose the two vectors v1 and v2 are given. Then, you can draw a
connecting line between the two vectors using five different syntax variants:
>>> curve(v1,v2) #1
>>> curve([v1,v2]) #2
>>> curve(pos=[v1,v2]) #3
>>> c = curve(v1) #4
>>> c.append(v2)
>>> c=curve() #5
>>> c.append(v1,v2)
Listing 7.5 shows how the curve() method connects the vertices of a tetrahedron with
lines of the length . The base of the tetrahedron is placed in the x-y plane. From the
364
7.2 Basic Shapes, Points, and Lines
radius r of the circumcircle of the base area, the edge length can be computed in the fol-
lowing way:
01 #05_lines.py
02 from vpython import *
03 scene.width=600
04 scene.height=600
05 scene.background=color.white
06 r=10. #radius of the plane
07 e=sqrt(3.)*r #edge length
08 scene.center=vector(0,0,0)
09 scene.range=1.8*r
10 x=r*cos(pi/6.)
11 y=r*sin(pi/6.)
12 z=sqrt(6.)*e/3. #height
13 #triangle: bottom left-top, bottom right-bottom left
14 v1=[(-x,-y,0),(0,r,0),(x,-y,0),(-x,-y,0)]
15 #star: bottom left-center, top-center, bottom right-center
16 v2=[(-x,-y,0),(0,0,z),(0,r,0),(0,0,z),(x,-y,0)]
17 points(pos=v2,radius=10.,color=color.red)
18 c=curve(pos=v1,color=color.green)
19 c.append(v2,color=color.yellow)
Output
Figure 7.5 shows the result of Listing 7.5 on the canvas.
Analysis
Line 06 sets the radius r of the x-y plane circumcircle (circumscribed circle) to 10 units
of length. Line 07 calculates the edge length e. In lines 10 and 11, the x and y coordinates
of the x-y plane are calculated. The statement in line 12 computes the height z of the
tetrahedron in the direction of the z-axis. The v1 list in line 14 contains the coordinate
data of the triangular base area of the x-y plane. The v2 list in line 16 contains the x-y-z
coordinates of the tetrahedron corners. In line 17, the points() method draws the four
vertices of the tetrahedron. The curve() method draws the line objects of the v1 list as a
triangle in line 18. In line 19, the star-shaped lines of the v2 list are added to the polyline c.
365
7 3D Graphics and Animations Using VPython
Exercise
Comment out lines 17 and 19, and restart the program. What do you see now?
Only the polyline of v2 should be displayed. Change the program accordingly and re-
start the program.
7.2.5 Sphere
The sphere(pos=vector(x0,y0,z0),radius=r,...) method creates a sphere object. The
pos vector determines the coordinates of the center of a sphere in space, and the radius
property determines its radius.
Listing 7.6 shows how the sphere() method can be used to arrange nine spheres sym-
metrically in space.
01 #06_spheres1.py
02 from vpython import *
03 scene.width=600
04 scene.hight=600
05 scene.background=color.white
06 x0=5.
07 y0=5.
08 z0=5.
09 R1=1.
10 R2=0.25
11 #center
12 sphere(pos=vector(0,0,0),radius=R1,color=color.red)
13 #top
14 sphere(pos=vector(0,y0,0),radius=R2,color=color.blue)
15 #bottom
16 sphere(pos=vector(0,-y0,0),radius=R2,color=color.blue)
366
7.2 Basic Shapes, Points, and Lines
17 #left
18 sphere(pos=vector(-x0,0,0),radius=R2,color=color.blue)
19 #right
20 sphere(pos=vector(x0,0,0),radius=R2,color=color.blue)
21 #back
22 sphere(pos=vector(0,0,-z0),radius=R2,color=color.blue)
23 #front
24 sphere(pos=vector(0,0,z0),radius=R2,color=color.blue)
25 label( pos=vec(0,0,0), text="O",height=30,box=False,opacity=0)
26 sphere(pos=vector(x0/2,0,0),radius=R2,color=color.blue)
27 sphere(pos=vector(-x0/2,0,0),radius=R2,color=color.blue)
Output
Analysis
Lines 06 to 08 define the coordinates of the small blue spheres in space. The center red
sphere has a radius of R1=1 (line 09), and the six other spheres have a radius of R2=0.25
each. In lines 12 to 24, the sphere() method creates the individual sphere objects. The
comments describe the positions of the individual spheres. In line 25, a label object
named O is created. This label suggests that a Bohr model of an oxygen atom (without
the inner shell) is represented by this object, as shown in Figure 7.6.
Exercise
Start the program and view the scene from different perspectives.
Add the statements for the two electrons of the inner shell of the oxygen atom to the
source code and start the program.
367
7 3D Graphics and Animations Using VPython
Crystal Lattice
The example shown in Listing 7.7 illustrates how you can place spheres within space
using three nested for loops and the sphere() method.
01 #07_spheres2.py
02 from vpython import *
03 scene.background=color.white
04 scene.width=600
05 scene.height=600
06 e = 5 #lattice spacing
07 R = 0.5 #radius of an atomic nucleus
08 for x in range(-e,e):
09 for y in range(-e,e):
10 for z in range(-e,e):
11 sphere(pos=vector(x,y,z),radius=R,color=color.red)
Output
Analysis
Three nested for loops (lines 08 to 11) compute the x-y-z coordinates for the red
spheres. The first for loop in line 08 sets the positions of the sphere objects on the x-
axis. The second for loop in line 09 sets the positions on the y-axis. The third for loop
in line 10 sets the positions on the z-axis. In line 11, the sphere(pos=vector(x,y,z),...)
method creates the individual red sphere objects. The representation in shown in
Figure 7.7 can be interpreted as a model for a crystal lattice, with the red spheres repre-
senting positively charged atomic nuclei.
368
7.2 Basic Shapes, Points, and Lines
Exercise
Start the program and view the scene from different perspectives.
How many spheres are represented?
7.2.6 Penetration
A problem known from descriptive geometry, penetration explores how mutually pen-
etrating bodies must be represented in three views, a concept often difficult for begin-
ners. For this purpose, a program that illustrates the spatial representation of
interpenetrating bodies is provided in Listing 7.8. Its graphical output is shown in
Figure 7.8, which spatially represents the interpenetration of a cone and a cylinder. This
program can be used to simulate different views of this penetration.
01 #08_penetration.py
02 from vpython import *
03 rc=10. #radius of the cone
04 hc=3.*rc #height of the cone
05 scene.background=color.white
06 scene.width=600
07 scene.hight=600
08 scene.range=2.1*rc
09 rz=rc/2. #radius of the cylinder
10 lz=2.5*rc #length of the cylinder
11 z=rc/1.5 #displacement of the cylinder
12 cone(pos=vec(0,-hc/2.5,0),axis=vec(0,hc,0),radius=rc)
13 cylinder(pos=vec(-lz/2.,0,z),axis=vec(lz,0,0),radius=rz)
Output
369
7 3D Graphics and Animations Using VPython
Analysis
The radius rc and the height hc of the cone are the reference values (lines 03 and 04).
The radius rz and the length lz of the cylinder (lines 09 and 10) depend on these values.
In line 12, a cone object is created using the cone() method. The axis=vec(0,hc,0) vector
defines its orientation in the direction of the y-axis.
In line 13, a cylinder object is created using the cylinder() method. The axis=vec
(lz,0,0) vector defines its orientation in the direction of the x-axis.
Exercise
Start the program and view the scene of the cone-cylinder penetration from the per-
spective of the front view, the side view from the left and of the top view.
Simulate different distances on the z-axis and different penetration angles.
01 #09_combination.py
02 from vpython import *
03 scene.background=color.white
04 scene.width=scene.height=600
05 a=5.
06 b=10.
07 scene.range=1.5*b
08 scene.autocenter=True
09 p1=pyramid(pos=vec(0,a,0),axis=vec(0,1,0),size=vec(a,a,a),color=color.green)
10 q1=box(pos=vector(0, a/2,0),size=vector(a,a,a),color=color.red)
11 q2=box(pos=vector(0,-b/2,0),size=vector(b,b,b),color=color.blue)
12 werkstueck=compound([p1,q1,q2])
Output
Figure 7.9 shows the resulting body composed of two cubes and a pyramid.
Analysis
In line 09, the p1 object is created using the following method:
pyramid(pos=vec(0,a,0),axis=vec(0,1,0),size=vec(a,a,a)
370
7.3 Bodies in Motion
The pyramid object p1 is shifted upwards by 5 units of length in the direction of the pos-
itive y-axis. The alignment is in the direction of the y-axis. The length, width, and
height of the pyramid each have a value of 5 units of length.
The q1 cube is shifted upwards by 2.5 units of length on the positive y-axis (line 10). The
q2 cube is shifted downwards by 5 units of length on the negative y-axis (line 11).
In line 12, the compound([p1,q1,q2]) method creates the workpiece object as a com-
pound body.
Exercise
Start the program and view the composite body in the front view, side view, and top
view.
Test the program with different dimensions.
371
7 3D Graphics and Animations Using VPython
01 #10_ball_vertical.py
02 from vpython import *
03 r=1. #radius
04 h=5. #height
05 scene.background=color.white
06 scene.center=vector(0,h,0)
07 box(pos=vector(0,0,0),size=vector(2*h,r/2,h), color=color.green)
08 ball = sphere(radius=r, color=color.yellow)
09 ball.pos=vector(0,2*h,0)
10 ball.v = vector(0,0,0) #velocity vector
11 g=9.81 #gravitational acceleration
12 dt = 0.01
13 while True:
14 rate(100)
15 ball.pos = ball.pos + ball.v*dt
16 if ball.pos.y < r:
17 ball.v.y = abs(ball.v.y) #upwards
18 else:
19 ball.v.y = ball.v.y - g*dt #downwards
Output
372
7.3 Bodies in Motion
Analysis
Line 06 causes the floor object box() from line 07 to be moved down by 5 units of
length. In line 08, the ball object is created. Line 09 determines the drop height of
10 units of length. In line 10, the velocity of the ball is initialized with the ball.v=
vector(0,0,0) property. In line 12, the time interval dt=0.01 was set to a realistic value.
The infinite loop runs between lines 13 to 19. This loop is executed 100 times per second
because of rate(100) (line 14). In line 15, the new ball position ball ball.pos is computed
from the old ball position and the product of velocity ball.v and time interval dt. If the
ball position is smaller than the ball radius r, the ball object ball moves upwards (lines
16 and 17); otherwise, it moves downwards (lines 18 and 19).
Exercise
Test the program with different time intervals and frame rates.
Test the program with different radii and heights.
Test the program with different accelerations (such as Moon and Jupiter).
01 #11_cylinder_horizontal.py
02 from vpython import *
03 scene.background=color.white
04 scene.width=600
05 scene.height=300
06 l=10. #length of the coil
07 r=l/5. #radius of the core
08 scene.center=vector(0,0,0)
09 cs=vector(1,0.7,0.2) #copper-colored
10 helix(pos=vec(-l/2,0,0),axis=vec(l,0,0),radius=1.25*r,
coils=10,thickness=0.3,color=cs)
11 np = cylinder(pos=vec(l/2,0,0),axis=vec(l/2,0,0),radius=r, color=color.red)
12 sp = cylinder(pos=vec(0,0,0),axis=vec(l/2,0,0),radius=r, color=color.green)
13 magnet=compound([np,sp])
14 magnet.pos=vector(0,0,0)
15 dx = 0.1
16 while True:
17 rate(50)
18 x = magnet.pos
373
7 3D Graphics and Animations Using VPython
19 x = x+vector(dx,0,0)
20 magnet.pos = x
21 if x.x>l/4. or x.x<=-l/4.:
22 dx = -dx
Output
A snapshot of the resulting animated magnet movement is shown in Figure 7.11.
Analysis
In line 10, the coil object is created using the helix() method. The left edge is shifted
5 units of length to the left on the negative x-axis. The length l of the coil is 10 units of
length. The coil has ten turns. In line 11, the red-colored north pole np of the bar magnet
is created using the cylinder() method. The same process is repeated in line 12 for the
green-colored south pole sp of the bar magnet. In line 13, the compound([np,sp]) method
creates the magnet object.
The animation of the horizontal movement of the bar magnet is performed in the
while loop (lines 16 to 22). In line 18, the x variable is assigned the magnet.pos position of
the magnet initialized in line 14. The sum algorithm in line 19 calculates the new x posi-
tion of the magnet. This position is assigned to the magnet.pos property in line 20. If the
deflection is greater than l/4 or less than -l/4 (line 21), a reversal of the direction of
motion occurs due to the change in sign dx = -dx (line 22).
01 #12_ball_wall.py
02 from vpython import *
374
7.3 Bodies in Motion
03 scene.width=scene.height=600
04 scene.background=color.white
05 cw=color.gray(0.9) #color of the walls
06 b = 5.0 #width
07 d = 0.3 #thickness of the wall
08 r=0.4 #ball radius
09 s2 = 2*b - d
10 s3 = 2*b + d
11 #right hand wall
12 box (pos=vec(b, 0, 0), size=vec(d, s2, s3), color = cw)
13 #left hand wall
14 box (pos=vec(-b, 0, 0), size=vec(d, s2, s3), color = cw)
15 #bottom wall
16 box (pos=vec(0, -b, 0), size=vec(s3, d, s3), color = cw)
17 #top wall
18 box (pos=vec(0, b, 0), size=vec(s3, d, s3), color = cw)
19 #back wall
20 box(pos=vec(0, 0, -b), size=vec(s2, s2, d), color = cw)
21 ball = sphere(radius=r,color=color.yellow)
22 ball.m = 2.0 #mass of the ball
23 ball.p = vec(-0.15, -0.23, 0.27) #impulse
24 #ball.p = vec(0,-1,0)
25 #ball.p = vec(-1,0,0)
26 #ball.p = vec(0,-1,-1)
27 b = b - d*0.5 - ball.radius
28 dt = 0.2
29 while True:
30 rate(100)
31 ball.pos = ball.pos + (ball.p/ball.m)*dt
32 if not (b > ball.pos.x > -b):
33 ball.p.x = -ball.p.x
34 if not (b > ball.pos.y > -b):
35 ball.p.y = -ball.p.y
36 if not (b > ball.pos.z > -b):
37 ball.p.z = -ball.p.z
Output
A snapshot of the resulting animated ball movement is shown in Figure 7.12.
375
7 3D Graphics and Animations Using VPython
Analysis
In lines 12 to 20, the box objects for the walls are created. The yellow sphere object ball
created in line 21 has a radius of r=0.4 (line 08). Line 22 defines a new property m for the
ball object. The identifier m is freely selectable. One could have chosen the designator
mass for the mass of the ball. The ball.p vector defined and initialized in line 23 rep-
resents the impulse of a mass. The identifier p is also freely selectable. As a reminder,
the following equation applies for the impulse:
When the ball bounces on the side, top, bottom, or back walls, respectively, the if que-
ries in lines 32 to 37 cause the ball motion to reverse its direction.
376
7.3 Bodies in Motion
Exercise
Test the program with the commented-out lines 24 to 26. What exactly is happening?
In line 21, add the make_trail=True, retain=200 properties and restart the program.
Test the program with other masses.
The trajectory depends on the initial velocity v0, the throwing angle α, and the throwing
height h.
Listing 7.13 shows the implementation of the animation of the oblique throw. The pro-
gram does not start the motion sequence until the left mouse button is clicked on the
canvas.
01 #13_oblique_throw.py
02 from vpython import *
03 h=1.2 #throwing height
04 b=60. #width of the reference plane
05 v0=22.5 #initial velocity
06 alpha=45. #throwing angle
07 alpha=radians(alpha)
08 g=9.81
09 r=b/40.
10 h=h+r
11 scene.background=color.white
12 scene.width=600
13 scene.height=600
14 scene.center=vector(0,b/4.,0)
15 ball = sphere(pos=vector(-b/2.,h,0),radius=r,color=color.yellow)
16 box(pos=vec(0,-b/50.,0),size=vec(b,b/25.,b/2.),color=color.green)
17 scene.caption="\nStart with mouse click"
18 scene.waitfor('click')
19 dt=0.01
20 t=0.0
21 while True:
22 rate(50)
377
7 3D Graphics and Animations Using VPython
23 x = v0*t*cos(alpha)
24 y = h + v0*t*sin(alpha) - 0.5*g*t**2
25 ball.pos = vector(x-b/2.,y+r,0)
26 if y<=0.0:
27 break
28 t=t+dt
Output
A snapshot of the resulting animated oblique throw is shown in Figure 7.13.
Analysis
In line 18, the scene.waitfor('click') statement causes the animation not to be exe-
cuted until after a mouse click within the scene. With this interruption, it is possible to
better identify the throwing height. Within the animation loop (lines 21 to 28), the x and
y components of the ball motion are computed in lines 23 and 24. In line 25, the current
vector of the motion position is assigned to the ball.pos property. When the ball
reaches the reference plane (line 26), the break statement aborts the animation.
Exercise
Test the program with different throwing angles and throwing velocities.
Vary the throwing heights as well.
If the semiaxes a and b are equal, a circular motion is animated; otherwise, the body
moves on an elliptical trajectory.
378
7.3 Bodies in Motion
01 #14_elliptical_orbit.py
02 from vpython import *
03 scene.width=600
04 scene.height=600
05 b=10. #semiaxis of the ellipse
06 a=1.157*b #semiaxis of the ellipse
07 Rm=1. #Moon radius
08 Re=3.7*Rm #Earth radius
09 rem=10.*Re #Earth-Moon distance
10 scene.background=color.white
11 earth = sphere(pos=vector(0.1*a,0,0),radius=Re,texture=textures.earth)
12 moon = sphere(pos=vector(rem,0,0),radius=Rm,color=color.gray(0.8))
13 w=1.0 #angular velocity
14 t=0
15 dt=1e-3
16 while True:
17 rate(100)
18 x = a*cos(w*t)
19 y = b*sin(w*t)
20 moon.pos = vector(x,y,0)
21 t=t+dt
Output
A snapshot of the resulting animated elliptical orbit of the moon is shown in Figure
7.14.
Analysis
In lines 05 to 09, the data for our Earth-Moon planetary system is defined. In line 11, the
sphere() method creates the earth object. The spherical earth object is moved to the
379
7 3D Graphics and Animations Using VPython
right on the positive x-axis by the amount 0.1*a. We chose this unrealistically high
value to illustrate that the geometric location of the Earth does not coincide with
the center of the ellipse. The surface of a body object can represented visually with the
texture property; obviously, we chose the textures.earth value. The earth object would
not actually need to be created explicitly because it will no longer be used further down
in the source code. The situation is different with the Moon. An object must be created
for the Moon because it is needed in the animation loop. The creation of the moon object
takes place in line 12.
The angular velocity w=1.0 does not correspond to reality (line 13). We set this value
arbitrarily to better understand the motion of the moon.
The while loop (lines 16 to 21) implements the animation. In line 20, for each time t the
moon.pos property is assigned the x-y coordinates determined in lines 18 and 19.
body .rotate(angle=w*dt,axis=vec(0,1,0),origin=vec(0,0,0))
The angle property must be assigned the angle w*dt. The rotational motion comes
about by computing a new angle for each time (dt) from the angular velocity w, which is
assumed to be constant. The axis vector axis specifies the axis of rotation, and the ori-
gin property specifies the center of rotation.
Listing 7.15 animates the rotation of a cube. The commented-out lines are for testing
purposes.
01 #15_rotation1.py
02 from vpython import *
03 scene.width=scene.height=600
04 scene.background=color.white
05 scene.center=vec(0,0,0)
06 r=1.
07 col=color.green
08 scene.range=1.5*r
09 red = box(pos=vec(0,0,0),axis=vec(0,1,0),size=vec(r,r,r),color=col)
10 #red =ring(pos=vec(0,0,0),axis=vec(0,0,1),radius=r,thickness=r/5.)
11 #red=ellipsoid(pos=vec(0,0,0),axis=vec(1,0,0),size=vec(2.0*r,r,r))
12 #red = arrow(pos=vec(0,0,0), axis=vec(r,0,0), color=col)
13 dt = 0.05
14 w=0.5 #angular velocity
380
7.3 Bodies in Motion
15 while True:
16 rate(25)
17 red.rotate(angle=w*dt,axis=vec(0,1,0),origin=vec(0,0,0))
Output
A snapshot of the resulting animation of a rotating cube is shown in Figure 7.15.
Analysis
In line 09, the box() method creates the red object with edge length r.
In line 17, the following method makes the cube rotate around the y-axis:
rotate(angle=w*dt,axis=vec(0,1,0),origin=vec(0,0,0))
By changing the angular velocity w in line 14, you can change the rotation frequency.
The center of rotation is located at the origin (zero).
Exercise
Test the program with different angular velocities. Also try different axes of rotation.
Test the program with the bodies commented out.
381
7 3D Graphics and Animations Using VPython
01 #16_rotation2.py
02 from vpython import *
03 scene.width=scene.height=600
04 scene.background=color.white
05 R=5.0 #radius of the sun
06 r=10.0 #Sun-Mercury distance
07 sphere(pos=vec(0,0,0),axis=vec(1,0,0),radius=R,color=color.yellow)
08 mercury=sphere(pos=vec(r,0,0),axis=vec(1,0,0),
radius=0.2*R,color=color.red)
09 venus=sphere(pos=vec(2*r,0,0),axis=vec(1,0,0),
radius=0.3*R,color=color.green)
10 earth=sphere(pos=vec(3*r,0,0),axis=vec(1,0,0),
radius=0.5*R,texture=textures.earth)
11 dt = 0.05
12 w1=0.3
13 w2=0.2
14 w3=0.1 #angular velocity
15 while True:
16 rate(25)
17 mercury.rotate(angle=w1*dt,axis=vec(0,0,1),origin=vec(0,0,0))
18 venus.rotate(angle=w2*dt,axis=vec(0,0,1),origin=vec(0,0,0))
19 earth.rotate(angle=w3*dt,axis=vec(0,0,1),origin=vec(0,0,0))
Output
Analysis
Lines 05 and 06 define the radius of the Sun R and the distance between Mercury and
the Sun r. The radii of the planets are defined as fractions of the solar radius, and their
distances are defined as a multiple of r.
The planetary objects, mercury, venus, and earth are generated in lines 08 to 10.
Lines 12 to 14 define the angular velocities of the planetary objects. The greater the dis-
tance between a planet and the Sun, the smaller its angular velocity must be.
382
7.3 Bodies in Motion
Within the while loop (lines 15 to 19), the rotate() method is applied to the planetary
objects mercury, venus, and earth. The axis=vec(0,0,1) vector specifies that the planets
rotate around the z-axis.
Exercise
Test the program with different angular velocities and distances.
Let the planets rotate around the y-axis.
The angle α is generated with a random number generator just like the angle . The
first angle has a value between 0 and π, and the second angle has a value between 0 and
2π. The sin α function calculates the distances of the particles from the origin of the
coordinate system. The cos φ and sin φ functions compute the randomly distributed
x-y coordinates.
Listing 7.17 shows how to animate the random, two-dimensional motion of a sphere.
The trajectories of the sphere are displayed as thin blue lines using the attach_
trail(object, ...) method.
01 #17_random.py
02 from vpython import *
03 a=10.
04 r=a/20.
05 scene.background=color.white
06 scene.width=scene.height=600
07 scene.center=vector(0,0,0)
08 scene.range=a
09 part = sphere(radius=r,color=color.red)
10 part.pos=vector(0,0,0)
11 attach_trail(part,radius=0.05,color=color.blue)
12 i=0
13 while i<10:
14 sleep(0.8)
383
7 3D Graphics and Animations Using VPython
15 alpha = pi*random()
16 phi = 2.0*pi*random()
17 x = a*sin(alpha)*cos(phi)
18 y = a*sin(alpha)*sin(phi)
19 part.pos=vector(x,y,0)
20 i=i+1
Output
A snapshot of the resulting animation of a randomly moving sphere is shown in Figure
7.17.
Analysis
In line 09, the sphere() method creates the part object. In line 10, the part.pos=vector
(0,0,0) property places the initial position of the part sphere in the origin of the coor-
dinate system. The attach_trail(part, ...) method in line 11 causes the trajectory
curves to be traced as blue lines with radius=0.05. The animation is executed ten times
within the while loop (lines 13 to 20). In line 14, the sleep(0.8) function ensures that the
loop pass gets interrupted for 0.8 seconds.
In line 15, the random() function generates random numbers between 0 and π for com-
puting the randomly distributed distances of the sphere from the coordinate origin.
The random numbers generated in line 16 lie in the interval from 0 to 2π. In lines 17 and
18, the randomly distributed x-y coordinates are computed.
384
7.4 Animation of Oscillations
Exercise
Test the program with different interruption times.
Test the program with different radii for the motion lines (line 11).
Thus, you’ll obtain a differential equation system with two first-order differential equa-
tions:
Since accuracy is not absolutely essential for animations but instead the efficiency of
the algorithm is most important, a convenient way to solve this differential equation
system is using the Euler method.
Listing 7.18 animates the motion of a simple pendulum. The differential equation sys-
tem is solved within the animation loop using the sum algorithm of the Euler method.
The pendulum consists of a cylinder object (cylinder), which represents the thread, and
a sphere object (sphere), which represents the mass.
01 #18_pendulum.py
02 from vpython import *
03 y0=-5. #shift on the y-axis
385
7 3D Graphics and Animations Using VPython
Output
A snapshot of the resulting animation of a mathematical pendulum is shown in Figure
7.18.
386
7.4 Animation of Oscillations
Analysis
Line 06 defines the deflection angle phi. The differential equation system of the pendu-
lum motion (lines 24 and 25) is solved within the animation loop (lines 22 to 29) using
the sum algorithm phi=phi+w*dt and w=w-w02*sin(phi)*dt. Lines 26 and 27 calculate the
x and y coordinates of the current sphere position. In lines 28 and 29, the positions of
the rod and of the sphere are updated.
Exercise
Test the program with different deflection angles.
Test the program with different pendulum lengths.
01 #19_spring_pendulum.py
02 from vpython import *
03 y0=-5. #shift on the y-acis
04 b=8. #width of the ceiling
05 l=0.8*y0 #length of the spring
06 r=1.2 #radius of the mass
07 c=1.1 #spring constant
08 m=1.5 #mass of the sphere
09 scene.width=600
10 scene.height=600
11 scene.center =vector(0,y0,0)
12 scene.background = color.white
13 box(pos=vector(0,b/40.,0),size=vector(b,b/20.,b/2.),
387
7 3D Graphics and Animations Using VPython
color=color.gray(0.8)) #ceiling
14 spring=helix(axis=vector(0,l,0),radius=0.6,color=color.yellow)
15 spring.thickness=0.2
16 spring.coils=8
17 mass=sphere(pos=spring.pos,radius=r,color=color.red)
18 w02=c/m #square of the angular frequency
19 y=-0.6*l #deflection
20 v=0. #initial velocity
21 dt=0.02
22 while True:
23 rate(100)
24 y=y+v*dt
25 v=v-w02*y*dt
26 spring.axis=vector(0,y+l,0)
27 mass.pos =vector(0,y+l-r,0)
Output
A snapshot of the resulting animation of a spring pendulum is shown in Figure 7.19.
Analysis
Lines 03 and 11 cause the shift of the coordinate origin by 5 units of length upwards. In
lines 05 to 08, the data of the spring-mass oscillator is defined.
In line 14, the helix() method creates the spring object spring. The properties of the
spring object are complemented in lines 15 and 16. In line 17, the sphere() method cre-
ates the sphere object mass.
388
7.5 Event Processing
The deflection (initial value) is set to 60% of the spring length l (line 19).
Within the infinite loop (lines 22 to 27), the differential equation system (lines 24 and
25) is solved using the Euler method.
The positions of the end of the spring and the center of the sphere are updated in lines
26 and 27.
Exercise
Test the program with different masses.
Test the program with different spring constants.
control(bind=function, ...)
The control identifier can be the name of a control, such as a button, slider, checkbox, or
radio button. To enable the controlelement method to trigger an event, a custom func-
tion must be passed to it as a parameter. This function is assigned to the bind property.
The parentheses of the custom function must be omitted. All other parameters depend
on the type of control. Listing 7.20 shows how to use the slider() method to change the
rotational frequency of a voltage pointer. The checkbox() method enables the activa-
tion of a power pointer that rotates with double frequency. The button() method can be
used to pause and restart the animation.
01 #20_event-processing.py
02 from vpython import *
03 scene.title="<h2>Rotating voltage and power pointer</h2>"
04 scene.width=scene.height=600
05 scene.background=color.white
06
07 runs = True
08 col=color.yellow
09
10 def start(b):
11 global runs
12 runs = not runs
389
7 3D Graphics and Animations Using VPython
Output
A snapshot of the resulting rotating pointers animation is shown in Figure 7.20.
390
7.5 Event Processing
Analysis
If the value of the global runs variable (lines 07 and 11) is True, the animation is executed
within the while loop (lines 43 to 48). If you want to pause the animation, you must click
the Pause button. Then, the label of the button changes to Start. In this case, the
button() method from line 34 calls the custom start(b) function from lines 10 to 14.
The command button is placed above the scene in the upper-left corner. The start(b)
function is called via bind=start. The parentheses of the function definition and the
function argument b must be omitted.
In line 37, the slider() method calls the custom bind=omega function from lines 16 and
17. The set values are stored in the sldF object and displayed in the txtA text field in line
38. In line 46, the change of the rotation frequency is performed using the w=sldF.value
assignment.
391
7 3D Graphics and Animations Using VPython
Exercise
Test the program with all settings.
Comment out line 11. Restart the program and click the Pause button. Analyze the error
message.
2. Convert this equation system into a first-order differential equation system using
the following substitutions:
and
392
7.6 Project Task: Animation of a Coupled Spring Pendulum
3. Set up the solution algorithm for the differential equation system using the Euler
method:
y1=y1+v1*dt
v1=v1-(c1+c2)/m1*y1*dt + c2/m1*y2*dt
y2=y2+v2*dt
v2=v2-c2/m2*(y2-y1)*dt
This algorithm must then be inserted within the animation loop. Listing 7.21 shows the
implementation.
01 #21_double_spring_pendulum.py
02 from vpython import *
03 y0=-5. #shift on the y-axis
04 b=10. #width of the ceiling
05 r=1.2 #radius of the mass
06 l=0.9*y0
07 c1=1. #spring constant
08 m1=1. #mass of the sphere
09 c2=1.
10 m2=1.
11 scene.width=600
12 scene.height=800
13 scene.center =vector(0,2*y0,0)
14 scene.background = color.white
15 box(pos=vector(0,b/40.,0),size=vector(b,b/20.,b/2.),
color=color.gray(0.8)) #ceiling
16 spring1 = helix(pos=vector(0,0,0),axis=vector(0,l,0),
17 color=color.yellow,radius=0.5*r,thickness=0.2,coils=10)
18 mass1 = sphere(pos=spring1.feder1.pos,radius=r, color=color.red)
19 spring2 = helix(pos=vector(0,l,0),axis=vector(0,l,0),
20 color=color.green,radius=0.5*r,thickness=0.2,coils=10)
21 mass2 = sphere(pos=vector(0,2*l,0),radius=r, color=color.blue)
22 y1=-0.6*l #deflection
23 y2=0
24 v1=v2=0 #initial velocity
25 lk=l-r
26 dt=0.02
27 while True:
28 rate(50)
29 y1=y1 + v1*dt
30 v1=v1-(c1+c2)/m1*y1*dt+c2/m1*y2*dt #-0.05*v1*dt
31 y2=y2 + v2*dt
32 v2=v2-c2/m2*(y2-y1)*dt #-0.05*v2*dt
393
7 3D Graphics and Animations Using VPython
33 spring1.axis=vector(0,y1+l,0)
34 mass1.pos =vector(0,y1+lk,0)
35 spring2.axis=vector(0,y1+y2+l,0)
36 spring2.pos.y =mass1.pos.y
37 mass2.pos =spring2.pos+vector(0,y1+y2+lk,0)
Output
A snapshot of the resulting animation of a coupled spring pendulum is shown in Figure
7.21.
Analysis
In lines 07 to 10, the masses and spring constants of the two coupled springs can be
changed.
In lines 16 to 21, the sphere() and helix() methods create the spring and mass objects.
In lines 29 to 32, the differential equation system is solved using the Euler method.
The axis property of spring1 and spring2 causes the two springs to deflect only in the
direction of the y-axis (lines 33 and 35).
In line 36, the spring2.pos.y property is assigned the current position of mass1.
Line 37 causes mass2 to be positioned at the end of spring2.
394
7.7 Project Task: Animation of Two Coupled Simple Pendulums
Exercise
Test the program with different masses.
Test the program with different spring constants.
In certain constellations, the spring-mass system reacts unstably.
Test the program with different damping values by removing the comments in lines 30
and 32.
and
and
395
7 3D Graphics and Animations Using VPython
you develop the algorithm according to the Euler method from the first-order differen-
tial equation system:
phi1=phi1+w1*dt
w1=w1-w02*phi1*dt+k*(phi2-phi1)*dt #-0.05*w1*dt
phi2=phi2+w2*dt
w2=w2-w02*phi1*dt-k*(phi2-phi1)*dt
This algorithm is inserted into the animation loop of the program. Listing 7.22 shows
the implementation.
01 #22_double_pendulum.py
02 from vpython import *
03 phi1=radians(-5.)
04 phi2=radians(5.)
05 b=12. #width of the ceiling
06 y0=-b/2.#shift on the y-axis
07 a=b/2. #distance between the pendulums
08 l=0.9*b #length of the pendulums
09 r=b/15. #radius of the spheres
10 m=10. #mass of the spheres
11 c=4.5 #spring constant
12 scene.width=600
13 scene.height=600
14 scene.center=vector(0,y0,0)
15 scene.range=0.8*b
16 scene.background = color.white
17 box(size=vector(b,b/20.,b/4.),color=color.gray(0.8)) #ceiling
18 rod1=cylinder(axis=vector(0,l,0),radius=0.05)
19 rod1.pos=vector(-a/2.,0,0)
20 rod2=cylinder(axis=vector(0,l,0),radius=0.05)
21 rod2.pos=vector(a/2.,0,0)
22 mass1 = sphere(radius=r,color=color.red)
23 mass2 = sphere(radius=r,color=color.blue)
24 spring=helix(axis=vector(a,0,0),radius=0.4)
25 spring.thickness=0.1
26 spring.coils=10
27 g=9.81 #gravitational acceleration
28 w02=g/l #pendulum frequency
29 k=c/m #spring frequency
30 w1=w2=0 #angular velocity
31 dt=0.02
32 while True:
33 rate(100)
34 phi1=phi1+w1*dt
396
7.7 Project Task: Animation of Two Coupled Simple Pendulums
35 w1=w1-w02*phi1*dt+k*(phi2-phi1)*dt #-0.05*w1*dt
36 phi2=phi2+w2*dt
37 w2=w2-w02*phi1*dt-k*(phi2-phi1)*dt #-0.05*w2*dt
38 x1= l*sin(phi1)
39 y1=-l*cos(phi1)
40 x2= l*sin(phi2)
41 y2=-l*cos(phi2)
42 rod1.axis=vector(x1,y1,0)
43 mass1.pos =vector(x1-a/2.,y1,0)
44 rod2.axis=vector(x2,y2,0)
45 mass2.pos =vector(x2+a/2.,y2,0)
46 spring.pos=mass1.pos+vector(r,0,0)
47 spring.axis.x=x2-x1+a-2*r
48 spring.axis.y=y2-y1
Output
A snapshot of the resulting animation of a simple coupled pendulum is shown in
Figure 7.22.
Analysis
In lines 03 and 04, you can change the deflection angles phi1 and phi2 of both pendu-
lums.
397
7 3D Graphics and Animations Using VPython
Lines 10 and 11 define the masses of the pendulums and the spring constant of the cou-
pling spring.
The differential equation system is solved in lines 34 to 37. The damping values are
commented out. They can be removed for testing purposes.
In lines 38 to 41, the current x-y coordinates are calculated from the deflection angles
phi1 and phi2.
In lines 42 to 48, the current positions are assigned to each pendulum and the coupling
spring.
Exercise
Test the program with different masses.
Test the program with different spring constants.
With certain settings, the double pendulum reacts unstably.
Test the program with different damping values by removing the comments in lines 35
and 37.
7.8 Tasks
1. Four spheres are supposed to touch each other in space. Write a VPython program to
represent this.
2. A graph consists of four nodes and has the shape of a parallelogram. All nodes are
connected to each other. The nodes are to be simulated as points. Write a VPython
program to represent this.
3. A cylinder oriented in the direction of the x-axis penetrates another cylinder ori-
ented in the direction of the y-axis. Write a VPython program to represent this.
4. Write a VPython program that creates an octahedron.
5. Write a VPython program that animates the motions of the Moon and the Earth
around the Sun. To simplify things, assume that the planets move along circular
orbits.
398
Chapter 8
Computing with Complex Numbers
This chapter describes how you can compute alternating current (AC)
networks, frequency responses and locus curves using complex calculus.
Complex numbers extend the real number range into the range of imaginary numbers.
A complex number consists of a real part a and an imaginary part b:
Complex numbers can be represented in the complex number plane, also called the
Gaussian number plane, as shown in Figure 8.1.
Im(z)
|z|
b
ɔ
a Re(z)
Complex numbers can be added, subtracted, divided, and multiplied directly in the
Python console. You can enter complex numbers together with the mathematical
operators into the console and then perform arithmetic operations by pressing
(Return). The integration of a module is not required for basic mathematical opera-
tions, such as the following:
>>> z1=1+2j
>>> z2=3+4j
>>> z1+z2
(4+6j)
>>> z1-z2
(-2-2j)
399
8 Computing with Complex Numbers
>>> z1/z2
(0.44+0.08j)
>>> z1*z2
(-5+10j)
You can also perform other mathematical operations defined for complex numbers,
for instance, exponentiation, root calculation, logarithmizing, and so on.
01 #01_operations.py
02 import numpy as np
03 n=3
04 z1=5-12j
05 z2=complex(3,4)
06 z2conj=np.conjugate(z2)
07 rez2=np.real(z2)
08 imz2=np.imag(z2)
09 absolutevalue=np.abs(z2)
10 angle=np.angle(z2)
11 s=z1+z2
12 d=z1-z2
13 p=z1*z2
14 q=z1/z2
15 pot=z2**n
16 w=np.sqrt(z2)
17 lg=np.log(z2)
18 hs=np.sinh(z2)
19 #Outputs
20 print("Complex number z1:",z1)
21 print("Complex number z2:",z2)
22 print("Conjugated z2:",z2conj)
23 print("Real part z2:",rez2)
24 print("Imaginary part z2:",imz2)
25 print("Absolute value of z2:",absolutevalue)
26 print("Angle of z2:",np.angle(z2,deg=True),"°")
27 print("Sum of z1+z2:",s)
28 print("Difference z1-z2:",d)
29 print("Product z1*z2:",p)
30 print("Quotient z1/z2:",q)
31 print("%1d.Power"%n,"of z2:",pot)
400
8.1 Mathematical Operations
Output
Analysis
Complex numbers can be defined with z1=5-12j (line 04) or with complex(3,4) (line 05).
The NumPy function np.conjugate(z2) generates the conjugate complex number
z2conj from the complex number z2 (line 06).
The real part and the imaginary part of the complex number z2 can be calculated using
the np.real(z2) and np.imag(z2) functions (lines 07 and 08).
To determine the absolute value and the angle of z2, you must apply the np.abs(z2) and
np.angle(z2) functions (lines 09 and 10).
Lines 11 to 14 perform some basic mathematical operations on z1 and z2.
Line 15 calculates the cube of z2, and line 16 calculates its root.
Lines 17 and 18 compute the natural logarithm and the sine hyperbolic of z2.
The output of the results for the mathematical operations on the complex numbers z1
and z2 occurs in lines 20 to 34.
401
8 Computing with Complex Numbers
Listing 8.2 compares whether the following calculations provide the same values:
z1=r*np.exp(1j*phi)
z2=r*np.cos(phi)+1j*r*np.sin(phi)
01 #02_euler.py
02 import numpy as np
03 r=10
04 phi=np.radians(30)
05 z1=r*np.exp(1j*phi)
06 z2=r*np.cos(phi)+1j*r*np.sin(phi)
07 #Outputs
08 print("z1:",z1)
09 print("z2:",z2)
10 print("Magnitude z1:",np.abs(z1))
11 print("Magnitude z2:",np.abs(z2))
12 print("Type of z1:",type(z1))
13 print("Type of z2:",type(z2))
Output
z1: (8.660254037844387+4.999999999999999j)
z2: (8.660254037844387+4.999999999999999j)
Magnitude z1: 10.0
Magnitude z2: 10.0
Type of z1: <class 'numpy.complex128'>
Type of z2: <class 'numpy.complex128'>
Analysis
Line 03 specifies the magnitude r of the complex numbers z1 and z2. In line 04, the
np.radians(30) function converts the angle of 30° into the radian. In line 05, the expo-
nential function np.exp(1j*phi) is passed the imaginary part of a complex number (the
angle!) as an argument. The real and imaginary parts of the angle, multiplied by the
amount r, are stored in the z1 variable. Note that the imaginary unit j must always be
preceded by a 1.
402
8.3 Calculating with Complex Resistors
In line 06, the term of the right hand side of Euler’s formula is assigned to the z2 vari-
able.
As expected, the outputs of lines 08 and 09 prove that the results computed in lines 05
and 06 match.
In lines 10 and 11, the NumPy function np.abs() calculates the amounts of the complex
numbers z1 and z2. Both results are identical.
The phase shift between voltage and current, which is usually always present, has not
yet been taken into account.
If you apply the rules of complex calculus, you can compute AC networks with sinusoi-
dal feed as if they were DC networks. In the literature, this method is also referred to as
the symbolic method.
Based on this method, total voltages and currents are calculated by adding up the real
and imaginary parts of the individual partial voltages and currents separately in each
case according to the rules of complex calculus. The multiplication and division of
complex resistors must also be performed according to the calculation rules of the
complex calculus. You can use the symbolic method only if all voltages and currents of
the network are sinusoidal.
When dividing the voltage pointer by the current pointer, the frequency is reduced.
Thus, pointers of complex resistors do not rotate. In any AC network, only ohmic, in-
ductive, and capacitive resistances occur. For a given angular frequency 𝜔, the follow-
ing applies with regard to the total resistance:
403
8 Computing with Complex Numbers
The inductive and the capacitive parts can be combined to an imaginary part X:
The real part R is formed by all ohmic resistances. If the inductive component predom-
inates, the imaginary part of the complex resistance has a positive sign. The current
lags behind the voltage. If the capacitive part predominates, the imaginary part of the
complex resistance has a negative sign. The current rushes ahead of the voltage. If the
imaginary part disappears, the network behaves like an ohmic resistor.
Complex currents
can be computed using Python by declaring the AC resistors as complex variables. For
a series circuit of an ohmic resistor of R = 10 Ω and an inductive reactance XL = 5 Ω, the
impedance is declared as a complex variable: Z=complex(10,5). Alternatively, you can
write Z=10+5j.
Based on the example T-circuit shown in Figure 8.2, I will demonstrate how the com-
plex calculus is carried out in real-life situations.
Z1 Z3
1
U1 Z2 Z4 U2
The three impedances of the T-circuit can be reduced to a single complex resistor
using the equivalent voltage source method. The original T-circuit then consists only of
404
8.3 Calculating with Complex Resistors
a series circuit of the equivalent resistance, which can be interpreted as the internal
resistance of the voltage source, and the complex load resistance .
In this way, the calculation of the current and the complex output voltage as well as the
complex output power is considerably simplified.
Listing 8.3 computes the output voltage, current, and output power of the T-circuit.
01 #03_t_circuit.py
02 import numpy as np
03 U1=230
04 Z1=1+2j
05 Z2=10-12j
06 Z3=1+2j
07 Z4=10-10j
08 Zi=Z1*Z2/(Z1+Z2)+Z3
09 I2=U1/(Zi+Z4)
10 U2=Z4*I2
11 P2=U2*I2
12 print("Internal resistance:",np.round(Zi,decimals=2), "ohms")
13 print("Output current: ",np.round(I2,decimals=2), "A")
14 print("Output voltage:",np.round(U2,decimals=2), "V")
15 print("Output power:",np.round(P2,decimals=2), "W")
Output
Analysis
The input voltage U1 is defined as int in line 03. In lines 04 to 07, the AC resistors Z1 to
Z4 are defined. The AC resistors have the complex data type because their imaginary
parts have been marked by a j.
405
8 Computing with Complex Numbers
C
R L
The complex resistance of the series resonant circuit consists of the real part R and the
two imaginary parts jω 𝐿 and 1/jω 𝐶 with the following equation:
406
8.4 Function Plots with Complex Magnitudes
To compute the complex frequency response of the current I(jω), the voltage must be
divided by the complex resistance, with the following equation:
Listing 8.4 computes the frequency response of the complex current flowing through
the series resonant circuit and plots its real and imaginary parts as a function of the
angular frequency.
01 #04_series_resonant_circuit.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 U=230 #int!
05 R=10 #int!
06 C=1e-6
07 L=1e-2
08 w=2.0*np.pi*np.linspace(0.01,3e3,1000)
09 Z=R+1j*w*L+1.0/(1j*w*C)
10 I=U/Z
11 fig, ax=plt.subplots(figsize=(8,6))
12 ax.plot(w,np.real(I),lw=2,color="red",label="Real part")
13 ax.plot(w,np.imag(I),lw=2,color="green",label="Imaginary part")
14 ax.set(xlabel="$\omega$ rad/s",ylabel=" I in A ")
15 ax.legend(loc="best")
16 ax.grid(True)
17 plt.show()
Listing 8.4 Frequency Response of the Current Waveform of a Series Resonant Circuit
Output
The resulting output is the current waveform of the real and the imaginary parts repre-
sented as a curve, as shown in Figure 8.4.
Analysis
Lines 04 to 07 provide the data. Line 08 creates a NumPy array for the angular fre-
quency w.
Line 09 contains the formula for the impedance of the series resonant circuit. In line 10,
the current I is calculated. A number of the int type is divided by a number of the com-
plex type. The result is a number of the complex type.
In lines 12 and 13, the Matplotlib method plot() prepares the function plot for display-
ing the real and imaginary parts. In line 17, the function plot is displayed on the screen
via plt.show().
407
8 Computing with Complex Numbers
L1
L2
408
8.4 Function Plots with Complex Magnitudes
From the circuit, you can directly read the complex resistance in the following way:
To manually draw the locus curve of this impedance, you must split the complex term
into its real and imaginary parts, which would be quite tedious.
Listing 8.5 carries out this task using the NumPy functions np.real(Z) and np.imag(Z).
01 #05_locus_curve.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 R=20
05 C=1e-6
06 L1=5e-2
07 L2=1e-2
08 wp=10e3 #angular frequency at point p
09 #complex resistance
10 def Z(w):
11 return 1j*w*L1+(R+1j*w*L2)*(1/(1j*w*C))/(R+1j*w*L2+1/(1j*w*C))
12
13 w=2.0*np.pi*np.linspace(0.01,3e3,500)
14 Zp=Z(wp)
15 fig, ax = plt.subplots(figsize=(8,6))
16 ax.plot(np.real(Z(w)),np.imag(Z(w)),lw=2)
17 ax.plot(np.real(Zp),np.imag(Zp),"o",c="red")
18 ax.set(title="Locus curve",xlabel="Real part in $\Omega$", ylabel=
"Imaginary part in $\Omega$")
19 ax.grid(True)
20 plt.show()
Output
Figure 8.6 shows the output in a function plot.
Analysis
In lines 04 to 07, you can define the values for the components. In line 08, you can
enter a selected location of the angular frequency wp, which will be shown in the locus
curve in connection with line 14 Zp=Z(wp) and line 17 as a red point in the function plot.
At the angular frequency of 10⋅103 s-1, the real part of the two-port network has a value
of 500Ω, and the imaginary part has a value of 400Ω. Thus, Z = 500Ω + j400Ω.
Line 10 defines the function for the impedance of the two-port network.
409
8 Computing with Complex Numbers
In line 13, a NumPy array for the range of the angular frequency w is created.
In line 16, the display of the locus curve is prepared and displayed on the screen in line
20 by using plt.show().
U1 U2
410
8.5 Project Task: Electric Power Transmission System
Four electrical constants, called primary line constants, completely determine the
transmission characteristics of a line. These constants include the resistance coating R',
the inductance coating L', the dielectric coating G', and the capacitance coating C'. The
magnitudes refer to 1km of line length.
The following data is given:
쐍 Line length: l = 400km
쐍 Line-to-line voltage at line end: U2 = 380kV
쐍 Active power at line end: P2 = 360MW
쐍 Power factor:
쐍 Primary line constants:
– R' = 31mΩ/km
– L' = 0.8mH/km
– G' = 0.02µS/km
– C' = 14.3nF/km
8.5.1 Task
a) The input voltage U1, the input current I1, the input power S1, and the efficiency of the
line should be computed using a Python program.
b) Using a T-equivalent circuit diagram of the line, the results from a) must also be
checked using a Python program.
Solution to A
1. Determine the phase voltage U2 and the line-to-line current I2 at the end of the line:
Set up the formulas for the propagation constant y and the characteristic impedance
. Based on the primary line constants, the propagation constant and the charac-
teristic impedance can be determined.
2. Set up the formulas for computing the input voltage and the input current .
411
8 Computing with Complex Numbers
At the sending end, the following applies to the voltage and current:
The line data is implemented as assignments, as is usually the case. For the line equa-
tions, we can use the NumPy functions np.sinh() and np.cosh().
Listing 8.6 computes the complex voltage , the complex current , the complex
input power , and the efficiency of the line.
01 #06_line1.py
02 import numpy as np
03 P2a=360e6 #power at line end
04 U2a=380e3 #line-to-line voltage at line end
05 l=400 #line length
06 f=50 #frequency
07 phi=0 #phase shift
08 R=31e-3 #resistance coating
09 L=0.8e-3 #inductance coating
10 G=0.02e-6 #dielectric coating
11 C=14.3e-9 #capacitance coating
12 #Computations
13 w=2*np.pi*f
14 U2=U2a/np.sqrt(3)
15 I2=P2a/(np.sqrt(3)*U2a*np.cos(phi))
16 Zw=np.sqrt((R+1j*w*L)/(G+1j*w*C))
17 g=np.sqrt((R+1j*w*L)*(G+1j*w*C))
18 U1= np.cosh(g*l)*U2 + Zw*np.sinh(g*l)*I2
19 I1=np.sinh(g*l)/Zw*U2 + np.cosh(g*l)*I2
20 S1=3*U1*np.conjugate(I1)/1e6
21 eta=1e-4*P2a/np.real(S1)
22 #Output
23 print("Characteristic impedance: %5.2f\u03A9, %5.1f°" \
24 %(np.abs(Zw),np.angle(Zw,deg=True)))
25 print("Input voltage: %5.2f V, %5.1f°"\
26 %(np.abs(U1),np.angle(U1,deg=True)))
27 print("Input current : %5.2f A, %5.1f°"\
28 %(np.abs(I1),np.angle(I1,deg=True)))
29 print("Input power: %5.0f MW %5.0f Mvar"\
30 %(np.real(S1),np.imag(S1)))
31 print("Efficiency \u03B7 = %5.0f percent" %(eta))
412
8.5 Project Task: Electric Power Transmission System
Output
Analysis
Lines 03 to 11 define the data of the line according to the specifications.
In lines 16 and 17, the characteristic impedance Zw and the propagation constant g are
calculated, in lines 18 and 19 the input voltage U1 and the input current I1 of the power
transmission system with the line equations.
Line 20 calculates the apparent power S1 at the input of the line. From the real part of
S1, the efficiency eta of the line can then be calculated in line 21.
The outputs are made in lines 23 through 31. The NumPy function np.angle(Zw,deg=
True) calculates the phase shift angle and converts the angle from radians to degrees.
The results exactly match the data from the technical literature.
Solution to B
You can determine the complex resistances and for the T-equivalent circuit of a
line, shown in Figure 8.2, using the characteristic impedance, the hyperbolic tangent,
and the propagation constant:
You can determine the load resistance from the output voltage and the output
current :
The voltage at the resistor is composed of the voltage drop at the resistor and
the voltage drop at the load resistor :
413
8 Computing with Complex Numbers
The index 1.0 represents the two nodes to which the resistor is connected. The volt-
age enables you to determine the current , which flows through the resistor :
You only need to enter these equations as Python source code into the editor of the
development environment. Listing 8.7 shows the implementation.
01 #07_line2.py
02 import numpy as np
03 P2a=360e6 #power at line end
04 U2a=380e3 #line-to-line voltage at line end
05 l=400 #line length
06 f=50 #frequency
07 phi=0 #phase shift
08 R=31e-3 #resistance coating
09 L=0.8e-3 #inductance coating
10 G=0.02e-6 #dielectric coating
11 C=14.3e-9 #capacitance coating
12 #Computations
13 w=2*np.pi*f
14 U2=U2a/np.sqrt(3)
15 I2=P2a/(np.sqrt(3)*U2a*np.cos(phi))
16 Zw=np.sqrt((R+1j*w*L)/(G+1j*w*C))
17 g=np.sqrt((R+1j*w*L)*(G+1j*w*C))
18 Z1=Zw*np.tanh(0.5*g*l)
19 Z2=Zw/np.sinh(g*l)
20 Z3=Z1
21 Z4=U2/I2
22 U10=(Z3+Z4)*I2
23 I10=U10/Z2
24 I1=I10+I2
25 U1=Z1*I1+U10
26 #Output
27 print("Z1= %3.2f \u03A9 %3.2fj \u03A9"\
28 %(np.real(Z1),np.imag(Z1)))
29 print("Z2= %3.2f \u03A9 %3.2fj \u03A9"\
30 %(np.real(Z2),np.imag(Z2)))
31 print("Z3= %3.2f \u03A9 %3.2fj \u03A9"\
414
8.6 Tasks
32 %(np.real(Z3),np.imag(Z3)))
33 print("Output current %5.3f A" %(I2))
34 print("Input voltage: %5.2f V, %5.1f°"\
35 %(np.abs(U1),np.angle(U1,deg=True)))
36 print("Input current : %5.2f A, %5.1f°"\
37 %(np.abs(I1),np.angle(I1,deg=True)))
Output
Analysis
The values for the longitudinal members Z1 and Z3, as well as the transverse member
Z2, are calculated in lines 18 and 19.
Lines 21 to 25 calculate step by step the input voltage U1 and the input current I1, start-
ing from the end of the equivalent circuit.
The outputs are made in lines 27 through 37. The results from lines 34 and 36 are consis-
tent with the results from Listing 8.6.
8.6 Tasks
1. Calculate the square root of using the Python console.
2. Write a program to convert complex resistors connected as a star into a delta circuit.
3. For a parallel resonant circuit, the frequency response must be represented
as real and imaginary parts. Write a program that solves this task.
4. Write a program for plotting the locus curve of a characteristic impedance (see Task
5 for data).
5. Let’s say we have a telephone cable with the primary line constants:
– R' = 60Ω/km
– L' = 0.6mH/km
– G' = 1µS/km
– C' = 50nF/km
415
8 Computing with Complex Numbers
Now, calculate the voltage U2 and the current I2 at the end of the line at a frequency of
= 1 kHz. The input voltage is U1 = 10V, and the input current has a value of I1 = 10 mA.
For these line equations, the following applies:
416
Chapter 9
Statistical Computations
In this chapter, you’ll learn how to calculate and analyze important sta-
tistical characteristics from normally distributed random numbers using
NumPy and SciPy. I’ll show you how to use a simulation program to
graphically represent the means and standard deviations of samples in
a two-track quality control chart.
417
9 Statistical Computations
Skew skew(a)
Table 9.1 The Statistical Methods of Python, NumPy, and SciPy (Cont.)
The data to be analyzed is stored in an array a. Above all, the SciPy submodule stats,
with its overwhelming number of statistical functions, is a powerful tool for extensive
statistical analyses. In this chapter, I’ll focus on the treatment of important statistical
mean values, the standard deviation, and the regression analysis of normally distrib-
uted physical measurements. The dimensions of a workpiece (such as the gear shaft
shown in Figure 9.1) are supposed to represent these measurement values. Since no real
measurement values are available, they are generated using a random number genera-
tor, stored in a file, and read from the file for statistical analysis. The results of the sta-
tistical analysis are then visualized by histograms and quality control charts.
In mechanical engineering, the process quality of a manufacturing process must be
permanently monitored to ensure the quality required by the customer. For this pur-
pose, a random sample of usually five workpieces is taken every hour from running
production. Software then analyzes the samples using statistical tools. The process
quality can be thus assessed based on the profile of the mean values and standard devi-
ations from the individual samples.
Figure 9.1 shows a technical drawing of a gear shaft with the required dimensions. Basi-
cally, all dimensions given in the drawing can be the subject of statistical analysis. How-
ever, we assume that the measurement values are approximately normally distributed,
which in real life is almost always the case. Each technical drawing provides all relevant
values for statistical analysis, such as the following values:
쐍 Length and width dimensions
쐍 Diameter of the shaft
쐍 Roughness depths
쐍 Hardness according to the Rockwell scale (HRC)
418
9.1 Generating, Saving, and Reading Measurement Values
For other production processes, the following values may also be relevant:
쐍 Filling quantities of certain foods or substances
쐍 Coating thicknesses
쐍 Resistance values of ohmic resistors
쐍 Capacitance values of capacitors
쐍 Coil inductance values
The basic idea behind statistical process control (SPC) is to infer the process quality of
the entire manufacturing process based on the statistical analysis of a relatively small
number (sample) of workpieces. Corrective action can thus be taken in the process,
before further rejects are produced.
values=np.random.normal(setpoint,standarddeviation,size=n)
01 #01_generate.py
02 import numpy as np
03 n=10
04 setpoint=50
05 s=1
06 values=np.random.normal(setpoint,s,size=n)
07 rvalues=np.around(values,decimals=2)
08 print("Normally distributed values:")
09 print(values)
419
9 Statistical Computations
10 print("Rounded values:")
11 print(rvalues)
12 print("Type of values:",type(values))
Output
Analysis
Line 02 imports the numpy module. As usual, the np identifier is assigned as an alias. Line
03 specifies the number of random numbers to be generated. We arbitrarily specified
the number 50 as the setpoint of a measured variable (line 04). The dispersion measure
of the standard deviation s has a reference value of 1 (line 05), which has also been spec-
ified arbitrarily. Line 06 generates ten normally distributed random numbers using the
np.random.normal(setvalue,s,size=10) NumPy function and assigns these numbers to
the values variable. In line 07, the decimal numbers are rounded to 2-digit precision
using the np.arrond(values,decimals=2) NumPy function.
Lines 09 and 11 output the random numbers. Line 12 specifies the type of variable val-
ues with <class 'numpy.ndarray'>. The ndarray data structure is typical of NumPy: In
this case, this array object represents a one-dimensional, homogeneous array with ele-
ments that have fixed sizes.
01 #02_reshape.py
02 import numpy as np
03 rows=5
04 columns=10
05 n=columns*rows
06 setpoint=10
07 s=1
420
9.1 Generating, Saving, and Reading Measurement Values
08 values=np.random.normal(setpoint,s,size=n)
09 rvalues=np.around(values,decimals=2)
10 svalues=np.sort(rvalues)
11 table=np.reshape(svalues,(rows,columns),order='F')
12 print("Measurement values:\n",svalues)
13 print("Table:\n", table)
14 print("Minimum value:", np.amin(rvalues))
15 print("Maximum value:", np.amax(rvalues))
Output
Measurement values:
[8.23 8.5 8.73 8.83 8.93 8.98 9.13 9.2 9.3 9.52 9.52 9.6 9.63 9.71
9.72 9.74 9.82 9.86 9.87 9.91 9.91 9.92 9.94 9.98 10.03 10.04 10.05
10.05 10.2 10.36 10.46 10.55 10.57 10.72 10.74 10.75 10.76 10.78 10.83 10.86
10.96 11.01 11.11 11.18 11.21 11.26 11.35 11.49 12.27 12.49]
Table:
[[8.23 8.98 9.52 9.74 9.91 10.04 10.46 10.75 10.96 11.26]
[ 8.5 9.13 9.6 9.82 9.92 10.05 10.55 10.76 11.01 11.35]
[ 8.73 9.2 9.63 9.86 9.94 10.05 10.57 10.78 11.11 11.49]
[ 8.83 9.3 9.71 9.87 9.98 10.2 10.72 10.83 11.18 12.27]
[ 8.93 9.52 9.72 9.91 10.03 10.36 10.74 10.86 11.21 12.49]]
Minimum value: 8.23
Maximum value: 12.49
Analysis
The program generates 50 normally distributed random numbers (line 08). Line 10
sorts the numbers to improve the verification of the reshaping. In real-life applications,
for example, in process monitoring via quality control charts, the real measurement
data must not be sorted, of course. The statistical analysis of the individual table col-
umns would provide a completely false picture of the manufacturing process.
In line 11, the reshape() NumPy function converts the one-dimensional array into a
table with five rows and ten columns. The svalues array is passed as the first parameter.
The second parameter contains the number of rows and columns as tuples. The third
parameter (order='F') determines how the table columns are formed from a section of
a row. “F” means that the numbers are read or written in the Fortran-like index order,
with the first index changing first and the last index changing last. The first column is
formed from the first five numbers in the series. The next five numbers are used to
form the second column of the measurement series, and so on.
In line 12, the sorted numbers are output as a one-dimensional array. Line 13 outputs
these numbers as a table with five rows and ten columns.
421
9 Statistical Computations
01 #03_write.py
02 import numpy as np
03 n=50
04 setpoint=50
05 s=1
06 values=np.random.normal(setpoint,s,size=n)
07 rvalues=np.around(values,decimals=2)
08 np.savetxt("data.txt", values,fmt="%4.2f")
09 print("Normally distributed values:")
10 print(rvalues)
11 print("Type of values:",type(values))
Output
Analysis
In line 08, the random numbers generated in line 06 are saved to the hard disk using
the np.savetxt("data.txt",values,fmt="%4.2f") NumPy function. The first parameter
is the freely selectable file name with the txt file extension. The numbers are therefore
stored in text format. They can be displayed and also edited with any text editor. The
second parameter is the values variable, in which the 50 elements of the array (filled
with random numbers) are stored. The third parameter specifies that the numbers
should be stored with two decimal places. For control purposes, line 10 outputs the
rounded values.
422
9.2 Frequency Distribution
01 #04_read.py
02 import numpy as np
03 values = np.loadtxt("data.txt")
04 n=len(values)
05 print("Loaded values:")
06 print(values)
07 print("Number of values:",n)
08 print("Type of values:",type(values))
Output
Loaded values:
[51.33 49.76 50.17 49.4 49.4 47.78 49.9 47.99 50.35 48.83 50.22 48.79 48.7
50.48 50.65 49.87 49.7 50.42 49.34 49.51 50.17 51.3 50.17 50.14 49.44 49.3
48.27 49.99 50.4 49.13 50.03 51.08 50.03 51.72 49.42 49.9 49.43 49.03 49.91
50.43 50.35 49.48 49.25 50.48 49.17 49.68 52.23 50.44 49.73 50.9]
Number of values: 50
Type of values: <class 'numpy.ndarray'>
Analysis
In line 03, the random numbers are read from the data.txt file and stored in the values
variable. The np.loadtxt("data.txt") function expects only one parameter, namely,
the name of the file from which the data is to be read. For control purposes, line 06 out-
puts the simulated measurement series. A comparison with the output from Listing 9.4
shows that the values from both programs match as expected.
423
9 Statistical Computations
H,I=np.histogram(array, bins=k)
Listing 9.5 shows how to implement a frequency table by using the NumPy module.
01 #05_tally_sheet.py
02 import numpy as np
03 values=np.loadtxt("data.txt")
04 n=len(values)
05 k=int(np.sqrt(n)+0.5)
06 minimum=np.amin(values)
07 maximum=np.amax(values)
08 R=round(maximum-minimum,2)
09 w=round(R/k,2)
10 svalues=np.sort(values)
11 H,I=np.histogram(values, bins=k)
12 h=100*H/n
13 print("Measurement values:\n",svalues)
14 print("Minimum value:", minimum)
15 print("Maximum value:", maximum)
16 print("Span:",R)
17 print("Number of classes:", k)
18 print("Class interval:", w)
19 print("Ranges:", np.around(I,decimals=2))
20 print("Absolute frequency:", H)
21 print("Relative frequency:", h,"%")
22 print("Number of measurement values:", sum(H))
424
9.2 Frequency Distribution
Output
Measurement values:
[47.78 47.99 48.27 48.7 48.79 48.83 49.03 49.13 49.17 49.25 49.3 49.34 49.4
49.4 49.42 49.43 49.44 49.48 49.51 49.68 49.7 49.73 49.76 49.87 49.9 49.9
49.91 49.99 50.03 50.03 50.14 50.17 50.17 50.17 50.22 50.35 50.35 50.4 50.42
50.43 50.44 50.48 50.48 50.65 50.9 51.08 51.3 51.33 51.72 52.23]
Minimum value: 47.78
Maximum value: 52.23
Span: 4.45
Number of classes: 7
Class interval: 0.64
Ranges: [47.78 48.42 49.05 49.69 50.32 50.96 51.59 52.23]
Absolute frequency: [ 3 4 13 15 10 3 2]
Relative frequency: [ 6. 8. 26. 30. 20. 6. 4.] %
Number of measurement values: 50
Analysis
The program loads the measurement values from the data.txt file (line 03), determines
the length of the values array (line 04), and computes the number of classes k (line 05).
The minimum and maximum are determined using NumPy functions np.amin(values)
(line 06) and np.amax(values) (line 07), respectively. From the span R (line 08) and the
number of classes k, the program computes the class interval w (line 09).
In line 11, the np.histogramm(param1, param2) NumPy function computes the absolute
frequency H and the intervals I of the individual classes and assigns them to the H,I
variables as tuples. This function expects two parameters: The values array is passed as
the first parameter; the second parameter bins=k expects the number of classes.
Line 20 provides the absolute frequency, and line 21 gives the relative frequency. For
control purposes, the sum of the absolute frequencies is output in line 22.
9.2.2 Histograms
Histograms visualize frequency tables as bar charts. The class interval corresponds to
the width of a rectangle and the absolute frequency corresponds to the height of a rect-
angle. The number of rectangles corresponds to the number of classes. To display his-
tograms, the matplotlib.pyplot module must first be imported. The hist(param1,
param2,param3,param4,...) method displays a histogram as a bar chart. Listing 9.6
shows how to visualize the frequency table created earlier in Listing 9.5.
01 #06_histogram.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 values = np.loadtxt("data.txt")
425
9 Statistical Computations
05 n=len(values)
06 k=int(np.sqrt(n)+0.5)
07 fig, ax=plt.subplots()
08 ax.hist(values,bins=k,edgecolor="b",color="w")
09 ax.set(xlabel="Measurement values",ylabel="Absolute frequency")
10 plt.show()
Output
The resulting histogram output from running the program is shown in Figure 9.2.
Analysis
For the display of the histogram via the hist(values,bins=k,edgecolor="b",color="w")
method, only four parameters are used (line 08). The first parameter is the values array.
The second parameter (bins=k) specifies the number of classes. The third parameter
(edgecolor="b") sets the border color of the bars to blue. The fourth parameter (color=
"w") specifies that white is the background color of the rectangles.
Line 09 defines the axis label. Line 10 is necessary so that the graphic is also output on
the screen.
426
9.3 Location Parameters
Both Python and the NumPy module have statistical functions that compute the arith-
metic mean. Listing 9.7 calculates means using a custom function named mean(), the
Python function statistics.mean(), and the NumPy function numpy.mean(). This pro-
gram also compares the individual runtimes of these functions to find the most effec-
tive method.
01 #07_mean_value.py
02 import numpy as np
03 import statistics as st
04 import time as t
05 n=100000
06 setpoint=50
07 s=1
08
09 def mean(values):
10 n=len(values)
11 sum=0
12 for i in range(n):
13 sum=sum+values[i]
14 return sum/n
15
16 #values=[1,2,3,4,5,6]
17 values=np.random.normal(setpoint,s,size=n)
18 t1=t.time()
19 m1=mean(values)
20 t2=t.time()
21 m2=st.mean(values)
22 t3=t.time()
23 m3=np.mean(values)
24 t4=t.time()
25 print("\t\tArithmetic mean","Time",sep=3*("\t"))
26 print("Custom version:",m1,t2-t1,sep=("\t"))
27 print("Python version:",m2,t3-t2,sep=("\t"))
28 print("NumPy version:",m3,t4-t3,sep=("\t"))
427
9 Statistical Computations
Output
Analysis
The Python modules statistics and time are imported in lines 03 and 04. Line 05 spec-
ifies a particularly high number of n=100000 random numbers so that meaningful run-
times can also be determined.
The mean() function in lines 09 to 14 computes the sum of all numbers and returns the
arithmetic mean sum/n.
The time measurement is performed for each function call according to the same
schema: Before a function is called, the system time t1 is determined. After returning
the value for the arithmetic mean, the current system time is stored in the t2 variable.
The runtime is then determined from the difference of t2-t1.
An interesting thing to note is that the custom mean() function is about 7.5 times faster
than the Python function st.mean(). However, the custom function also provides a
somewhat less accurate value. The NumPy function np.mean() is about 1,100 times
faster than the Python function st.mean(). Thus, for future programs, we recommend
using the NumPy function.
To calculate the geometric mean, all the individual measurement values of a sample
must be multiplied with each other. Then, the nth root is taken from this product:
428
9.3 Location Parameters
Listing 9.8 calculates all four location values using NumPy and SciPy functions. The
measurement values are read from the data.txt file.
01 #08_location_parameters.py
02 import numpy as np
03 import scipy.stats as sta
04 values=np.loadtxt("data.txt")
05 mw=np.mean(values)
06 md=np.median(values)
07 mode=sta.mode(values)
08 hm=sta.hmean(values)
09 gm=sta.gmean(values)
10 print(np.sort(values))
11 print("Mode:",mode)
12 print("Arithmetic mean:",mw)
13 print("Median: ",md)
14 print("Harmonic mean: ",hm)
15 print("Geometric mean: ",gm)
Output
[47.78 47.99 48.27 48.7 48.79 48.83 49.03 49.13 49.17 49.25 49.3 49.34 49.4
49.4 49.42 49.43 49.44 49.48 49.51 49.68 49.7 49.73 49.76 49.87 49.9 49.9
49.91 49.99 50.03 50.03 50.14 50.17 50.17 50.17 50.22 50.35 50.35 50.4 50.42
50.43 50.44 50.48 50.48 50.65 50.9 51.08 51.3 51.33 51.72 52.23]
Mode: ModeResult(mode=array([50.17]), count=array([3]))
Arithmetic mean: 49.8718
Median: 49.9
Harmonic mean: 49.85678962270494
Geometric mean: 49.864292035952936
Analysis
Line 03 imports the statistics module scipy.stats from SciPy. You can use the sta alias
to access the statistics functions of this module. The arithmetic mean and the median
are calculated using the NumPy functions np.mean() and np.median() in lines 05 and 06.
The mode, harmonic mean, and geometric mean are computed using the SciPy func-
tions sta.mode(), sta.hmean(), and sta.gmean() in lines 07 to 09. The return value of the
sta.mode() function contains a tuple of two values. The first value indicates the mode;
the second value indicates how often the mode occurs (line 11).
429
9 Statistical Computations
While this measure is easy to calculate, for more detailed statistical studies, this
approach is not recommended because it is quite sensitive to outliers.
More precisely, the standard deviation describes the dispersion of the measurement
values around a mean value. This value is calculated by taking the difference between
each individual measurement value and the arithmetic mean of the measurement
series. This difference is squared to give negative differences a positive sign and then
totaled. The totaled squares of the differences must then be divided by n − 1. The result
obtained in this way is referred to as variance. If you calculate the square root of the
variance, you obtain the standard deviation, as shown in the following equation:
Listing 9.9 computes the standard deviation from 100,000 normally distributed ran-
dom numbers using the three functions: a custom function named stdaw(), Python’s
statistics.std() function, and the NumPy function numpy.std(). To determine the
most powerful function, the runtimes of these three functions are compared with each
other.
01 #09_stdaw.py
02 import numpy as np
03 import statistics as st
04 import time as t
05 n=100000
06 setpoint=100
07 s=2
08 def stdaw(values):
09 n=len(values)
10 sum=0
11 for i in range(n):
12 sum=sum+values[i]
13 mean=sum/n
14 sum_rq=0
430
9.4 Dispersion Parameters
15 for i in range(n):
16 sum_rq=sum_rq+(values[i]-mean)**2
17 v=sum_rq/(n-1) #variance
18 return np.sqrt(v)
19
20 #values=[1,2,3,4,5,6]
21 values=np.random.normal(setpoint,s,size=n)
22 t1=t.time()
23 s1=stdaw(values)
24 t2=t.time()
25 s2=st.stdev(values)
26 t3=t.time()
27 s3=np.std(values,ddof=1)
28 t4=t.time()
29 print("\t\t Standard deviation","Time",sep=2*("\t"))
30 print("Custom version :",s1,t2-t1)
31 print("Python version :",s2,t3-t2)
32 print("NumPy version :",s3,t4-t3)
Output
Analysis
The custom function staw() consistently implements the algorithm (lines 08 to 18). In
line 17, the variance is calculated. To ensure that the staw() function also returns the
standard deviation, the square root of the variance must be determined, which hap-
pens in line 18.
The commented-out line 20 is supposed to provide test values. All three functions out-
put the expected value of s = 1.8708.
The additional ddof=1 parameter of the std() NumPy function in line 27 ensures that
the total of the squares from the differences (values[i]-mean) is not divided by n, as
given by default, but by n – 1. The ddof acronym stands for Delta Degrees of Freedom.
Surprisingly, the custom staw() function is about six times faster than Python’s
stdev() function. As expected, the NumPy version (np.std()) is much faster than the
other two versions, about 800 times faster than the Python version.
431
9 Statistical Computations
The machine capability index Cm describes only the influence of the dispersion of mea-
surement values on the manufacturing process. The distance of the sample mean from
the tolerance center is not taken into account. This criterion, that is, the distance of the
mean value to the tolerance center, is described by the machine capability characteris-
tic value Cmk in the following equation:
The critical distance is the smallest distance of the arithmetic mean value of the
measurement series to the tolerance limit. The tolerance limit can be near the lower
limit or near the upper limit of the measured values, depending on the location of .
Based on this requirement, it always follows that the machine capability index Cmk
must be smaller than the machine capability index Cm.
Listing 9.10 computes the machine capability index and the machine capability charac-
teristic value from a sample of 50 measurement values. The simulated measurement
values are again read from the data.txt file using the np.loadtext() NumPy function.
The tolerance limits are specified, and the arithmetic mean and the standard deviation
are calculated by the program from the measurement series of the sample. By varying
the tolerance limits, you can simulate whether the machine meets the process quality
requirements.
01 #10_mcapability.py
02 import numpy as np
03 setpoint=50
04 To=5
05 Tu=-5
06 T=To-Tu
07 values = np.loadtxt("data.txt")
432
9.4 Dispersion Parameters
08 m=np.mean(values)
09 s=np.std(values,ddof=1)
10 Cm=T/(6*s)
11 UCL=setpoint+To #Upper control limit
12 LCL=setpoint+Tu #Lower control limit
13 delta_o=UCL-m
14 delta_u=m-LCL
15 if delta_o > delta_u:
16 delta_k=delta_u
17 else:
18 delta_k=delta_o
19 Cmk=delta_k/(3*s)
20 print("Mean:",m)
21 print("Standard deviation:",s)
22 print("Machine capability index: ",Cm)
23 print("Machine capability characteristic value:",Cmk)
Output
Mean: 49.8718
Standard deviation: 0.8745510568402549
Machine capability index: 1.905739697677936
Machine capability characteristic value: 1.8568765318294738
Analysis
The machine capability index and the machine capability characteristic value are
greater than 1.67. The machine is therefore able to maintain the required process qual-
ity. However, this check passes due to the high tolerances of ± 10% (lines 04 and 05). If
the tolerances were reduced, then it would become apparent that the machine cannot
maintain the required process quality.
In line 10, the machine capability index Cm is calculated from the tolerance (line 06) and
the standard deviation (line 09). The program calculates the machine capability param-
eter Cmk in lines 11 to 19. For this purpose, the upper limit value UCL (line 11) and the lower
limit value LCL (line 12) must be determined. The delta_o (line 13) and delta_u (line 14)
deviations are needed to compute the smallest distance from the arithmetic mean
delta_k. This computation step is performed in lines 15 to 18 by an if-else query. The
machine capability parameter is then calculated in line 19.
The output of the machine parameters is performed in lines 22 and 23. The arithmetic
mean and the standard deviation are output in lines 20 and 21 to show the relationship
between the statistical parameters and machine parameters: The greater the standard
433
9 Statistical Computations
deviation and the greater the distance of the setpoint from the arithmetic mean of the
measurement values, the worse the machine capability will be rated.
The parameter is referred to as the expected value. This value coincides with the arith-
metic mean, the mode, and the median when the number of measurement values of a
normally distributed measurement series approaches infinity. The parameter is
referred to as the variance. The square root of the variance corresponds to the standard
deviation of a measurement series with a very large number of measurement values.
01 #11_gauss.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 #density function
05 def g(x,sigma,my):
06 y=np.exp(-0.5*(x-my)**2/sigma**2)/(sigma*np.sqrt(2*np.pi))
07 return y
08
09 s=1
10 m=0
11 x = np.arange(m-3*s, m+3*s, 0.01);
12 y = g(x,s,m)
13 fig, ax=plt.subplots()
14 ax.plot(x, y)
15 ax.plot(-1, g(-s,s,m),"ro")
16 ax.plot( 1, g( s,s,m),"ro")
17 ax.set(xlabel="x",ylabel="g(x)")
18 plt.show()
434
9.5 Normal Distribution
Output
Figure 9.3 shows the output density of a standard normal distribution as a curve.
Analysis
In lines 05 to 07, the function of the normal distribution is defined. The g(x,sigma,my)
function must be passed the standard deviation and the arithmetic mean as argu-
ments.
Lines 09 and 10 contain the standard deviation s=1 and the mean m=0. These parameters
specify that a normalized density function is represented.
Line 11 creates an array with the initial value m-3*s and the final value m+3*s. The third
parameter of the arange() NumPy function sets the increment to 0.01.
In line 12, the density function is called with the x, s, and m parameters. The function val-
ues are assigned to the y variable. This variable is also an array in which all function val-
ues of the g(x,s,m) function have been stored.
435
9 Statistical Computations
Listing 9.12 calculates the probabilities within the bounds , , and . The
and parameters specify that the program represents a standard normal distribu-
tion.
01 #12_probability.py
02 import numpy as np
03 from scipy.integrate import quad
04 def f(x,sigma,my):
05 y=np.exp(-0.5*(x-my)**2/sigma**2)/(sigma*np.sqrt(2*np.pi))
06 return y
07
08 s=1
09 m=0
10 w1=quad(f, -s, s,args=(s,m))
11 w2=quad(f,-2*s,2*s,args=(s,m))
12 w3=quad(f,-3*s,3*s,args=(s,m))
13 wp1,wp2,wp3=100*w1[0],100*w2[0],100*w3[0]
14 print("Expected value between -\u03C3 and +\u03C3: %2.2f%%"%wp1)
15 print("Expected value between -2\u03C3 and +2\u03C3: %2.2f%%"%wp2)
16 print("Expected value between -3\u03C3 and +3\u03C3: %2.2f%%"%wp3)
Output
Analysis
The statement in line 03 imports the scipy module with the integrate package and the
quad() function. This function, which is called in lines 10 to 12, expects three parame-
ters. The name of the function is passed as the first parameter without parentheses. The
second and third parameters set the lower and upper integration limits. The third
parameter consists of a tuple (args=(s,m)) with the function parameters of the normal
distribution, namely, standard deviation s and mean m.
The quad() SciPy function returns two values as a tuple. The first value is the computed
area of the numerical integration. The second return value specifies an error estimate.
Since only the first return value is of interest in our case, only this values w1[0], w2[0],
and w3[0] are assigned to the wp1, wp2, and wp3 variables, respectively, in line 13. The
computed areas are multiplied by a factor of 100 so that the output of the probabilities
is represented in percentages.
436
9.6 Skew
The output in lines 14 to 16 uses the Unicode character U+03C3 for the formula character
of the standard deviation. The 99.73% expected value between and is of partic-
ular interest: This range specifies the limits of process quality. The determined mea-
surement values should fall within in this interval; conversely, this value also means
that only 0.27% scrap is allowed.
9.6 Skew
For SPC, you must know whether a measurement series is normally distributed, more
or less. This criterion can be checked with the statistical measure of skewness. Skew is a
statistical measure between −1 and +1 that describes whether and how strongly a fre-
quency distribution is skewed to the right or to the left. If the frequency distribution is
skewed to the right, it is called right skewed. The dimension number S is then negative.
If the frequency distribution is skewed to the left, it is called left skewed. The dimension
number S is then positive. The smaller S is, the more symmetrical the frequency distri-
bution is. For the normal distribution, S = 0. Karl Pearson (1895–1980) gave a simple rule
of thumb to describe the deviation of a frequency distribution from symmetry:
If the median turns out to be smaller than the mean , then S is positive, and the dis-
tribution is left skewed. If the median is larger than the mean , then S is negative, and
the distribution is right skewed.
For the left-skewed distribution, the following often holds true: mode < median < mean.
The reverse is true for the right-skewed distribution: mean < median < mode.
However, these rules of thumb do not always determine the correct value of the skew.
The stats package from the scipy module calculates the correct value of skew using the
stats.skew(array) function.
Listing 9.13 computes the approximate value and the exact value of skewness from 50
random numbers.
01 #13_skew.py
02 import numpy as np
03 from scipy import stats
04 n=50
05 setpoint=50
06 s=2
07 values=np.random.normal(setpoint,s,size=n)
08 rvalues=np.around(values,decimals=2)
09 mode=stats.mode(rvalues, axis=None)
10 mw=np.mean(rvalues)
437
9 Statistical Computations
11 md=np.median(rvalues)
12 stabw=np.std(rvalues,ddof=1)
13 S1=(mw-md)/stabw
14 S2=stats.skew(rvalues)
15 print(np.sort(rvalues))
16 print("Mode: ",mode)
17 print("Mean: ",mw)
18 print("Median: ",md)
19 print("Standard deviation:",stabw)
20 print("Skew (approximation):",S1)
21 print("Skew (exact): ",S2)
22 if S2<0:
23 print("right-skewed")
24 else:
25 print("left-skewed")
Output
[45.41 46.2 46.39 46.5 47.14 47.24 47.25 47.38 47.59 47.73 48.04 48.21 48.3
48.33 48.56 48.76 49.31 49.4 49.45 49.73 49.75 49.78 49.89 49.91 49.93 49.95
49.98 50.05 50.15 50.17 50.34 50.42 50.43 50.74 50.89 50.94 51.02 51.09 51.14
51.32 51.58 51.92 51.97 52.17 52.19 52.37 52.64 53.34 53.63 54.01]
Mode: ModeResult(mode=array([45.41]), count=array([1]))
Mean: 49.81260000000001
Median: 49.94
Standard deviation: 2.0127043539294416
Skew (approximation): -0.06329792040806283
Skew (exact): -0.09607672698739309
right-skewed
Analysis
Note that different values for skewness are output after each program start because the
program also generates different random numbers in each case. In line 13, the S1 skew
is calculated according to the simple Pearson formula. The exact calculation of the S2
skew is performed in line 14 using SciPy function stats.skew(rvalues). By starting the
program multiple times, you can determine that the calculation according to Pearson
does not always match the result of the stats.skew() SciPy function. In lines 22 to 25,
the sign of the S2 skew is analyzed. If S2<0, right-skewed is output, and if S2>0, left-
skewed is output.
438
9.7 Regression Analysis
T 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
X 46 53 29 61 36 39 47 49 52 38 55 32 57 54 44
Y 12 15 7 17 10 11 11 12 14 9 16 8 18 14 12
Table 9.2 Measurement Values for the Relative Humidity X and Moisture Content of
Material Y in Percent
In the simplest case, a linear relationship exists between the X and Y values, which can
be described by a linear function with the slope m and the intercept a of the function
line with the y axis:
y = mx +a
The m and a parameters are also referred to as regression parameters. The strength of
the correlation between X and Y of the two measurement series is determined by the
correlation coefficient r.
The slope of the regression line is the quotient of the covariance and the square of the
standard deviation of the x-values:
439
9 Statistical Computations
The intercept of the regression line is computed from the difference of the mean
value of all y-values and the product of the slope m with the mean value of all x-
values:
All regression parameters can be computed directly using the following SciPy function:
m,a,r,p,e = stats.linregress(X,Y)
The return values p and e of the tuple are not needed for computing the regression
parameters.
Listing 9.14 calculates the slope m, the y-axis intercept a of the regression line and the
correlation coefficient r for the measurement values listed in Table 9.2. To show the dif-
ferent options of Python, the parameters are calculated and compared by using the
corresponding NumPy and SciPy functions.
01 #14_correlation.py
02 import numpy as np
03 from scipy import stats
04 X=np.array([46,53,29,61,36,39,47,49,52,38,55,32,57,54,44])
05 Y=np.array([12,15,7,17,10,11,11,12,14,9,16,8,18,14,12])
06 xm=np.mean(X)
07 ym=np.mean(Y)
08 sx=np.std(X,ddof=1)
09 sy=np.std(Y,ddof=1)
10 sxy=np.cov(X,Y)
11 r1=sxy/(sx*sy)
12 r2=np.corrcoef(X,Y)
13 m1=sxy[0,1]/sx**2
14 m2=r2[0,1]*sy/sx
15 a1=ym-m2*xm
16 m3, a2, r3, p, e = stats.linregress(X,Y)
17 print("NumPy1 slope:",m1)
18 print("NumPy2 slope:",m2)
19 print("SciPy slope:",m3)
20 print("Intersection with the y-axis:",a1)
21 print("Intersection with the y-axis:",a2)
22 print("Def. correlation coefficient:",r1[0,1])
23 print("NumPy correlation coefficient:",r2[0,1])
440
9.7 Regression Analysis
Output
Analysis
Lines 04 and 05 contain the measurement values for the relative humidity X and the
moisture content of the material Y. The program computes the regression parameters
from these measurement values and uses the correlation coefficient to check whether
a correlation exists between the influencing variable X and the outcome variable Y and
to determine how strong this correlation is.
Line 10 computes the covariance sxy using the np.cov(X,Y) NumPy function. This func-
tion returns a 2 × 2 matrix. The value for sxy is either in the first row, second column, of
the matrix or in the second row, first column, of the matrix. The correlation coefficient
r1 is then calculated with sxy in line 11.
A simpler way to calculate the correlation coefficient is to directly use NumPy function
np.corrcoef(X,Y) (line 12). This function also returns a 2 × 2 matrix. The value for r2 is
either in the first row, second column, of the matrix or in the second row, first column
of the matrix.
Line 13 calculates the slope m1 from the covariance sxy[0,1] and the square of the stan-
dard deviation sx from the X measurement values. The slope m2 is calculated in line 14
using the correlation coefficient r2(0,1) and the standard deviations sx and sy.
In line 15, the y-axis intercept a1 is calculated from the mean values of the X and Y values
using the conventional method. Instead of the slope m2, the slope m1 could have been
used as well.
The most effective method to calculate all three parameters with only one statement is
shown in line 16. The slope m3, the y-axis intercept a2, and the correlation coefficient r3
are returned as tuples by the SciPy function stats.linregress(X,Y).
441
9 Statistical Computations
The print() function outputs the regression parameters in lines 17 to 25. All computa-
tion methods provide the same results. The slope is about 0.32, and the y-axis intercept
has a value of about −2.51. Thus, the regression line adheres to the following equation:
01 #15_regeression_line.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 from scipy import stats
05 X=np.array([46,53,29,61,36,39,47,49,52,38,55,32,57,54,44])
06 Y=np.array([12,15,7,17,10,11,11,12,14,9,16,8,18,14,12])
07 m, a, r, p, e = stats.linregress(X,Y)
08 fig, ax=plt.subplots()
09 ax.plot(X, Y,'rx')
10 ax.plot(X, m*X+a)
11 ax.set_xlabel("Relative humidity in %")
12 ax.set_ylabel("Moisture content of the material")
13 plt.show()
Output
Figure 9.4 shows the output regression line after the calculation performed in Listing
9.15.
Analysis
The program determines the y-axis intercept a and the slope m in line 07 using the
stats.linregress(X,Y) SciPy function. Line 09 plots the discrete xi and yi values as red
crosses using the plot(X, Y, 'rx') method. The ax.plot(X, m*X+a) statement in line 10
causes the plot of the regression line. The crosses of the scatter plot clearly show that a
strong correlation exists between the relative humidity and the moisture content of
the material.
442
9.8 Project Task: Simulation of a Quality Control Chart
The following equation applies to the lower control limit of the mean value chart:
The following equation applies to the upper control limit of the standard deviation
chart:
443
9 Statistical Computations
The values of factors A3 and B4 can be found in relevant tables. Thus, for a sample of
n = 5, we find for A3 = 1.152 and for B4 = 1.669.
The required process quality is not met if the following conditions are true:
쐍 The lower or upper intervention limit is exceeded.
쐍 Seven consecutive values are above or below the centerline (run).
쐍 Seven consecutive values in an interval are ascending or descending (trend).
쐍 More than 90% of the values lie within the middle third of the intervention limits
(middle third).
Listing 9.16 computes the arithmetic mean, standard deviation, and intervention limits
for each sample from ten samples with five measurement values each. The measure-
ment data is read sequentially from a file and converted into a table with five rows and
ten columns using NumPy function reshape().
01 #16_qrt_table.py
02 import numpy as np
03 rows=5
04 columns=10
05 A3=1.152
06 B4=1.669
07 values = np.loadtxt("data.txt")
08 n=len(values)
09 table=np.reshape(values,(rows,columns),order='F')
10 mw=[]
11 staw=[]
12 for i in range(columns):
13 sum1=0
14 sum2=0
15 for j in range(rows):
16 sum1=sum1+table[j,i]
17 mean=sum1/rows
18 for j in range(rows):
19 sum2=sum2+(mean-table[j,i])**2
20 standardabw=np.sqrt(sum2/(rows-1))
21 mw.append(round(mean,2))
22 staw.append(round(standardabw,3))
23 mmw=np.mean(mw)
24 mws=np.mean(staw)
25 UCLm=mmw + A3*mws #Upper control limit
26 LCLm=mmw - A3*mws #Lower control limit
27 UCLs=B4*mws
28 print("Table of measurement values:\n",table)
29 print("Mean value of the samples:")
444
9.8 Project Task: Simulation of a Quality Control Chart
30 print(mw)
31 print("Standard deviation of the samples:")
32 print(staw)
33 print("Mean value chart")
34 print("Upper intervention limit:",UCLm)
35 print("Lower intervention limit:",LCLm)
36 print("Standard deviation chart")
37 print("Upper intervention limit:",UCLs)
Output
Analysis
In lines 10 and 11, two empty mw[] and staw[] lists are defined for the means and stan-
dard deviations of the ten samples. These lists are passed as parameters to the
np.mean() NumPy function in lines 23 and 24.
The mw means and standard deviations staw of each sample are calculated in lines 15 to
22. The sum algorithms and the nested loop construct used in this case are elaborate
and, as will be shown in the next example, actually unnecessary because they do not
exhaust the capabilities of Python.
The statements in lines 25 to 27 compute the upper and lower intervention limits
according to the specifications with factors A3 and B4. For samples with n = 5, the calcu-
lation of the lower intervention limits for the standard deviation is omitted.
445
9 Statistical Computations
The outputs are created in lines 28 through 37. The table of the 50 measurement values
is displayed so that you can check whether the program correctly calculates the statis-
tical characteristics of the individual samples. Each column of the table can be assigned
the corresponding value for the mean and standard deviation.
Checking process quality on the basis of numerical values is too cumbersome in oper-
ational practice. For this reason, the courses of the mean values and standard devia-
tions of the individual samples are visualized graphically as polylines. Listing 9.17
shows how such a graphic program can be implemented with the resources of Python
(slicing).
01 #17_qrt_graphics.py
02 import numpy as np
03 import matplotlib.pyplot as plt
04 rows=5
05 columns=10
06 A3=1.152
07 B4=1.669
08 values = np.loadtxt("data.txt")
09 n=len(values)
10 table=np.reshape(values,(rows,columns),order='F')
11 h=np.linspace(1,columns,columns)
12 mw=[]
13 staw=[]
14 for i in range(columns):
15 mw.append(round(np.mean(table[0:rows,i]),2))
16 staw.append(round(np.std(table[0:rows,i],ddof=1),3))
17 mmw=np.mean(mw)
18 mws=np.mean(staw)
19 UCLm=mmw+A3*mws #Upper control limit
20 LCLm=mmw-A3*mws #Lower control limit
21 UCLs=B4*mws
22 x=[1,columns]
23 y1=[UCLm,UCLm]
24 y2=[mmw,mmw]
25 y3=[LCLm,LCLm]
26 y4=[UCLs,UCLs]
27 fig, ax = plt.subplots(2, 1)
28 ax[0].set_title("Mean value chart")
29 ax[0].plot(x,y1,'r-')
30 ax[0].plot(x,y2,'g-')
31 ax[0].plot(x,y3,'r-')
32 ax[0].plot(h,mw,'bx-')
33 ax[0].set_ylabel("Mean")
34 ax[1].set_title("Standard deviation chart")
446
9.8 Project Task: Simulation of a Quality Control Chart
35 ax[1].plot(x,y4,'r-')
36 ax[1].plot(h,staw,'gx-')
37 ax[1].set_xlabel("Samples")
38 ax[1].set_ylabel("s")
39 fig.tight_layout()
40 plt.show()
Output
Figure 9.5 shows a graphical representation of the two-lane control chart output by the
program.
Analysis
In lines 14 to 16, the program computes the mean mw and standard deviation staw of a
sample within a for loop with column index i. The for loop iterates all table columns
from 1 to 10. This improvement over the first version is achieved by what’s called slicing
with the slicing operator [index1: index2]. Both statistical characteristic values are
computed for each column from the beginning of the row (index 0) to the end of the
row (index 4). Thus, slicing does not require a for loop.
Lines 23, 25 and 26 define the y-coordinates of the intervention limits. The mean value
of all 50 measurement values is determined by the y-coordinate in line 24.
447
9 Statistical Computations
In line 27, a graphical window for two subplots is created using the subplots(2,1)
method. The first parameter sets the number of subplots, and the second parameter
sets the number of columns. Thus, the standard deviation chart is drawn directly below
the mean chart. The subplots() method creates the fig and ax objects. ax[0] and ax[1]
can be used to access the methods of the Matplotlib module. The 0 index sets the prop-
erties of the first subplot, while index 1 sets the properties of the second subplot.
For further work on this program, a useful next step might be to assign normally dis-
tributed random numbers to the values variable in line 08 by using the random.nor-
mal() NumPy function. Thus, with each new program start, you can simulate whether
an overrun of intervention limits has occurred in a trend, a run, or a middle third.
9.9 Tasks
1. Write a program to calculate the harmonic mean and compare the runtime with the
runtime of SciPy function stats.hmean(array).
2. Write a program to compute the geometric mean and compare the runtime with the
runtime of SciPy function stats.gmean(array).
3. The following formula is supposed to be used to compute the standard deviation:
Write an appropriate program and compare the runtime with Python function
stdev(array), the NumPy function numpy.std(array,ddof=1), and the SciPy function
stats.tstd(array).
4. Write a program that computes the absolute and relative probability of how often a
given measure might occur in the limits from a to b in a normally distributed series
of measurements with n workpieces. The population n of all measurement values
and the standard deviation of a series are given.
5. The skew of a frequency distribution can also be computed using the following for-
mula:
448
Chapter 10
Boolean Algebra
In this chapter, you’ll learn how to create truth tables using Python and
how to simplify logical functions by using the SymPy module.
When George Boole formulated the algebra named after him in 1854, no one yet
thought of the importance this subfield called discrete mathematics and how it would
impact the entire evolution of technology. Boolean algebra forms the theoretical foun-
dation of all modern automation and computer systems. In Boolean algebra, variables
have only two states: 0 (false) and 1 (true). Only three logical operations are applicable
to these variables: the logical AND operation (conjunction), the logical OR operation
(disjunction), and the negation.
The representation of functions y = 𝑓(x) known from analysis is also applicable to Bool-
ean algebra:
Since the independent variables may only take the states 0 or 1, the depen-
dent variables also have only these two states. This functional dependence
can be illustrated by a technical system (blackbox) with inputs x and outputs y, as
shown in Figure 10.1.
x1 y1
x2 y2
Digital
System
xⁿ y
ⁿ
Boolean algebra helps you design and develop digital circuits and controls. In the
development process, truth tables are first created to represent the function of the con-
trol task. Logic functions are then derived from these truth tables and simplified using
Boolean algebra. The logic functions obtained in this way then form the basis for pro-
gramming programmable logic controllers (PLCs) or for implementing electronic cir-
cuits, for example with transistor-transistor logic (TTL) integrated circuits (ICs) as
hardware.
449
10 Boolean Algebra
Python provides a powerful tool for simplifying logic functions: the simplify_logic()
method from the SymPy module.
Operation Python
Keyword
AND and
OR or
NEGATION not
10.1.1 Conjunction
x1
& y
x2
Three variants are common for the notation of the AND operation:
or or
In Python, the AND operation is implemented using the and keyword or the & operator
(bitwise AND). In your Python development environment, enter the source code pro-
vided in Listing 10.1 and run the program.
01 #01_and.py
02 print("x1\tx2\ty")
03 for x1 in False,True:
04 for x2 in False,True:
450
10.1 Logical Operations
05 y=x1 and x2
06 #y=x1 & x2
07 print(x1,x2,y,sep='\t')
As expected, you’ll obtain the following truth table of the AND operation:
x1 x2 y
False False False
False True False
True False False
True True True
For the program to output a table with all four possible combinations of the input vari-
ables, a double-nested for loop is implemented in lines 03 and 04. The commented-out
line 06 contains the & operator for the bitwise AND operation. Both variants provide
the same result. In line 07, the sep='\t' parameter inserts a tab between all outputs.
10.1.2 Disjunction
x1
≥1 y
x2
or or
01 #02_or.py
02 print("x1\tx2\ty")
03 for x1 in False,True:
04 for x2 in False,True:
05 y=x1 or x2
451
10 Boolean Algebra
06 #y=x1 | x2
07 print(x1,x2,y,sep='\t')
x1 x2 y
False False False
False True True
True False True
True True True
10.1.3 Negation
I now want to describe negation through examples using the NAND and NOR func-
tions. The NAND function allows you to negate the output of the AND circuit. The NOR
function allows you to negate the output of the OR circuit. The negation is symbolized
either with an overbar (¯) or the negation sign (¬). In the circuit symbols for the NAND
and NOR circuits, the negation is symbolized by a small circle at the output.
x1
& y
x2
x1
≥1 y
x2
452
10.2 Laws of Boolean Algebra
In Python, the negation can be implemented using the not keyword or the ~ operator
(a tilde, for bitwise negation). Let’s examine both functions using the Python program
provided in Listing 10.3.
01 #03_negation.py
02 print("x1\tx2\tNAND\tNOR")
03 for x1 in False,True:
04 for x2 in False,True:
05 y1=not (x1 and x2)
06 y2=not (x1 or x2)
07 print(x1,x2,y1,y2,sep='\t')
The program provided in Listing 10.3 outputs the following truth table of negated AND
and OR functions:
x1 x2 NAND NOR
False False True True
False True True False
True False True False
True True False False
The negated AND function is False if both input variables have the value True. The
negated OR function is True if both input variables have the value False.
>>> 0 and 1
0
>>> 0 & 1
0
453
10 Boolean Algebra
>>> 0 or 1
1
>>> 0 | 1
1
Similarly, a NOR function can be transformed into a NAND function with negated
inputs with the following equation:
The program provided Listing 10.4 illustrates the validity of this law.
01 #04_demorgan.py
02 print("x1\tx2\ty1\ty2\ty3\ty4")
03 for x1 in False, True:
04 for x2 in False, True:
05 y1=not(x1 and x2)
06 y2=not x1 or not x2
07 y3=not(x1 or x2)
08 y4=not x1 and not x2
09 print(x1,x2,y1,y2,y3,y4,sep='\t')
Output
x1 x2 y1 y2 y3 y4
False False True True True True
False True True True False False
True False True True False False
True True False False False False
454
10.2 Laws of Boolean Algebra
The table of the program output confirms that the values of the third column (y1)
match the values of the fourth column (y2). The same applies to the fifth and sixth col-
umns (y3=y4).
Both forms of the distributive law can now be proven using a Python program. Enter
the source code provided in Listing 10.5 into your development environment and start
the program.
01 #05_distributive.py
02 print("x1 \t x2 \t y1 \t y2 \t y3 \t y4")
03 for x1 in False, True:
04 for x2 in False, True:
05 for x3 in False, True:
06 y1=(x1 and x2) or (x1 and x3)
07 y2=x1 and (x2 or x3)
08 y3=(x1 or x2) and (x1 or x3)
09 y4=x1 or (x2 and x3)
10 print(x1,x2,y1,y2,y3,y4,sep='\t')
Output
x1 x2 y1 y2 y3 y4
False False False False False False
False False False False False False
False True False False False False
False True False False True True
True False False False True True
True False True True True True
True True True True True True
True True True True True True
The truth table confirms both forms of the distributive law. In lines 03 to 05, a triple-
nested for loop is implemented so that all eight possible combinations of input states
can be captured.
455
10 Boolean Algebra
The simplification of logic functions is usually quite complex and prone to errors. For
this reason, you should rely on software-based support for the development process.
Python provides developers with a powerful tool for simplifying complex logic func-
tions: the simplify_logic(y) method from the SymPy module. Through three exam-
ples, I will now demonstrate how to use this tool.
>>> x1=0
>>> x1 or not x1
True
>>> x1=1
>>> x1 or not x1
1
Obviously, regardless of the value of the variable x1, the following rule applies: x1 or not
x1 = 1. This law can be illustrated by connecting two switches in parallel. Regardless of
whether a switch has the state 0 or 1, the circuit is always closed.
The following example of four input variables x1 … x4 and one output variable y illus-
trates how a logic function can be simplified. The input variables are conjunctively
linked in the following ways:
All four logic functions of this example are linked disjunctively in the following way:
456
10.3 Circuit Synthesis
By factoring out x1 and x2, we can obtain the following simplified logic function:
01 #06_simplify1.py
02 from sympy.logic import simplify_logic
03 from sympy import symbols
04 x1, x2, x3, x4 = symbols('x1 x2 x3 x4')
05 y1 = ( x1 & x2 & ~x3 & ~x4)
06 y2 = (~x1 & x2 & ~x3 & ~x4)
07 y3 = ( x1 & ~x2 & ~x3 & ~x4)
08 y4 = (~x1 & ~x2 & ~x3 & ~x4)
09 y = y1 | y2 | y3 | y4
10 V = simplify_logic(y)
11 print("y = ",V)
Listing 10.6 Simplification of Four Logic Functions with Four Variables Each
Output
In line 02, Python’s sympy.logic module is using the simplify_logic method to simplify
logic functions. In line 03, the sympy module is imported with the symbols class for the
symbolic processing of the logical operations. The statement in line 04 specifies the
names of the logical input variables.
The lines 05 to 08 contain the logic functions y1 to y4. Note that all variables must be
linked via the binary operator & (bitwise AND). All negations require the unary operator ~
(a tilde, for bitwise negation). In line 0909, these four functions are linked disjunctively
using the | operator (bitwise OR). The implementation of logic functions is not bound
to a special form. You can implement logic functions as Python source code in conjunc-
tive form, in the disjunctive normal form, or in any other valid form.
The simplify_logic(y) method in line 10 simplifies the logic function from line 09. The
simplified function is then output in line 11. The result matches the theoretically deter-
mined value.
457
10 Boolean Algebra
No. x1 x2 x3 x4 y
0 0 0 0 0 1
1 0 0 0 1 1
2 0 0 1 0 1
3 0 0 1 1 1
4 0 1 0 0 1
5 0 1 0 1 0
6 0 1 1 0 0
7 0 1 1 1 0
8 1 0 0 0 1
9 1 0 0 1 0
10 1 0 1 0 1
11 1 0 1 1 1
12 1 1 0 0 0
13 1 1 0 1 0
14 1 1 1 0 1
15 1 1 1 1 1
Table 10.2 Truth Table for a Digital Circuit with Four Inputs
The truth table provided in Table 10.2 contains ten minterms. For the first and the sec-
ond lines, the minterms are provided. The other eight minterms can be written accord-
ing to the same schema:
No. 0:
No. 1:
and so on.
However, when you design a digital circuit, writing down the minterms is not neces-
sary. Instead, can use a Karnaugh map (K-map) instead. You can then enter all circuit
states of the output variables with y = 1 directly into the K-map shown in Table 10.3.
458
10.3 Circuit Synthesis
x1 ¬x1
1 x4
x2
1 1
¬x4
1 1 1 1
¬x2
1 1 1 x4
x3 ¬x3 x3
All connected rectangular blocks of two, four, and eight with y = 1 can be combined into
simplified terms. Based on the K-map in Table 10.3, you can then obtain the following
simplified logic function:
Listing 10.7 simplifies the minterms of the truth table from Table 10.2.
01 #07_simplify2.py
02 from sympy.logic import simplify_logic
03 from sympy import symbols
04 x1, x2, x3, x4 = symbols('x1 x2 x3 x4')
05 y1 = (~x1 & ~x2 & ~x3 & ~x4)
06 y2 = (~x1 & ~x2 & ~x3 & x4)
07 y3 = (~x1 & ~x2 & x3 & ~x4)
08 y4 = (~x1 & ~x2 & x3 & x4)
09 y5 = (~x1 & x2 & ~x3 & ~x4)
10 y6 = ( x1 & ~x2 & ~x3 & ~x4)
11 y7 = ( x1 & ~x2 & x3 & ~x4)
12 y8 = ( x1 & ~x2 & x3 & x4)
13 y9 = ( x1 & x2 & x3 & ~x4)
14 y10 =( x1 & x2 & x3 & x4)
15 y=y1 | y2 | y3 | y4 | y5 | y6 | y7 | y8 |y9 | y10
16 V=simplify_logic(y)
17 print("y = ",V)
The output of the program from Listing 10.7 confirms the result obtained using the K-
map from Table 10.3:
y = (x1 & x3) | (~x1 & ~x2) | (~x2 & ~x4) | (~x1 & ~x3 & ~x4)
459
10 Boolean Algebra
The logic functions can be directly copied from the truth table in Table 10.2 into the
Python source code, as shown in Listing 10.8.
01 #08_simplify3.py
02 from sympy.logic import simplify_logic
03 from sympy import symbols
04 x1, x2, x3, x4 = symbols('x1 x2 x3 x4')
05 y1 = ( x1 | ~x2 | x3 | ~x4)
06 y2 = ( x1 | ~x2 | ~x3 | x4)
07 y3 = ( x1 | ~x2 | ~x3 | ~x4)
08 y4 = (~x1 | x2 | x3 | ~x4)
09 y5 = (~x1 | ~x2 | x3 | x4)
10 y6 = (~x1 | ~x2 | x3 | ~x4)
11 y=y1 & y2 & y3 & y4 & y5 & y6
12 V=simplify_logic(y)
13 print("y = ",V)
Output
y = (x1 & x3) | (~x1 & ~x2) | (~x2 & ~x4) | (~x1 & ~x3 & ~x4)
The output from Listing 10.8 matches the result of the program from Listing 10.7.
460
10.4 Project Task: Seven-Segment Coding
By using a logic function for each segment, we can control the segments in such a way
that the desired number is always displayed.
The design of our digital circuit should have the four inputs A, B, C, and D (binary-coded
decimal [BCD] code) and the seven outputs a through g. The circuit must control the
seven segments so that the numbers from 0 to 9 are shown correctly in the display. For
the representation of zero, for example, current must flow in all segments except g.
Table 10.4 defines in detail how the segments should be controlled.
Inputs Outputs
T D C B A a b c d e f g
0 0 0 0 0 1 1 1 1 1 1 0
1 0 0 0 1 0 1 1 0 0 0 0
2 0 0 1 0 1 1 0 1 1 0 1
3 0 0 1 1 1 1 1 1 0 0 1
4 0 1 0 0 0 1 1 0 0 1 1
5 0 1 0 1 1 0 1 1 0 1 1
6 0 1 1 0 0 0 1 1 1 1 1
7 0 1 1 1 1 1 1 0 0 0 0
8 1 0 0 0 1 1 1 1 1 1 1
9 1 0 0 1 1 1 1 1 0 1 1
10 1 0 1 0 x x x x x x x
11 1 0 1 1 x x x x x x x
12 1 1 0 0 x x x x x x x
13 1 1 0 1 x x x x x x x
14 1 1 1 0 x x x x x x x
15 1 1 1 1 x x x x x x x
461
10 Boolean Algebra
Only the lines from 0 to 9 are needed to display the ten digits. The fields that are not
required have been marked with an x. These fields are referred to as don’t-care terms.
Since it does not matter whether they take the logical state 0 or 1, they can be counted
as logical 1 for the OR normal form or as logical 0 for the AND normal form, which sim-
plifies the logic functions even further.
Experienced digital technicians can immediately recognize the solution for segment c,
for example, from the conjunctive normal form:
You do not need to consider input D because it can be either 0 or 1, so whether it is pres-
ent does not matter.
You can transfer the logic functions for the segments a to g directly from the truth table
as disjunctive or conjunctive normal form into the source code of the program. The
don’t-care terms (in lines 10 through 15 in Table 10.4) are not included in Listing 10.9.
01 #09_seven_segment.py
02 from sympy.logic import simplify_logic
03 from sympy import symbols
04 D, C, B, A = symbols('D C B A')
05 a=( D|C|B|~A) & (D|~C|B|A) & (D|~C|~B|A)
06 b=( D|~C|B|~A)&(D|~C|~B|A)
07 c=( D|C|~B|A)
08 d=( D|C|B|~A) & (D|~C|B|A) & (D|~C|~B|~A)
09 e=(~D&~C&~B&~A)|(~D&~C&B&~A)|(~D&C&B&~A)|(D&~C&~B&~A)
10 f=(D|C|B|~A) & (D|C|~B|A) & (D|C|~B|~A) & (D|~C|~B|~A)
11 g=(D|C|B|A) & (D|C|B|~A) & (D|~C|~B|~A)
12 print("a = ",simplify_logic(a))
13 print("b = ",simplify_logic(b))
14 print("c = ",simplify_logic(c))
15 print("d = ",simplify_logic(d))
16 print("e = ",simplify_logic(e))
17 print("f = ",simplify_logic(f))
18 print("g = ",simplify_logic(g))
Output
462
10.5 Tasks
Analysis
Lines 05 to 11 contain the logic functions of segments a through g. The functions are
implemented as either min or max terms. The result for segment c=A|C|D|~B (line 07)
was expected because nothing at all could be simplified. Individual logic functions
could be simplified even further by considering don’t-care terms (see task number 6).
10.5 Tasks
1. Justify the following:
a) 2 | 2 = 2
b) 3 | 4 = 7
c) 3 & 8 = 0
d) 2 & 2 = 2
e) ~3 = –4
2. Write a program that outputs the truth table of an alternating circuit (antivalence
function).
3. Write a program that outputs the truth table for the functions
and .
4. Write a program that simplifies the function
.
5. For a system with three motors, a warning (indicator light or horn) should be issued
if the sum of the powers is greater than or equal to 4kW. The rated power of the
motors is P1 = 1kW, P2 = 2kW und P3 = 3kW. Develop a logic function for the controller
and write a program for simplifying the logic function.
6. The logic functions of segment c need to be simplified considering don’t-care terms.
Write an appropriate Python program.
7. Write a program that checks the results of a seven-segment display.
463
Chapter 11
Interactive Programming
Using Tkinter
In this chapter, you’ll learn how to create user interfaces using Python’s
Tkinter module. The project task demonstrates the simulation of a
control loop with a PID controller.
One disadvantage of console applications is how they severely limit interactions with
the user. The Python console provides only the input and print methods for input and
output but no other interactive controls. In addition, mathematical functions cannot
be represented graphically.
To compensate for this shortcoming, several other graphical user interfaces (GUIs) for
Python exist besides Tkinter, such as pyGTK, wxPython, or PyQt. Because of the some-
times pretty high programming effort and the high required resources, I won’t go into
these modules in more detail.
The name of the Tkinter module is an abbreviation of “Tk-interface.” Tk is a cross-plat-
form toolkit for programming GUI. This toolkit provides all the classes for implement-
ing the controls (widgets), such as labels (Label), single-line text fields (Entry) and
command buttons (Button). Tkinter is already part of Python, so it does not need to be
installed like many other modules.
The particular advantage of Tkinter is that you can write functional prototypes for
engineering applications with a GUI with little programming effort. Because Tkinter
programs require few resources, they also run on Raspberry Pis.
Tkinter programs are developed in five steps:
1. Importing the Tkinter module
2. Creating an object for the main window of the user interface
3. Creating objects for the controls (widgets)
4. Arranging the controls (widgets) in the user interface
5. Implementing an event query
In all the following programs, prefixes represent the controls we use. This approach
makes the source code more clear and easier to read. Based on the prefixes, the controls
we use can be identified more quickly in source code analysis.
Table 11.1 contains an overview of the most important controls and their prefixes.
465
11 Interactive Programming Using Tkinter
Button Command button for triggering events (e.g., a mouse click) cmd
For a description of the Tkinter module, including an overview of all classes and meth-
ods, enter the following statements in the Python shell:
Most of the following sample programs refer to the project task described in Section
11.6. The project is divided into individual subtasks, each of which determines self-con-
tained partial solutions: implementing inputs and outputs for numerical calculations,
designing user interfaces, graphical representations of functions, querying mouse
coordinates to determine function values, and the developing and processing of algo-
rithms for the numerical solutions of the differential equations of the controller.
466
11.1 Interactions with Command Buttons, Textboxes, and Labels
11.1.1 Labels
You can create a label by using the constructor of the Label class:
You must pass at least two arguments to the constructor of the Label class. The first
parameter is the object of the main window, window. The second parameter is the text
property, which determines the text that will be displayed on the user interface. The
remaining parameters are optional. By calling the Label constructor, you can display
the lblE object.
The first sample program demonstrates how to use Tkinter to implement a window for
simple text output. In your Python development environment, enter the source code
provided in Listing 11.1.
01 #01_gui_window.py
02 import tkinter as tk
03 window=tk.Tk()
04 lblResult=tk.Label(window,text="Result output",font=("Arial 24"))
05 lblResult.pack()
06 window.mainloop()
When you start the program, the window shown in Figure 11.1 should appear on the
screen.
Analysis
In line 02, the tkinter module is imported, and the tk alias is set. This alias enables you
to access all methods of the classes of Tk().
In line 03, an object of the Tk class named window is created. In the further course of the
program, this object is needed as a parameter for other controls.
In line 04, an object named lblResult of the Label class is created. This object is useful
to prefix all the names of the label fields. Here and in all sample programs that follow,
the lbl prefix is used for all label fields. This convention makes reading Tkinter source
code much easier when applied to other controls as well (see Table 11.1). The lblResult
variable can also be used to output the result of a computer operation if required. Three
parameters are passed to the Label() constructor (method of the same name of the
467
11 Interactive Programming Using Tkinter
Label class): The first parameter is the name of the window window created as an object
in line 03. This parameter determines in which window the lblResult label will be dis-
played. The second parameter (text) is passed the label’s caption as a string. The third
parameter sets the font type and size of the output.
The pack() method in line 05 is needed to display and arrange controls in the main win-
dow. If no parameters are passed to this method, then all the controls are arranged one
below the other in the order in which they are listed, line by line.
The mainloop() method in line 06 is an infinite loop that is responsible for retrieving all
user activities (events), processing them, and executing the requested actions, such as
function calls or exiting the program.
All methods are always accessed using the dot operator according to the convention
ObjectName.Method(param1, param2, ...).
txtE=Entry(window, width=5,justify="right")
The first parameter (window) is again the object of the main window. The second param-
eter sets the width of the text box, and the third parameter sets the alignment of the
text. After calling the constructor the txtE object is created.
You can create a command button via the constructor of the Button class:
Again, the first parameter is the object of the main window, window. The second param-
eter sets the label of the command button. To the third parameter (command) must be
assigned the name of the function function, which is to be executed when you click the
command button. You must omit the usual parentheses in a function call.
468
11.2 The Layout Manager of Tkinter
쐍 Balance
A window is divided by a vertical line in the center. The information density (num-
ber of controls) should be approximately equally distributed on both sides, left and
right. The window shown in Figure 11.2 does not meet this requirement because the
information has not been spread across two columns. The label fields should be on
the left, and the text fields should be on the right.
쐍 Symmetry
A window is divided by a horizontal line in the middle. The horizontally opposed
controls should be of the same type. The labels and the corresponding text fields
should therefore each be arranged in one line. A new line must be set up for each
new input. On the left-hand side of the window, above and below the horizontal line,
there are the labels. On the right-hand side of the window, above and below the hor-
izontal line, there are the text fields. The window shown in Figure 11.2 does not meet
this requirement because the labels have not been placed in front of the text fields
on one level. However, the requirement for symmetry cannot always be met in real
life.
쐍 Sequence
The user’s perception should be guided sequentially through the window. Unneces-
sary jumps should be avoided. Because the user first looks at the upper left-hand
area, the most important information should also be at the top left.
쐍 Simplicity
Different fonts or colors should be used with restraint. Each window should be
designed to be as simple as possible.
쐍 Minimizing virtual lines
Users unconsciously form virtual lines when viewing user interfaces. In other words,
if the controls were not placed vertically at the same distance from the window
frame, as shown in Figure 11.2, then the user will find the window design awkward:
The harmony of the user’s observation will be disturbed.
Tkinter’s layout manager provides the following methods: pack(), frame(), place(), and
grid(). The frame method allows you to divide the user interface into separate areas
(frames). The layout manager then inserts the controls into the desired frame. The
place method allows you to place the individual controls anywhere in the user inter-
face (window) by specifying their x-y coordinates. The coordinate origin is located in
the upper-left corner of the window. Since the frame and place methods do not provide
satisfactory results, and at a relatively high cost, I won’t discuss them further. The sam-
ple programs for these two methods can be found in the download area of https://
www.rheinwerk-computing.com/5852/ or https://drsteinkamp.de.
The pack method is particularly well suited for testing a prototype. If this prototype
meets all technical requirements, its design can be optimized using the grid layout
manager.
469
11 Interactive Programming Using Tkinter
The formula shows that the program requires two single-line text fields (Entry) for the
input of the cylinder length l and the cylinder diameter d as well as one label each for
the labeling of the text fields and the result output. You can use the following example
as a pattern for the calculation of any complex formula. You only need to add the nec-
essary text fields for the inputs and adjust the function according to the calculation
rule.
To keep the source code as simple and clear as possible, this example deliberately
avoids an appealing and functional design. In your development environment, enter
the source code provided in Listing 11.2 and start the program.
01 #02_gui_pack.py
02 import tkinter as tk
03 #calculation of the moment of inertia
04 def moment_of_inertia():
05 pi=3.14159
06 rho=7.85 #kg/dm^3
07 d = float(txtDiameter.get())#dm
08 l = float(txtLength.get()) #dm
09 try:
10 J = rho*l*pi*d**4/32
11 J = ('{0:6.2f}'.format(1e-2*J)) #kgm^2
12 lblResult["text"] = "J=" + str(J) + " kgm^2"
13 except:
14 lblResult["text"]= "Enter numbers"
15 #graphics area
16 main = tk.Tk()
17 main.minsize(400,200)
18 main.title("Moment of inertia of a cylinder")
19 #create label
20 lblDiameter=tk.Label(main, text="Enter diameter in dm")
21 lblLength=tk.Label(main, text="Enter length in dm")
22 lblResult = tk.Label(main,text="")
23 #create text fields
24 txtDiameter=tk.Entry(main, width=5,justify="right")
470
11.2 The Layout Manager of Tkinter
25 txtDiameter.insert(5, "0.8")
26 txtLength = tk.Entry(main,width=5, justify="right")
27 txtLength.insert(5,"10")
28 #create command buttons
29 cmdCompute=tk.Button(main, text="Compute", command=moment_of_inertia)
30 cmdExit=tk.Button(main, text="Exit", command=main.destroy)
31 #insert controls
32 lblDiameter.pack()
33 txtDiameter.pack()
34 lblLength.pack()
35 txtLength.pack()
36 cmdCompute.pack()
37 lblResult.pack()
38 cmdExit.pack()
39 main.mainloop()
Listing 11.2 Program with Label Fields, Text Fields, and Command Buttons
After starting the program, the user interface appears on the desktop roughly as shown
in Figure 11.2.
The actual appearance of the window depends on the operating system used (i.e.,
macOS, Linux, or Windows).
Analysis
From line 04, the moment_of_inertia() function is defined. The get() method reads the
strings from the txtDiameter and txtLength text fields, converts them to floats, and
assigns them to the d and l variables. An exception handling process ensures that input
errors (when letters instead of numbers are entered) will be caught (lines 09 to 14).
In line 17, the minimum window size of 400 pixels width and 200 pixels height is set.
Thus, the aspect ratio is 2:1.
471
11 Interactive Programming Using Tkinter
In line 24, the txtDiameter object is created by calling the Entry() constructor of the
Entry class. Three parameters are passed to the constructor: the name of the main win-
dow, the width of the text field, and the alignment of the string (right aligned in this
case).
The insert(param1, param2) method is used to read the content of the text field and
store it in the txtDiameter object (line 25). The first parameter sets the length of the
string. The second parameter contains default values.
In lines 29 and 30, the constructors of the Button(param1, param2, param3) class are
called. The name of the main window is passed as the first parameter. The second
parameter specifies the label of the command button. The third parameter (command=
function name) is used to call and run a function. Note that the function call omits the
parentheses. The command=main.destroy method call terminates the program.
The pack() method does not yet contain any parameters. The controls are displayed
line by line and centered in the order of the pack statements.
If you insert the side="left" parameter in the pack method, the layout manager will
place the controls in one row from left to right.
The layout of the program is visually unappealing and dysfunctional for now. Using the
grid method, you can design a user-friendly interface with little effort.
lblDiameter txtDiameter
lblLength txtLength
cmdCompute cmdExit
Add the source code provided Listing 11.3 to our example from Listing 11.2 and start the
program.
472
11.2 The Layout Manager of Tkinter
01 #03_gui_grid.py
02 import tkinter as tk
03 #function for moment of inertia
04 def moment_of_inertia():
05 pi=3.14159
06 rho=7.85 #kg/dm^3
07 d = float(txtDiameter.get()) #dm
08 l = float(txtLength.get()) #dm
09 try:
10 J = rho*l*pi*d**4/32
11 J = ('{0:6.2f}'.format(1e-2*J)) #kgm^2
12 lblResult["text"] = str(J) + " kgm^2"
13 except:
14 lblResult["text"]= "Enter numbers"
15 #graphics area
16 main = tk.Tk()
17 main.minsize(400,115)
18 main.title("Moment of inertia of a cylinder")
19 lblDiameter=tk.Label(main, text="Enter diameter in dm")
20 lblLength=tk.Label(main, text="Enter length in dm")
21 lblMoment_of_inertia = tk.Label(main,text="Moment of inertia")
22 lblResult = tk.Label(main,text="")
23 txtDiameter=tk.Entry(main, justify="right")
24 txtDiameter.insert(2,"0.8")
25 txtLength = tk.Entry(main, justify="right")
26 txtLength.insert(2,"10")
27 cmdCompute=tk.Button(main, text="Compute",command=moment_of_inertia)
28 cmdExit=tk.Button(main, text="Exit",command=main.destroy)
29 lblDiameter.grid(row=0,column=0,sticky="w")
30 txtDiameter.grid(row=0,column=1,sticky="e")
31 lblLength.grid(row=1,column=0,sticky="w")
32 txtLength.grid(row=1,column=1,sticky="e")
33 lblMoment_of_inertia.grid(row=2,column=0,sticky="w")
34 lblResult.grid(row=2,column=1,sticky="e")
35 cmdCompute.grid(row=3,column=0)
36 cmdExit.grid(row=3,column=1)
37 main.mainloop()
The user interface created using the grid method is shown in Figure 11.3.
473
11 Interactive Programming Using Tkinter
Analysis
The label fields are located in the left column (column=0), just like the Compute com-
mand button. The text fields for the inputs are located in the right column (column=1),
just like the Exit command button. The label fields are left aligned (sticky=w), whereas
the text fields and the result output are right aligned (sticky=e). Since the grid method
of the command buttons does not contain any other properties, they are centered in
the cells.
Listing 11.3 shows that the grid layout meets all design criteria for user interfaces with
relatively little effort. The following sample programs show further layout options for
the grid method.
11.2.3 Summary
Let’s briefly summarize what we’ve learned so far: The constructors of the Label and
Entry classes require the name of the main window or the name of the frame as the first
parameter. Other parameters determine the text of the labels and the appearance of
the controls, for instance:
For better identification, the names of the controls are given unique prefixes (e.g., lbl...,
txt..., cmd..., etc.).
The Insert() method of the Entry class requires the string length of the variable as the
first parameter. It makes sense to specify a realistic default value as the second param-
eter in the following way:
txtName.Insert(stringlength, defaultvalue)
The constructor of command buttons expects a third parameter for the function call, in
the following way:
474
11.3 Selection with Radio Button
The minimum size of a window can be set using the following statement:
window.minsize(width, height)
Tkinter’s layout manager uses the grid method to specify the arrangement of all con-
trols as a table grid:
objName.grid(row,column)
All methods of the Tk() class are accessed using the dot operator (.), as in objName.
Method().
Radiobutton(main,text="Sphere",variable=select,value="sp")
The first parameter (main) refers to the window in which the radio button is to be
inserted. The second parameter sets the label. The third parameter (variable) must be
assigned a global variable that manages and summarizes the options of the selection.
The fourth parameter activates the selected option. Listing 11.4 shows how the moment
of inertia is calculated for the three bodies (point mass, solid cylinder, and sphere), after
the user makes a selection with radio buttons.
01 #04_radiobutton.py
02 import tkinter as tk
03
04 def compute():
05 m=1 #Mass in kg
06 r=0.5 #Radius in m
07 Jp = m*r**2 #Point mass
08 Jz = 0.5*m*r**2 #Solid cylinder
09 Jk = 2./5.*m*r**2 #Sphere
10 if select.get()=="pm":
11 lblResult["text"]=str(Jp)+" kgm^2"
12 elif select.get()=="sc":
13 lblResult["text"]=str(Jz)+" kgm^2"
14 elif select.get()=="sp":
15 lblResult["text"]=str(Jk)+" kgm^2"
16 #graphics area
17 main = tk.Tk()
475
11 Interactive Programming Using Tkinter
18 main.minsize(580,100)
19 main.title("Selection with radio button")
20 select=tk.StringVar()
21 select.set("sc")
22 #create controls
23 optPm=tk.Radiobutton(main,text="Point mass",variable=select,value="pm")
24 optSc=tk.Radiobutton(main,text="Solid cylinder",variable=select,value="sc")
25 optSp=tk.Radiobutton(main,text="Sphere",variable=select,value="sp")
26 lblJ=tk.Label(main, text="J=")
27 lblResult = tk.Label(main,text="")
28 cmdStart = tk.Button(main, text="Compute",command=compute)
29 cmdExit=tk.Button(main,text="Exit",command=main.destroy)
30 #display controls
31 optPm.pack(side="left")
32 optSc.pack(side="left")
33 optSp.pack(side="left")
34 cmdStart.pack(side="left")
35 cmdExit.pack(side="left")
36 lblJ.pack(side="left")
37 lblResult.pack(side="left")
38 main.mainloop()
Output
Figure 11.4 shows how selection with radio buttons is enabled and displayed in the user
interface when executing the program provided in Listing 11.4.
Analysis
In line 20, the global select variable is created as a copy of the StringVar class. In line 21,
this variable is initialized using select.set("sc"). The sc designator is supposed to
stand for “solid cylinder.” So, the preselection is on the Solid cylinder option.
In lines 23 to 25, the Radiobutton() method creates the three objects optPm, optSc, and
optSp. By assigning the common select variable to the value property in all three meth-
ods, all three radio buttons are combined into one unit.
476
11.4 Slider
11.4 Slider
The slider control enables you to use the mouse pointer to continuously adjust values
for variables. The Scale(main, parmeterlist) method creates a slider object.
Listing 11.5 calculates the moment of inertia for a solid steel cylinder with a length of 1
meter (m) for cylinder diameters from 50 to 200 millimeters (mm).
01 #05_slider.py
02 import tkinter as tk
03 main=tk.Tk()
04 #moment of inertia
05 def moment_of_inertia(self):
06 pi=3.14159
07 rho=7.85 #Density of steel kg/dm^3
08 l=10 #Length of the cylinder in dm
09 d=sldD.get() #Diameter in mm
10 d=1e-2*d #Conversion to dm
11 J = rho*l*pi*d**4/32 #in kgdm^2
12 J=('{0:5.3f}'.format(1e-2*J)) #Conversion to m
13 lblD["text"] = "J=" + str(J) + " kgm^2"
14 #generate slider object
15 sldD=tk.Scale(main, width=20, length=400,
16 from_= 50, to= 200, #range in mm
17 orient='horizontal',
18 resolution=1, #resolution in mm
19 tickinterval=25,
20 label="d in mm",
21 command=moment_of_inertia, #function call
22 font=("Arial 14"))
23 #graphics area
24 main.minsize(500,110)
25 main.title("Moment of inertia of a cylinder")
26 lblD=tk.Label(main,text="J=",font=("Arial 14"))
27 sldD.set(80) #set initial value
28 lblD.pack()
477
11 Interactive Programming Using Tkinter
29 sldD.pack()
30 main.mainloop()
Output
Analysis
In line 09, the get() method determines the current value of the diameter set by the
slider.
In line 15, the slider object, sldD, is created for the cylinder diameter. In line 21, the
moment_of_inertia function is called. Note that you must omit the parentheses. In line
27, you can set an initial value for the cylinder diameter using the sldD.set(80) state-
ment.
create_line(x1,y1,x2,y2,fill='black',width=3)
For the representation of a line, two coordinates must be specified: top left x1,y1 and
bottom right x2,y2. The fill and width properties set the color and width of the line.
478
11.5 The Canvas Drawing Area
Listing 11.6 draws two lines: a horizontal black line in the center of the drawing area and
a blue line running from top left to bottom right.
01 #06_canvas_line.py
02 import tkinter as tk
03 main = tk.Tk()
04 xmax,ymax=500,500
05 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
06 canZ.create_line(0,ymax/2,xmax, ymax/2, fill='black', width=3)
07 canZ.create_line(0,0,xmax, ymax, fill='blue', width=3)
08 canZ.pack()
09 main.mainloop()
Output
Figure 11.6 shows how the lines are displayed in the user interface.
Analysis
In line 04, the width and the height of the drawing area (canvas) are set to 500 pixels
each.
In line 05, the Canvas() constructor of the Canvas class creates the canZ object. This
object is needed to access the methods of the Canvas class.
479
11 Interactive Programming Using Tkinter
In line 06, the create_line() method draws a black horizontal line in the center of the
drawing area. In line 07, the create_line() method draws a blue line from the top-left
corner to the bottom-right corner of the drawing area. The lines have a width of 3 pix-
els.
In line 08, the pack() method ensures that the two lines are displayed in the canZ draw-
ing area.
create_line(x,-sy*f(sx*x)+ym,(x+1),-sy*f(sx*(x+1))+ym,...)
01 #07_scaling.py
02 import math as m
03 import tkinter as tk
04 main = tk.Tk()
05 xmax, ymax = 500, 500
06 x1,x2 = 0, 10
07 y1,y2 =-10, 10
08 ym=ymax/2
09 sx=(x2-x1)/xmax
10 sy=ymax/(y2-y1)
11 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
12 canZ.create_line(0,ym,xmax,ym,fill='black',width=2)
13
14 def f(x):
15 return 8*m.sin(x)
16
480
11.5 The Canvas Drawing Area
17 def draw():
18 dx=1
19 x=0
20 while x<=xmax:
21 canZ.create_line(x,-sy*f(sx*x)+ym,(x+1),\
22 -sy*f(sx*(x+1))+ym,fill='blue',width=2)
23 x=x+dx
24
25 cmdStart=tk.Button(main, text="Draw", command=draw)
26 canZ.pack()
27 cmdStart.pack()
28 main.mainloop()
Output
Figure 11.7 shows the sine curve defined in Listing 11.7 inside the canvas.
Analysis
The drawing area has a size of 500×500 pixels (line 05). The scaling factors sx and sy are
computed in lines 09 and 10.
481
11 Interactive Programming Using Tkinter
In lines 14 and 15, the mathematical function f(x)=8*m.sin(x) is defined. For further
testing purposes, you can also enter other functions here.
The draw() function defined in lines 17 to 23 is called by the Button() constructor of the
Button class in line 25.
In lines 21 and 22, the create_line() method draws the sine function as a blue polyline
with a width of 2 pixels.
01 #08_mouse_coordinates.py
02 import tkinter as tk
03 import math as m
04 main = tk.Tk()
05 xmax, ymax = 500, 500
06 x1,x2 = 0, 10
07 y1,y2 =-10, 10
08 ym=ymax/2
09 sx=(x2-x1)/xmax
10 sy=ymax/(y2-y1)
11 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
12 canZ.create_line(0,ym,xmax,ym,fill='black',width=2)
13
14 def f(x):
15 return 8*m.sin(x)
16
17 def coordinate(e):
18 x, y = e.x, e.y
19 x, y = sx*x, (0.5*ymax-y)/sy
20 x, y = ('{0:4.1f}'.format(x)), ('{0:4.1f}'.format(y))
21 lblCoordinate["text"]=' x: '+str(x)+' y: '+str(y)
22
23 def draw():
24 dx=1
25 x=0
26 while x<=xmax:
27 canZ.create_line(x,-sy*f(sx*x)+ym,(x+1),\
482
11.5 The Canvas Drawing Area
28 -sy*f(sx*(x+1))+ym,fill='blue',width=2)
29 x=x+dx
30
31 lblCoordinate=tk.Label(main, text="")
32 cmdStart=tk.Button(main, text="Draw", command=draw)
33 canZ.pack()
34 cmdStart.pack(side="left")
35 lblCoordinate.pack(side="left")
36 canZ.bind("<Motion>", coordinate)
37 main.mainloop()
Output
How the query of coordinates via mouse pointer is displayed to the user is shown in
Figure 11.8.
Analysis
In lines 17 to 21, the coordinate(e) function is defined. In line 36, this function is called
by the bind() method. The x, y = e.x, e.y assignment of the current mouse position is
performed in line 18. The position of the mouse pointer still needs to be scaled (line 19).
483
11 Interactive Programming Using Tkinter
The coordinates of the mouse pointer are converted to a string in line 21 and assigned
to the lblCoordinate label. The current mouse position is displayed with the lblCoordi-
nate label in line 31.
The canvas is located in the upper area. Below the canvas, the data of the process must
be entered in the left-hand column: armature resistance (R), armature inductance (L),
rated torque (Mn), rated armature current (Ia), and the moment of inertia (J). The mid-
dle column is for the gain (Kp), reset time (Tn), and derivative time (Tv) parameters. The
command buttons are located in the right-hand column. Except for symmetry, almost
all of the criteria we’ve listed, according to which a user interface should be designed,
are fulfilled.
In the next step, you’ll analyze the active circuit diagram of a standardized control loop,
as shown in Figure 11.10.
484
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
w e u y
Controller Process
-y
In this diagram, w represents the reference variable, e the control error, u the manipu-
lated variable, z the disturbance variable, and y the controlled variable. For the algo-
rithm of the control loop simulation, you only need to add the equations for the PID
controller and the process into a loop. The loop is run until the transient state is com-
pleted.
In the third and last step, you must set up the differential equations for the process.
This task can be achieved with the help of a substitute circuit of the externally excited
DC motor, as shown in Figure 11.11.
R L i(t)
UR UL
U1 Cdyn U2
The differential equation system must be developed from the substitute circuit in the
following way:
485
11 Interactive Programming Using Tkinter
i = i + (U1-R*i-u2)*dt/L
u2 = u2 + i*dt/C
01 #09_process.py
02 import tkinter as tk
03 main = tk.Tk()
04 main.title("Second-order process")
05 xmax, ymax = 800, 400
06 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
07 #label fields
08 lblR=tk.Label(main, text="R")
09 lblL = tk.Label(main, text="L")
10 lblMn=tk.Label(main, text="Mn")
11 lblIa=tk.Label(main, text="Ia")
12 lblJ=tk.Label(main, text="J")
13 lblTmax=tk.Label(main, text="tmax")
14 #text fields
15 txtR=tk.Entry(main, width=5)
16 txtR.insert(5,"1.5")
17 txtL=tk.Entry(main, width=5)
18 txtL.insert(5,"24")
19 txtMn=tk.Entry(main, width=5)
20 txtMn.insert(5,"170")
21 txtIa=tk.Entry(main, width=5)
22 txtIa.insert(5,"40")
23 txtJ=tk.Entry(main, width=5)
24 txtJ.insert(5,"0.22")
25 txtTmax=tk.Entry(main, width=5)
26 txtTmax.insert(5,"300")
27
28 def delete():
29 return canZ.delete("all")
30
31 def process():
32 w = ymax/2
33 U1 = w
486
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
34 R = float(txtR.get())
35 L = float(txtL.get())
36 J = float(txtJ.get())
37 Ia = float(txtIa.get())
38 Mn = float(txtMn.get())
39 tmax=float(txtTmax.get())
40 C=1e3*J*(Ia/Mn)**2
41 u2=i=t = 0
42 dt = 0.5
43 sx=xmax/tmax
44 u2_t = []
45 canZ.create_line(0, w, xmax, w, fill='green', width=2)
46 while t<=tmax:
47 i = i + (U1-R*i-u2)*dt/L
48 u2 = u2 + i*dt/C
49 t=t+dt
50 u2_t.append(int(sx*t))
51 u2_t.append(int(-u2) + ymax)
52 canZ.create_line(u2_t, fill='blue', width=2)
53 #command buttons
54 cmdStart=tk.Button(main, text="Start", command=process)
55 cmdNew = tk.Button(main, text="New", command=delete)
56 cmdExit=tk.Button(main, text="Exit", command=main.destroy)
57 #arrange controls
58 canZ.pack()
59 lblR.pack(side="left")
60 txtR.pack(side="left")
61 lblL.pack(side="left")
62 txtL.pack(side="left")
63 lblMn.pack(side="left")
64 txtMn.pack(side="left")
65 lblIa.pack(side="left")
66 txtIa.pack(side="left")
67 lblJ.pack(side="left")
68 txtJ.pack(side="left")
69 lblTmax.pack(side="left")
70 txtTmax.pack(side="left")
71 cmdStart.pack(side="left")
72 cmdNew.pack(side="left")
73 cmdExit.pack(side="left")
74 main.mainloop()
487
11 Interactive Programming Using Tkinter
Output
Figure 11.12 shows the step response in the user interface.
Analysis
First, all label fields are defined (lines 08 to 13), followed by the definition of the text
fields (lines 15 to 26). The delete() function in lines 28 and 29 enables you to delete the
drawing area for new simulations.
In lines 31 to 51, the process() function is defined. The reference variable w in lines 32
and 33 is used to set half of the drawing area. The entries for the process data are made
in lines 34 to 38. The dynamic capacity C is multiplied by the factor 1e3 in line 40. This
operation causes the time axis to be scaled to milliseconds. The initial values u2, i, and
t must be initialized with zero (line 41). The scaling of the t-axis is performed in line 43,
as described earlier. The empty list u2_t (line 44) is needed to store the simulation
result. Within the while loop (lines 46 to 51), the solution of the differential equation
system is performed by using the Euler algorithm. In lines 50 and 51, the time values
and the values of the output voltage u2 are inserted into the empty list, u2_t.
In line 54, the Button method calls the custom function process. When you click the
Start button, the simulation will be executed.
In lines 58 to 73, the pack(side="left") method inserts all controls from left to right in
the main window.
488
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
up = Kp*e #P controller
ui = ui + Kp*e*dt/Tn #PI controller
ud = Kp*Tv*(e-e0)/dt #PD controller
e0=e
In the final line, the last value of the control error e is assigned to the e0 variable, so that
the PD controller can be computed using the difference quotient.
Listing 11.10 simulates either the step response of a P, a PI, or a PID controller.
01 #14_pid_controller.py
02 import tkinter as tk
03 main = tk.Tk()
04 main.title("Step response of P, PI and PID controllers")
05 xmax, ymax = 800,400
06 select=tk.StringVar()
07 select.set("PID")
08 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
09
10 def delete():
11 return canZ.delete("all")
12
13 def controller():
14 tmax=400
15 Kp=50
16 Tn=50
17 Tv=50
18 e=1
19 t=ui=e0=ur=0
20 dt=2
21 sx=xmax/tmax
22 u2_t = []
23 while t<=tmax:
24 up = Kp*e #P controller
25 ui = ui + Kp*e*dt/Tn #PI controller
26 ud = Kp*Tv*(e-e0)/dt #PD controller
27 e0=e
28 t=t+dt
29 if select.get()=="P": ur=up
489
11 Interactive Programming Using Tkinter
Output
How the step response of the P, PI, or PID controllers is displayed in the user interface
is shown in Figure 11.13.
490
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
Analysis
In line 06, the string variable select is defined and set to the default value PID in line 07.
In lines 13 to 34, the controller() function is defined. For the control parameters, values
were selected that provide the most meaningful result possible.
The important statements are in lines 24 to 27, where the algorithm described earlier is
implemented. The P controller has the output variable (manipulated variable) up; the PI
controller has the output variable ui; and the PD controller has the output variable ud.
The controller is selected in lines 29 to 31. Note that you must use the equality operator
== in the if-elif query.
Simulation Prototype
For complex programs, a useful step is to first program a prototype without text fields
and command buttons.
Listing 11.11 shows a simple test version for the control circuit simulation.
01 #11_control_circuit1.py
02 import tkinter as tk
03 main = tk.Tk()
04 main.title("PID controller with second-order process")
05 xmax, ymax = 800,400
06 canZ = tk.Canvas(width=xmax, height=ymax, bg='white')
07 U1=100.0
08 R,L=1,25
09 I,M,J = 40,170,1
10 C=1e3*J*(I/M)**2
11 tmax=250
12 Kp=5
13 Tn=50
14 Tv=10
15 t=i=y=ui=e=e0=0.0
16 w=ymax/2
17 dt=0.25
18 sx=xmax/tmax
19 u2_t = []
20 while t<=tmax:
21 e=w-y
22 up = Kp*e
23 ud = Kp*Tv*(e-e0)/dt
24 e0=e
25 ui = ui + Kp*e*dt/Tn
26 U1 = up + ui + ud
27 i = i + (U1-R*i-y)*dt/L
491
11 Interactive Programming Using Tkinter
28 y = y + i*dt/C
29 t=t+dt
30 u2_t.append(sx*t)
31 u2_t.append(-y + ymax)
32 canZ.create_line(u2_t, fill='blue', width=2)
33 canZ.create_line(0, w, xmax, w, fill='red', width=2)
34 canZ.pack()
35 main.mainloop()
Output
Figure 11.14 shows the control circuit simulation for the test version.
Analysis
In line 07, the value of the input voltage U1=100 of the manipulated variable is set. The
value 100 was chosen so that the output is a percentage. A value of 100% indicates that
the rotational frequency has reached exactly its setpoint. The values for the motor data
(lines 08 and 09) were taken from the technical literature.
In line 20, the while loop starts, and it ends in line 31. In line 21, the control error e=w-y
is calculated. Lines 22 to 26 contain the algorithm of the PID controller. Lines 27 and 28
contain the algorithm of the process. The loop is run until the end time tmax of 250ms
set in line 11 is reached.
492
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
01 #12_control_circuit2.py
02 import tkinter as tk
03 main = tk.Tk()
04 main.title("Control circuit with second-order process")
05 xmax, ymax = 800,400
06 xy=0 #global variable
07 U1=100.0 #setpoint
08 w=ymax/2.#reference variable
09 rd=4 #edge
10 main.minsize(xmax,ymax)
11 main.resizable(False,False)
12 canZ = tk.Canvas(width=xmax,height=ymax,bg='white')
13 canZ.grid(row=0,column=0,columnspan=5)
14 def frame():
15 canZ.create_line(0,w,xmax,w,fill='red',width=2)
16 canZ.create_line(rd,rd,xmax,rd,fill="black",width=2)
17 canZ.create_line(rd,ymax,xmax,ymax,fill="black",width=2)
18 canZ.create_line(rd,0,rd,ymax,fill="black",width=2)
19 canZ.create_line(xmax, rd, xmax, ymax,fill="black",width=2)
20
21 def delete():
22 return canZ.delete(xy)
23
24 def coordinate(event):
25 tmax=float(txtTmax.get())
26 x, y = event.x, event.y
27 x, y = tmax*x/xmax-2, U1*(ymax-y)/w
28 x, y = ('{0:4.0f}'.format(x)), ('{0:3.0f}'.format(y))
29 lblKoordinate["text"]="t:"+str(x)+" ms y:"+str(y)+"%"
30
31 def control_circuit():
32 global xy
33 tmax=float(txtTmax.get())
34 Kp = float(txtKp.get())
35 Tn = float(txtTn.get())
36 Tv = float(txtTv.get())
37 R = float(txtR.get())
38 L=float(txtL.get())
39 J = float(txtJ.get())
40 Ia= float(txtIa.get())
41 Mn = float(txtMn.get())
493
11 Interactive Programming Using Tkinter
42 C=1.0e3*J*(Ia/Mn)**2
43 t=y=ui=ud=i=e=e0=0
44 dt=0.05
45 sx=xmax/tmax
46 u2_t = []
47 while t<=tmax:
48 e=w-y
49 up = Kp*e
50 ui = ui + Kp*e*dt/Tn
51 ud = Kp*Tv*(e-e0)/dt
52 e0=e
53 U1 = up + ui + ud
54 i = i + (U1-R*i-y)*dt/L
55 y = y + i*dt/C
56 t=t+dt
57 u2_t.append(sx*t)
58 u2_t.append(-y+ymax)
59 xy=canZ.create_line(u2_t,fill='blue',width=2)
60
61 frame()
62 txtTmax=tk.Entry(main, width=5)
63 txtTmax.insert(5,"250")
64 #armature resistance
65 tk.Label(main,text="R in Ohm").grid(row=1,column=0,sticky="w")
66 txtR=tk.Entry(main, width=5)
67 txtR.insert(5,"1.5")
68 txtR.grid(row=1,column=1,sticky="w")
69 #armature inductance
70 tk.Label(main,text="L in mH").grid(row=2,column=0,sticky="w")
71 txtL=tk.Entry(main, width=5)
72 txtL.insert(5,"24")
73 txtL.grid(row=2,column=1,sticky="w")
74 #rated torque
75 tk.Label(main, text="Mn in Nm").grid(row=3,column=0,sticky="w")
76 txtMn=tk.Entry(main, width=5)
77 txtMn.insert(5,"172")
78 txtMn.grid(row=3,column=1,sticky="w")
79 #rated current
80 tk.Label(main, text="Ia in A").grid(row=4,column=0,sticky="w")
81 txtIa=tk.Entry(main, width=5)
82 txtIa.insert(5,"41")
83 txtIa.grid(row=4,column=1,sticky="w")
494
11.6 Project Task: Rotational Frequency Control of an Externally Excited DC Motor
84 #moment of inertia
85 tk.Label(main, text="J in kgm^2").grid(row=5,column=0,sticky="w")
86 txtJ=tk.Entry(main, width=5)
87 txtJ.insert(5,"1")
88 txtJ.grid(row=5,column=1,sticky="w")
89 #controller gain
90 tk.Label(main, text="Kp").grid(row=1,column=2,sticky="w")
91 txtKp=tk.Entry(main, width=5)
92 txtKp.insert(5,"5")
93 txtKp.grid(row=1,column=3,sticky="w")
94 #reset time
95 tk.Label(main, text="Tn in ms").grid(row=2,column=2,sticky="w")
96 txtTn=tk.Entry(main, width=5)
97 txtTn.insert(5,"50")
98 txtTn.grid(row=2,column=3,sticky="w")
99 #derivative time
100 tk.Label(main, text="Tv in ms").grid(row=3,column=2,sticky="w")
101 txtTv=tk.Entry(main, width=5)
102 txtTv.insert(5,"5")
103 txtTv.grid(row=3,column=3,sticky="w")
104 #coordinates
105 tk.Label(main, text="tmax").grid(row=4,column=2,sticky="w")
106 txtTmax.grid(row=4,column=3,sticky="w")
107 tk.Label(main, text="Coordinate").grid(row=5,column=2,sticky="w")
108 lblKoordinate=tk.Label(main,width=15)
109 lblKoordinate.grid(row=5,column=3,sticky="w")
110 #command buttons
111 tk.Button(main,text="Start",command=control_circuit,
width=7).grid(row=1,column=4)
112 tk.Button(main,text="New",command=delete,
width=7).grid(row=2,column=4)
113 tk.Button(main,text="Exit",command=main.destroy,
width=7).grid(row=3,column=4)
114 canZ.bind('<Motion>', coordinate)
115 main.mainloop()
Output
The output of the final version of the rotation frequency control simulation program is
shown in Figure 11.15.
495
11 Interactive Programming Using Tkinter
Analysis
In line 06, the global xy variable is defined, and in the control_circuit() function in
line 32, the variable is marked as global. It occurs again in line 59 where it is assigned all
the coordinate data of the simulation. Now, the delete() function in line 22 can delete
all data stored in the xy variable if it is called in line 112 by pressing New. Otherwise, the
program does not contain any other unknown program elements. For a better orienta-
tion, the individual program parts have been commented.
11.7 Tasks
1. Write a Tkinter program that computes the volume, mass, and surface area of a steel
cylinder.
2. Write a Tkinter program that calculates the current I = U/R for a simple circuit. Both
the voltage and the current are each to be changed using a slider.
3. Write a Tkinter program that computes the current and voltage drops in a series cir-
cuit of three resistors. The voltage and the resistances should be changed using a
slider in each case.
4. Write a Tkinter program that simulates a control loop with a third-order process.
5. Complement the simulation program for rotational frequency control in such a way
that you can select the individual controllers P, PI, or PID via a radio button.
496
Appendix
Term Description
Data structure Objects for which specified operations are defined; its short
formula is object + operations.
Constructor Routines for creating and initializing new objects. All objects
belonging to the class were created by constructors of this
class.
497
A Appendix
Term Description
498
A.3 Antiderivative of Elementary Functions
499
A Appendix
1 Rectangle
Rectangle
2 with
pulse width control : Pulse width
3 Sawtooth
Isosceles
4
triangle
Half-wave
5
rectification
Full-wave
6
rectification
Phase
7
angle control
: Phase angle
500
A.6 Bibliography
A.6 Bibliography
The following contains the bibliography for the original German-language edition of
this book. Where available, English-language resources have been provided:
쐍 Arens, Tilo et al: Mathematik. Berlin, Heidelberg 2015.
쐍 Bartsch, Hans-Jochen: Taschenbuch mathematischer Formeln. Munich, Vienna 1997.
쐍 Beuth, Klaus: Digitaltechnik. Würzburg 2006.
쐍 Czichos, Horst (ed.): Hütte: Die Grundlagen der Ingenieurwissenschaften. Berlin, Hei-
delberg, New York 2000.
쐍 Downey, Allen B.: Think Python. O’Reilly 2012.
쐍 Führer, Arnold; Heidemann, Klaus; Nerreter, Wolfgang: Grundgebiete der Elektro-
technik 2. Zeitabhängige Vorgänge. Munich 2011.
쐍 Halliday, David; Resnick, Robert; Walker, Jearl: Fundamentals of Physics. Extended
6th Edition. John Wiley & Sons 2001.
쐍 Herter, Eberhard; Lörcher, Wolfgang: Nachrichtentechnik. Übertragung – Vermit-
tlung – Verarbeitung. Munich, Vienna 2004.
쐍 Holbrook, James G.: Laplace Transforms for Electronic Engineers. Pergamon Press Ltd.
1966.
쐍 Höwing, Marika: Einführung in die Elektrotechnik. Bonn 2019.
501
A Appendix
쐍 Johansson, Robert: Numerical Python: Scientific Computing and Data Science Appli-
cations with Numpy, SciPy and Matplotlib. New York 2019.
쐍 Klein, Bernd: Einführung in Python. Für Ein- und Umsteiger. Munich 2021.
쐍 Klein, Bernd: Numerisches Python. Arbeiten mit NumPy, Matplotlib und Pandas.
Munich 2019.
쐍 Kofler, Michael: Python. Der Grundkurs. Bonn 2020.
쐍 Kuchling, Horst: Taschenbuch der Physik. Munich 2014.
쐍 Küpfmüller, Karl: Einstieg in die theoretische Elektrotechnik. Berlin, Heidelberg, New
York 1973.
쐍 Natt, Oliver: Physik mit Python. Simulationen, Visualisierungen und Animationen von
Anfang an. Berlin 2020.
쐍 Nerreter, Wolfgang: Grundlagen der Elektrotechnik. Munich 2006.
쐍 Philippsen, Hans-Werner: Einstieg in die Regelungstechnik. Munich 2015.
쐍 Rupprecht, Werner: Netzwerksynthese. Entwurfstheorie linearer passiver und aktiver
Zweipole und Vierpole. Berlin, Heidelberg, New York 1972.
쐍 Rybach, Johannes: Physik für Bachelors. Munich 2008.
쐍 Skolaut, Werner (ed.): Maschinenbau. Ein Lehrbuch für das ganze Bachelor-Studium.
Berlin, Heidelberg 2014.
쐍 Theis, Thomas: Einstieg in Python. Bonn 2019.
쐍 Tietze, Ulrich; Schenk, Christoph; Gamm, Eberhard: Electronic Circuits --- Handbook
for Design and Applications. Springer 2008.
쐍 Tipler, Paul A.; Mosca, Gene: Physics for Scientists and Engineers. With Modern Phys-
ics. Sixth Edition. Freeman and Company 2008.
쐍 Vöth, Stefan: Dynamik schwingungsfähiger Systeme. Von der Modellbildung bis zur
Betriebsfestigkeitsrechnung mit MATLAB/SIMULINK. Wiesbaden 2006.
쐍 Weltner, Klaus: Mathematik für Physiker und Ingenieure. Vols. 1 and 2. Berlin, Heidel-
berg 2013.
쐍 Woyand, Hans-Bernhard: Python für Ingenieure und Naturwissenschaftler. Einführung
in die Programmierung, mathematische Anwendungen und Visualisierungen. Munich
2021.
쐍 Zimmermann, Klaus: Übungsaufgaben Technische Mechanik. Cologne 1994.
502
The Author
503
Index
505
Index
506
Index
507
Index
508
Index
509
Index
510
Index
511
Service Pages
The following sections contain notes on how you can contact us.
You can also share your reading experience via Twitter, Facebook, or email.
Supplements
If there are supplements available (sample code, exercise materials, lists, and so on), they
will be provided in your online library and on the web catalog page for this book. You can
directly navigate to this page using the following link: http://www.rheinwerk-computing.
com/5852. Should we learn about typos that alter the meaning or content errors, we will
provide a list with corrections there, too.
Technical Issues
If you experience technical issues with your e-book or e-book account at Rheinwerk Com-
puting, please feel free to contact our reader service: [email protected].
i
Legal Notes
This section contains the detailed and legally binding usage conditions for this e-book.
Copyright Note
This publication is protected by copyright in its entirety. All usage and exploitation rights
are reserved by the author and Rheinwerk Publishing; in particular the right of reproduction
and the right of distribution, be it in printed or electronic form.
Copyright notes, brands, and other legal reservations as well as the digital watermark may
not be removed from the e-book.
Digital Watermark
This e-book copy contains a digital watermark, a signature that indicates which person
may use this copy. If you, dear reader, are not this person, you are violating the copyright.
So please refrain from using this e-book and inform us about this violation. A brief email to
[email protected] is sufficient. Thank you!
Trademarks
The common names, trade names, descriptions of goods, and so on used in this publication
may be trademarks without special identification and subject to legal regulations as such.
All products mentioned in this book are registered or unregistered trademarks of their
respective companies.
ii
Limitation of Liability
Regardless of the care that has been taken in creating texts, figures, and programs, neither
the publisher nor the author, editor, or translator assume any legal responsibility or any
liability for possible errors and their consequences.
iii